2019-10-11 13:00:26 +00:00
|
|
|
import json, os, socket, typing
|
2019-10-11 14:12:26 +00:00
|
|
|
from src import IRCBot, Logging, PollSource
|
2019-10-11 13:00:26 +00:00
|
|
|
|
|
|
|
class ControlClient(object):
|
|
|
|
def __init__(self, sock: socket.socket):
|
|
|
|
self._socket = sock
|
|
|
|
self._read_buffer = b""
|
|
|
|
self._write_buffer = b""
|
2019-10-11 14:54:43 +00:00
|
|
|
self.version = -1
|
|
|
|
self.log_level = None # type: typing.Optional[int]
|
2019-10-11 13:00:26 +00:00
|
|
|
|
|
|
|
def fileno(self) -> int:
|
|
|
|
return self._socket.fileno()
|
|
|
|
|
2019-10-25 22:32:00 +00:00
|
|
|
def read_lines(self) -> typing.Optional[typing.List[str]]:
|
2019-10-11 14:16:39 +00:00
|
|
|
try:
|
|
|
|
data = self._socket.recv(2048)
|
|
|
|
except:
|
|
|
|
data = b""
|
2019-10-11 13:00:26 +00:00
|
|
|
if not data:
|
2019-10-11 14:12:26 +00:00
|
|
|
return None
|
2019-10-11 13:00:26 +00:00
|
|
|
lines = (self._read_buffer+data).split(b"\n")
|
|
|
|
lines = [line.strip(b"\r") for line in lines]
|
|
|
|
self._read_buffer = lines.pop(-1)
|
|
|
|
return [line.decode("utf8") for line in lines]
|
|
|
|
|
|
|
|
def write_line(self, line: str):
|
2019-10-11 14:12:26 +00:00
|
|
|
self._socket.send(("%s\n" % line).encode("utf8"))
|
2019-10-11 13:00:26 +00:00
|
|
|
|
|
|
|
def disconnect(self):
|
|
|
|
try:
|
|
|
|
self._socket.shutdown(socket.SHUT_RDWR)
|
|
|
|
except:
|
|
|
|
pass
|
|
|
|
try:
|
|
|
|
self._socket.close()
|
|
|
|
except:
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class Control(PollSource.PollSource):
|
2019-10-17 11:39:56 +00:00
|
|
|
def __init__(self, bot: IRCBot.Bot, filename: str):
|
2019-10-11 14:12:26 +00:00
|
|
|
self._bot = bot
|
|
|
|
self._bot.log.hook(self._on_log)
|
|
|
|
|
2019-10-17 11:39:56 +00:00
|
|
|
self._filename = filename
|
2019-10-11 13:00:26 +00:00
|
|
|
self._socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
2019-10-25 22:32:00 +00:00
|
|
|
self._clients: typing.Dict[int, ControlClient] = {}
|
2019-10-11 13:00:26 +00:00
|
|
|
|
2019-10-11 14:12:26 +00:00
|
|
|
def _on_log(self, levelno: int, line: str):
|
|
|
|
for client in self._clients.values():
|
2019-10-25 22:32:00 +00:00
|
|
|
if client.log_level is not None and client.log_level <= levelno:
|
2019-10-11 14:12:26 +00:00
|
|
|
self._send_action(client, "log", line)
|
|
|
|
|
2019-10-11 13:00:26 +00:00
|
|
|
def bind(self):
|
2019-10-17 11:39:56 +00:00
|
|
|
if os.path.exists(self._filename):
|
|
|
|
os.remove(self._filename)
|
|
|
|
self._socket.bind(self._filename)
|
2019-10-11 13:00:26 +00:00
|
|
|
self._socket.listen(1)
|
|
|
|
|
|
|
|
def get_readables(self) -> typing.List[int]:
|
|
|
|
return [self._socket.fileno()]+list(self._clients.keys())
|
|
|
|
|
|
|
|
def is_readable(self, fileno: int):
|
|
|
|
if fileno == self._socket.fileno():
|
2019-10-14 17:17:15 +00:00
|
|
|
client_s, address = self._socket.accept()
|
|
|
|
self._clients[client_s.fileno()] = ControlClient(client_s)
|
2019-10-11 14:54:43 +00:00
|
|
|
self._bot.log.debug("New control socket connected")
|
2019-10-11 13:00:26 +00:00
|
|
|
elif fileno in self._clients:
|
|
|
|
client = self._clients[fileno]
|
|
|
|
lines = client.read_lines()
|
2019-10-25 22:32:00 +00:00
|
|
|
if lines is None:
|
2019-10-11 13:00:26 +00:00
|
|
|
client.disconnect()
|
|
|
|
del self._clients[fileno]
|
|
|
|
else:
|
|
|
|
for line in lines:
|
|
|
|
response = self._parse_line(client, line)
|
|
|
|
|
|
|
|
def _parse_line(self, client: ControlClient, line: str):
|
2019-10-11 14:12:26 +00:00
|
|
|
id, _, command = line.partition(" ")
|
|
|
|
command, _, data = command.partition(" ")
|
|
|
|
if not id or not command:
|
|
|
|
client.disconnect()
|
|
|
|
return
|
|
|
|
|
|
|
|
command = command.lower()
|
|
|
|
response_action = "ack"
|
|
|
|
response_data = None
|
|
|
|
|
2019-10-11 14:58:47 +00:00
|
|
|
keepalive = True
|
|
|
|
|
2019-10-11 14:12:26 +00:00
|
|
|
if command == "version":
|
|
|
|
client.version = int(data)
|
|
|
|
elif command == "log":
|
|
|
|
client.log_level = Logging.LEVELS[data.lower()]
|
2019-10-11 14:54:43 +00:00
|
|
|
elif command == "rehash":
|
2019-10-11 14:58:47 +00:00
|
|
|
self._bot.log.info("Reloading config file")
|
2019-10-11 14:54:43 +00:00
|
|
|
self._bot.config.load()
|
2019-10-11 14:58:47 +00:00
|
|
|
self._bot.log.info("Reloaded config file")
|
|
|
|
keepalive = False
|
2019-10-14 13:52:58 +00:00
|
|
|
elif command == "reload":
|
|
|
|
result = self._bot.try_reload_modules()
|
|
|
|
response_data = result.message
|
|
|
|
keepalive = False
|
2019-10-17 15:53:48 +00:00
|
|
|
elif command == "stop":
|
|
|
|
self._bot.stop()
|
2019-10-11 14:12:26 +00:00
|
|
|
|
|
|
|
self._send_action(client, response_action, response_data, id)
|
2019-10-11 14:58:47 +00:00
|
|
|
if not keepalive:
|
|
|
|
client.disconnect()
|
2019-10-11 14:12:26 +00:00
|
|
|
|
2019-10-25 22:32:00 +00:00
|
|
|
def _send_action(self, client: ControlClient, action: str,
|
|
|
|
data: typing.Optional[str], id: typing.Optional[str]=None):
|
2019-11-08 13:38:07 +00:00
|
|
|
try:
|
|
|
|
client.write_line(
|
|
|
|
json.dumps({"action": action, "data": data, "id": id}))
|
|
|
|
except:
|
|
|
|
pass
|