MkDocs, Readme, Files API, Automated session saving, v2.0.1
MkDocs: sphinx docstrings rewritten to google, improved config, written the major part of how-to.
Readme: centered title + logo, added badges, features list, updated changelog.
Improved Files API, added automatical session saving and restoring to Client.
Some changes in makefile and gitignore.
License Notice now refers to all contributors.
2022-08-26 16:14:07 +04:00
|
|
|
"""Connects to Aternos WebSocket API
|
2022-07-01 14:28:39 +04:00
|
|
|
for real-time information"""
|
|
|
|
|
2022-01-22 15:10:30 +04:00
|
|
|
import enum
|
|
|
|
import json
|
|
|
|
import asyncio
|
2022-08-22 09:55:08 +04:00
|
|
|
|
2022-10-05 16:59:48 +04:00
|
|
|
from typing import Iterable
|
2022-07-01 14:28:39 +04:00
|
|
|
from typing import Union, Any
|
2022-10-05 16:59:48 +04:00
|
|
|
from typing import Tuple, List, Dict
|
2022-07-01 14:28:39 +04:00
|
|
|
from typing import Callable, Coroutine
|
2022-03-25 16:45:38 +04:00
|
|
|
from typing import TYPE_CHECKING
|
2022-01-22 15:10:30 +04:00
|
|
|
|
2022-08-22 09:55:08 +04:00
|
|
|
import websockets
|
|
|
|
|
2023-05-24 18:15:18 +04:00
|
|
|
from .atlog import log
|
2022-01-22 15:10:30 +04:00
|
|
|
from .atconnect import REQUA
|
2023-05-24 18:15:18 +04:00
|
|
|
|
2022-03-25 16:45:38 +04:00
|
|
|
if TYPE_CHECKING:
|
2022-06-23 15:13:56 +04:00
|
|
|
from .atserver import AternosServer
|
|
|
|
|
2023-05-24 18:15:18 +04:00
|
|
|
|
2022-07-01 14:28:39 +04:00
|
|
|
OneArgT = Callable[[Any], Coroutine[Any, Any, None]]
|
|
|
|
TwoArgT = Callable[[Any, Tuple[Any, ...]], Coroutine[Any, Any, None]]
|
2023-06-30 11:07:31 +04:00
|
|
|
FunctionT = Union[OneArgT, TwoArgT] # pylint: disable=invalid-name
|
2022-07-01 14:28:39 +04:00
|
|
|
ArgsTuple = Tuple[FunctionT, Tuple[Any, ...]]
|
2022-07-01 09:36:52 +04:00
|
|
|
|
2022-01-22 15:10:30 +04:00
|
|
|
|
2022-04-06 11:44:14 +04:00
|
|
|
class Streams(enum.Enum):
|
|
|
|
|
2022-06-23 15:13:56 +04:00
|
|
|
"""WebSocket streams types"""
|
|
|
|
|
|
|
|
status = (0, None)
|
|
|
|
queue = (1, None)
|
|
|
|
console = (2, 'console')
|
|
|
|
ram = (3, 'heap')
|
|
|
|
tps = (4, 'tick')
|
2022-07-01 14:28:39 +04:00
|
|
|
none = (-1, None)
|
2022-06-17 12:30:58 +04:00
|
|
|
|
2022-06-23 15:13:56 +04:00
|
|
|
def __init__(self, num: int, stream: str) -> None:
|
MkDocs, Readme, Files API, Automated session saving, v2.0.1
MkDocs: sphinx docstrings rewritten to google, improved config, written the major part of how-to.
Readme: centered title + logo, added badges, features list, updated changelog.
Improved Files API, added automatical session saving and restoring to Client.
Some changes in makefile and gitignore.
License Notice now refers to all contributors.
2022-08-26 16:14:07 +04:00
|
|
|
|
2022-06-23 15:13:56 +04:00
|
|
|
self.num = num
|
|
|
|
self.stream = stream
|
2022-04-06 11:44:14 +04:00
|
|
|
|
2022-01-22 15:10:30 +04:00
|
|
|
|
|
|
|
class AternosWss:
|
|
|
|
|
MkDocs, Readme, Files API, Automated session saving, v2.0.1
MkDocs: sphinx docstrings rewritten to google, improved config, written the major part of how-to.
Readme: centered title + logo, added badges, features list, updated changelog.
Improved Files API, added automatical session saving and restoring to Client.
Some changes in makefile and gitignore.
License Notice now refers to all contributors.
2022-08-26 16:14:07 +04:00
|
|
|
"""Class for managing websocket connection"""
|
2022-06-23 15:13:56 +04:00
|
|
|
|
2022-07-01 14:28:39 +04:00
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
atserv: 'AternosServer',
|
|
|
|
autoconfirm: bool = False) -> None:
|
MkDocs, Readme, Files API, Automated session saving, v2.0.1
MkDocs: sphinx docstrings rewritten to google, improved config, written the major part of how-to.
Readme: centered title + logo, added badges, features list, updated changelog.
Improved Files API, added automatical session saving and restoring to Client.
Some changes in makefile and gitignore.
License Notice now refers to all contributors.
2022-08-26 16:14:07 +04:00
|
|
|
"""Class for managing websocket connection
|
|
|
|
|
|
|
|
Args:
|
|
|
|
atserv (AternosServer):
|
|
|
|
atserver.AternosServer instance
|
|
|
|
autoconfirm (bool, optional):
|
|
|
|
Automatically start server status listener
|
|
|
|
when AternosWss connects to API to confirm
|
|
|
|
server launching
|
|
|
|
"""
|
|
|
|
|
2022-06-23 15:13:56 +04:00
|
|
|
self.atserv = atserv
|
|
|
|
self.servid = atserv.servid
|
2022-07-01 14:28:39 +04:00
|
|
|
|
2022-08-22 09:55:08 +04:00
|
|
|
cookies = atserv.atconn.session.cookies
|
|
|
|
self.session = cookies['ATERNOS_SESSION']
|
|
|
|
|
2022-10-05 16:59:48 +04:00
|
|
|
self.recv: Dict[Streams, List[ArgsTuple]]
|
|
|
|
self.recv = {
|
|
|
|
Streams.status: [],
|
|
|
|
Streams.queue: [],
|
|
|
|
Streams.console: [],
|
|
|
|
Streams.ram: [],
|
|
|
|
Streams.tps: [],
|
|
|
|
}
|
|
|
|
|
2022-06-23 15:13:56 +04:00
|
|
|
self.autoconfirm = autoconfirm
|
|
|
|
self.confirmed = False
|
|
|
|
|
2022-10-05 16:59:48 +04:00
|
|
|
self.socket: Any = None
|
2022-07-01 14:28:39 +04:00
|
|
|
self.keep: asyncio.Task
|
|
|
|
self.msgs: asyncio.Task
|
|
|
|
|
2022-06-23 15:13:56 +04:00
|
|
|
async def confirm(self) -> None:
|
|
|
|
|
MkDocs, Readme, Files API, Automated session saving, v2.0.1
MkDocs: sphinx docstrings rewritten to google, improved config, written the major part of how-to.
Readme: centered title + logo, added badges, features list, updated changelog.
Improved Files API, added automatical session saving and restoring to Client.
Some changes in makefile and gitignore.
License Notice now refers to all contributors.
2022-08-26 16:14:07 +04:00
|
|
|
"""Simple way to call
|
|
|
|
`AternosServer.confirm`
|
|
|
|
from this class"""
|
2022-06-23 15:13:56 +04:00
|
|
|
|
|
|
|
self.atserv.confirm()
|
|
|
|
|
2022-07-01 14:28:39 +04:00
|
|
|
def wssreceiver(
|
|
|
|
self,
|
|
|
|
stream: Streams,
|
2022-10-05 16:59:48 +04:00
|
|
|
arg: Tuple[Any, ...] = ()) -> Callable[[FunctionT], Any]:
|
2022-06-23 15:13:56 +04:00
|
|
|
"""Decorator that marks your function as a stream receiver.
|
|
|
|
When websocket receives message from the specified stream,
|
|
|
|
it calls all listeners created with this decorator.
|
|
|
|
|
MkDocs, Readme, Files API, Automated session saving, v2.0.1
MkDocs: sphinx docstrings rewritten to google, improved config, written the major part of how-to.
Readme: centered title + logo, added badges, features list, updated changelog.
Improved Files API, added automatical session saving and restoring to Client.
Some changes in makefile and gitignore.
License Notice now refers to all contributors.
2022-08-26 16:14:07 +04:00
|
|
|
Args:
|
|
|
|
stream (Streams): Stream that your function should listen
|
2022-10-05 16:59:48 +04:00
|
|
|
arg (Tuple[Any, ...], optional): Arguments which will be passed to your function
|
MkDocs, Readme, Files API, Automated session saving, v2.0.1
MkDocs: sphinx docstrings rewritten to google, improved config, written the major part of how-to.
Readme: centered title + logo, added badges, features list, updated changelog.
Improved Files API, added automatical session saving and restoring to Client.
Some changes in makefile and gitignore.
License Notice now refers to all contributors.
2022-08-26 16:14:07 +04:00
|
|
|
|
|
|
|
Returns:
|
|
|
|
...
|
2022-06-23 15:13:56 +04:00
|
|
|
"""
|
|
|
|
|
2022-10-05 16:59:48 +04:00
|
|
|
def decorator(func: FunctionT) -> Callable[[Any, Any], Coroutine[Any, Any, Any]]:
|
|
|
|
|
|
|
|
handlers = self.recv.get(stream, None)
|
|
|
|
|
|
|
|
if handlers is None:
|
|
|
|
self.recv[stream] = [(func, arg)]
|
|
|
|
else:
|
|
|
|
handlers.append((func, arg))
|
|
|
|
|
|
|
|
async def wrapper(*args, **kwargs) -> Any:
|
|
|
|
return await func(*args, **kwargs)
|
|
|
|
|
|
|
|
return wrapper
|
|
|
|
|
2022-06-23 15:13:56 +04:00
|
|
|
return decorator
|
|
|
|
|
|
|
|
async def connect(self) -> None:
|
|
|
|
|
MkDocs, Readme, Files API, Automated session saving, v2.0.1
MkDocs: sphinx docstrings rewritten to google, improved config, written the major part of how-to.
Readme: centered title + logo, added badges, features list, updated changelog.
Improved Files API, added automatical session saving and restoring to Client.
Some changes in makefile and gitignore.
License Notice now refers to all contributors.
2022-08-26 16:14:07 +04:00
|
|
|
"""Connects to the websocket server
|
|
|
|
and starts all stream listeners"""
|
2022-06-23 15:13:56 +04:00
|
|
|
|
|
|
|
headers = [
|
|
|
|
('Host', 'aternos.org'),
|
|
|
|
('User-Agent', REQUA),
|
|
|
|
(
|
|
|
|
'Cookie',
|
|
|
|
f'ATERNOS_SESSION={self.session}; '
|
|
|
|
f'ATERNOS_SERVER={self.servid}'
|
|
|
|
)
|
|
|
|
]
|
2022-07-01 14:28:39 +04:00
|
|
|
self.socket = await websockets.connect( # type: ignore
|
2022-06-23 15:13:56 +04:00
|
|
|
'wss://aternos.org/hermes/',
|
|
|
|
origin='https://aternos.org',
|
|
|
|
extra_headers=headers
|
|
|
|
)
|
|
|
|
|
|
|
|
@self.wssreceiver(Streams.status)
|
MkDocs, Readme, Files API, Automated session saving, v2.0.1
MkDocs: sphinx docstrings rewritten to google, improved config, written the major part of how-to.
Readme: centered title + logo, added badges, features list, updated changelog.
Improved Files API, added automatical session saving and restoring to Client.
Some changes in makefile and gitignore.
License Notice now refers to all contributors.
2022-08-26 16:14:07 +04:00
|
|
|
async def confirmfunc(msg: Dict[str, Any]) -> None:
|
2022-06-23 15:13:56 +04:00
|
|
|
|
MkDocs, Readme, Files API, Automated session saving, v2.0.1
MkDocs: sphinx docstrings rewritten to google, improved config, written the major part of how-to.
Readme: centered title + logo, added badges, features list, updated changelog.
Improved Files API, added automatical session saving and restoring to Client.
Some changes in makefile and gitignore.
License Notice now refers to all contributors.
2022-08-26 16:14:07 +04:00
|
|
|
"""Automatically confirm
|
|
|
|
Minecraft server launching
|
|
|
|
|
|
|
|
Args:
|
|
|
|
msg (Dict[str, Any]): Server info dictionary
|
|
|
|
"""
|
2022-06-23 15:13:56 +04:00
|
|
|
|
|
|
|
if not self.autoconfirm:
|
|
|
|
return
|
|
|
|
|
2023-05-24 20:03:09 +04:00
|
|
|
in_queue = msg['class'] == 'queueing'
|
|
|
|
pending = msg['queue']['pending'] == 'pending'
|
2022-06-23 15:13:56 +04:00
|
|
|
confirmation = in_queue and pending
|
|
|
|
|
|
|
|
if confirmation and not self.confirmed:
|
MkDocs, Readme, Files API, Automated session saving, v2.0.1
MkDocs: sphinx docstrings rewritten to google, improved config, written the major part of how-to.
Readme: centered title + logo, added badges, features list, updated changelog.
Improved Files API, added automatical session saving and restoring to Client.
Some changes in makefile and gitignore.
License Notice now refers to all contributors.
2022-08-26 16:14:07 +04:00
|
|
|
await self.confirm()
|
2022-06-23 15:13:56 +04:00
|
|
|
|
|
|
|
@self.wssreceiver(Streams.status)
|
MkDocs, Readme, Files API, Automated session saving, v2.0.1
MkDocs: sphinx docstrings rewritten to google, improved config, written the major part of how-to.
Readme: centered title + logo, added badges, features list, updated changelog.
Improved Files API, added automatical session saving and restoring to Client.
Some changes in makefile and gitignore.
License Notice now refers to all contributors.
2022-08-26 16:14:07 +04:00
|
|
|
async def streamsfunc(msg: Dict[str, Any]) -> None:
|
2022-06-23 15:13:56 +04:00
|
|
|
|
|
|
|
"""Automatically starts streams. Detailed description:
|
|
|
|
|
MkDocs, Readme, Files API, Automated session saving, v2.0.1
MkDocs: sphinx docstrings rewritten to google, improved config, written the major part of how-to.
Readme: centered title + logo, added badges, features list, updated changelog.
Improved Files API, added automatical session saving and restoring to Client.
Some changes in makefile and gitignore.
License Notice now refers to all contributors.
2022-08-26 16:14:07 +04:00
|
|
|
https://github.com/DarkCat09/python-aternos/issues/22#issuecomment-1146788496
|
2022-06-23 15:13:56 +04:00
|
|
|
According to the websocket messages from the web site,
|
|
|
|
Aternos can't receive any data from a stream (e.g. console) until
|
|
|
|
it requests this stream via the special message
|
|
|
|
to the websocket server: `{"stream":"console","type":"start"}`
|
|
|
|
on which the server responses with: `{"type":"connected"}`
|
|
|
|
Also, there are RAM (used heap) and TPS (ticks per second)
|
|
|
|
streams that must be enabled before trying to get information.
|
|
|
|
Enabling the stream for listening the server status is not needed,
|
|
|
|
these data is sent from API by default, so there's None value in
|
|
|
|
the second item of its stream type tuple
|
|
|
|
(`<Streams.status: (0, None)>`).
|
MkDocs, Readme, Files API, Automated session saving, v2.0.1
MkDocs: sphinx docstrings rewritten to google, improved config, written the major part of how-to.
Readme: centered title + logo, added badges, features list, updated changelog.
Improved Files API, added automatical session saving and restoring to Client.
Some changes in makefile and gitignore.
License Notice now refers to all contributors.
2022-08-26 16:14:07 +04:00
|
|
|
|
|
|
|
Args:
|
|
|
|
msg (Dict[str, Any]): Server info dictionary
|
2022-06-23 15:13:56 +04:00
|
|
|
"""
|
|
|
|
|
|
|
|
if msg['status'] == 2:
|
|
|
|
# Automatically start streams
|
|
|
|
for strm in self.recv:
|
|
|
|
|
|
|
|
if not isinstance(strm, Streams):
|
|
|
|
continue
|
|
|
|
|
2022-10-05 16:59:48 +04:00
|
|
|
# If the handlers list is empty
|
|
|
|
if not self.recv.get(strm):
|
|
|
|
continue
|
|
|
|
|
2022-06-23 15:13:56 +04:00
|
|
|
if strm.stream:
|
2023-05-24 18:15:18 +04:00
|
|
|
log.debug('Requesting %s stream', strm.stream)
|
2022-06-23 15:13:56 +04:00
|
|
|
await self.send({
|
|
|
|
'stream': strm.stream,
|
|
|
|
'type': 'start'
|
|
|
|
})
|
|
|
|
|
|
|
|
await self.wssworker()
|
|
|
|
|
|
|
|
async def close(self) -> None:
|
|
|
|
|
|
|
|
"""Closes websocket connection and stops all listeners"""
|
|
|
|
|
|
|
|
self.keep.cancel()
|
|
|
|
self.msgs.cancel()
|
|
|
|
await self.socket.close()
|
|
|
|
del self.socket
|
|
|
|
|
|
|
|
async def send(self, obj: Union[Dict[str, Any], str]) -> None:
|
|
|
|
|
|
|
|
"""Sends a message to websocket server
|
|
|
|
|
MkDocs, Readme, Files API, Automated session saving, v2.0.1
MkDocs: sphinx docstrings rewritten to google, improved config, written the major part of how-to.
Readme: centered title + logo, added badges, features list, updated changelog.
Improved Files API, added automatical session saving and restoring to Client.
Some changes in makefile and gitignore.
License Notice now refers to all contributors.
2022-08-26 16:14:07 +04:00
|
|
|
Args:
|
|
|
|
obj (Union[Dict[str, Any],str]):
|
|
|
|
Message, may be a string or a dict
|
2022-06-23 15:13:56 +04:00
|
|
|
"""
|
|
|
|
|
2022-10-05 16:59:48 +04:00
|
|
|
if self.socket is None:
|
2023-05-24 18:15:18 +04:00
|
|
|
log.warning('Did you forget to call socket.connect?')
|
2022-10-05 16:59:48 +04:00
|
|
|
await self.connect()
|
|
|
|
|
2022-10-05 17:08:24 +04:00
|
|
|
assert self.socket is not None
|
|
|
|
|
2022-06-23 15:13:56 +04:00
|
|
|
if isinstance(obj, dict):
|
|
|
|
obj = json.dumps(obj)
|
|
|
|
|
|
|
|
await self.socket.send(obj)
|
2022-10-05 17:08:24 +04:00
|
|
|
|
2022-10-05 16:59:48 +04:00
|
|
|
async def command(self, cmd: str) -> None:
|
|
|
|
|
|
|
|
"""Sends a Minecraft command
|
|
|
|
to the websocket server
|
|
|
|
|
|
|
|
Args:
|
|
|
|
cmd (str): Command, slash at
|
|
|
|
the beginning is not required
|
|
|
|
"""
|
|
|
|
|
|
|
|
await self.send(
|
|
|
|
{
|
|
|
|
'stream': 'console',
|
|
|
|
'type': 'command',
|
|
|
|
'data': cmd,
|
|
|
|
}
|
|
|
|
)
|
2022-06-23 15:13:56 +04:00
|
|
|
|
|
|
|
async def wssworker(self) -> None:
|
|
|
|
|
|
|
|
"""Starts async tasks in background
|
|
|
|
for receiving websocket messages
|
|
|
|
and sending keepalive ping"""
|
|
|
|
|
|
|
|
self.keep = asyncio.create_task(self.keepalive())
|
|
|
|
self.msgs = asyncio.create_task(self.receiver())
|
|
|
|
|
|
|
|
async def keepalive(self) -> None:
|
|
|
|
|
2022-10-05 16:59:48 +04:00
|
|
|
"""Each 49 seconds sends keepalive ping
|
|
|
|
to the websocket server"""
|
2022-06-23 15:13:56 +04:00
|
|
|
|
|
|
|
try:
|
|
|
|
while True:
|
|
|
|
await asyncio.sleep(49)
|
|
|
|
await self.socket.send('{"type":"\u2764"}')
|
|
|
|
|
|
|
|
except asyncio.CancelledError:
|
|
|
|
pass
|
|
|
|
|
|
|
|
async def receiver(self) -> None:
|
|
|
|
|
|
|
|
"""Receives messages from websocket servers
|
|
|
|
and calls user's streams listeners"""
|
|
|
|
|
2022-07-01 14:28:39 +04:00
|
|
|
while True:
|
|
|
|
try:
|
2022-06-23 15:13:56 +04:00
|
|
|
data = await self.socket.recv()
|
|
|
|
obj = json.loads(data)
|
2022-07-01 14:28:39 +04:00
|
|
|
msgtype = Streams.none
|
2022-06-23 15:13:56 +04:00
|
|
|
|
|
|
|
if obj['type'] == 'line':
|
|
|
|
msgtype = Streams.console
|
|
|
|
msg = obj['data'].strip('\r\n ')
|
|
|
|
|
|
|
|
elif obj['type'] == 'heap':
|
|
|
|
msgtype = Streams.ram
|
|
|
|
msg = int(obj['data']['usage'])
|
|
|
|
|
|
|
|
elif obj['type'] == 'tick':
|
|
|
|
msgtype = Streams.tps
|
|
|
|
ticks = 1000 / obj['data']['averageTickTime']
|
|
|
|
msg = 20 if ticks > 20 else ticks
|
|
|
|
|
|
|
|
elif obj['type'] == 'status':
|
|
|
|
msgtype = Streams.status
|
|
|
|
msg = json.loads(obj['message'])
|
|
|
|
|
|
|
|
if msgtype in self.recv:
|
|
|
|
|
2022-10-05 16:59:48 +04:00
|
|
|
# function info tuples
|
|
|
|
handlers: Iterable[ArgsTuple]
|
|
|
|
handlers = self.recv.get(msgtype, ())
|
|
|
|
|
|
|
|
for func in handlers:
|
|
|
|
|
|
|
|
# if arguments is not empty
|
|
|
|
if func[1]:
|
|
|
|
# call the function with args
|
|
|
|
coro = func[0](msg, func[1]) # type: ignore
|
|
|
|
|
|
|
|
else:
|
|
|
|
# mypy error: too few arguments
|
|
|
|
# looks like a bug, so it is ignored
|
|
|
|
coro = func[0](msg) # type: ignore
|
|
|
|
|
|
|
|
# run
|
|
|
|
asyncio.create_task(coro)
|
2022-06-23 15:13:56 +04:00
|
|
|
|
2022-07-01 14:28:39 +04:00
|
|
|
except asyncio.CancelledError:
|
|
|
|
break
|