edmond: init

This commit is contained in:
dece 2020-10-08 18:46:45 +02:00
commit c109a0116b
9 changed files with 284 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
config.json

11
Pipfile Normal file
View file

@ -0,0 +1,11 @@
[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true
[dev-packages]
[packages]
[requires]
python_version = "3.7"

20
Pipfile.lock generated Normal file
View file

@ -0,0 +1,20 @@
{
"_meta": {
"hash": {
"sha256": "7e7ef69da7248742e869378f8421880cf8f0017f96d94d086813baa518a65489"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.7"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {},
"develop": {}
}

6
config.json.example Normal file
View file

@ -0,0 +1,6 @@
{
"host": "irc.freenode.net",
"port": 6667,
"nick": "edm0nd",
"channels": ["#idi0crates"]
}

0
edmond/__init__.py Normal file
View file

20
edmond/__main__.py Normal file
View file

@ -0,0 +1,20 @@
import argparse
import edmond.bot
import edmond.config
import edmond.log
def main():
argparser = argparse.ArgumentParser()
argparser.add_argument("-c", "--config", default="config.json")
args = argparser.parse_args()
logger = edmond.log.get_logger(name="edmond")
config = edmond.config.load_config(args.config, logger=logger)
bot = edmond.bot.Bot(config, logger)
bot.run()
if __name__ == "__main__":
main()

47
edmond/bot.py Normal file
View file

@ -0,0 +1,47 @@
import irc.client
from irc.client import NickMask
from edmond.log import Logger
class Bot(irc.client.SimpleIRCClient, Logger):
def __init__(self, config, logger):
super().__init__()
self.config = config
self.logger = logger
@property
def nick(self):
return self.config["nick"]
def on_welcome(self, connection, event):
self.log_i(f"Connected to server {event.source}.")
for channel in self.config["channels"]:
connection.join(channel)
def on_join(self, connection, event):
self.log_i(f"Joined {event.target}.")
def on_part(self, connection, event):
self.log_i(f"Left {event.target} (args: {event.arguments[0]}).")
def on_pubmsg(self, connection, event):
channel = event.target
nick = NickMask(event.source).nick
message = event.arguments[0]
self.log_d(f"Message in {channel} from {nick}: {message}")
def on_privmsg(self, connection, event):
nick = NickMask(event.source).nick
target = event.target
message = event.arguments[0]
self.log_d(f"Private message from {nick} to {target}: {message}")
def run(self):
self.log_i("Starting Edmond.")
self.connect(self.config["host"], self.config["port"], self.nick)
try:
self.start()
except KeyboardInterrupt:
self.log_i("Stopping Edmond.")

9
edmond/config.py Normal file
View file

@ -0,0 +1,9 @@
import json
def load_config(config_path, logger=None):
try:
with open(config_path, "rt") as config_file:
return json.load(config_file)
except OSError as exc:
logger.critical(f"Could not load config file: {exc}")

170
edmond/log.py Normal file
View file

@ -0,0 +1,170 @@
"""A cross-platform, package independant, colored stream/file logger."""
import logging
import platform
import ctypes
import ctypes.util
class _AnsiColorStreamHandler(logging.StreamHandler):
DEFAULT = '\x1b[0m'
RED = '\x1b[31m'
GREEN = '\x1b[32m'
YELLOW = '\x1b[33m'
CYAN = '\x1b[36m'
CRITICAL = RED
ERROR = RED
WARNING = YELLOW
INFO = GREEN
DEBUG = CYAN
def __init__(self, stream=None):
super().__init__(stream)
def format(self, record):
text = super().format(record)
color = self._get_color_code(record.levelno)
return color + text + self.DEFAULT
@classmethod
def _get_color_code(cls, level):
if level >= logging.CRITICAL:
return cls.CRITICAL
elif level >= logging.ERROR:
return cls.ERROR
elif level >= logging.WARNING:
return cls.WARNING
elif level >= logging.INFO:
return cls.INFO
elif level >= logging.DEBUG:
return cls.DEBUG
else:
return cls.DEFAULT
# Disable protected member access warning for MSVC functions.
# pylint: disable=W0212
class _WinColorStreamHandler(logging.StreamHandler):
STD_INPUT_HANDLE = -10
STD_OUTPUT_HANDLE = -11
STD_ERROR_HANDLE = -12
FOREGROUND_BLACK = 0x0000
FOREGROUND_BLUE = 0x0001
FOREGROUND_GREEN = 0x0002
FOREGROUND_CYAN = 0x0003
FOREGROUND_RED = 0x0004
FOREGROUND_MAGENTA = 0x0005
FOREGROUND_YELLOW = 0x0006
FOREGROUND_GREY = 0x0007
FOREGROUND_INTENSITY = 0x0008
FOREGROUND_WHITE = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED
BACKGROUND_BLACK = 0x0000
BACKGROUND_BLUE = 0x0010
BACKGROUND_GREEN = 0x0020
BACKGROUND_CYAN = 0x0030
BACKGROUND_RED = 0x0040
BACKGROUND_MAGENTA = 0x0050
BACKGROUND_YELLOW = 0x0060
BACKGROUND_GREY = 0x0070
BACKGROUND_INTENSITY = 0x0080
DEFAULT = FOREGROUND_WHITE
CRITICAL = FOREGROUND_RED | FOREGROUND_INTENSITY
ERROR = FOREGROUND_RED | FOREGROUND_INTENSITY
WARNING = FOREGROUND_YELLOW | FOREGROUND_INTENSITY
INFO = FOREGROUND_GREEN
DEBUG = FOREGROUND_CYAN
def __init__(self, stream=None):
super().__init__(stream)
self.output_handle = self._get_output_handle(stream)
@classmethod
def _get_output_handle(cls, stream):
if stream is None:
return ctypes.windll.kernel32.GetStdHandle(cls.STD_OUTPUT_HANDLE)
else:
msvcrt_loc = ctypes.util.find_msvcrt()
msvcrt_lib = ctypes.cdll.LoadLibrary(msvcrt_loc)
return msvcrt_lib._get_osfhandle(stream.fileno())
def emit(self, record):
color_code = self._get_color_code(record.levelno)
self._set_color_code(color_code)
super().emit(record)
self._set_color_code(self.FOREGROUND_WHITE)
@classmethod
def _get_color_code(cls, level):
if level >= logging.CRITICAL:
return cls.CRITICAL
elif level >= logging.ERROR:
return cls.ERROR
elif level >= logging.WARNING:
return cls.WARNING
elif level >= logging.INFO:
return cls.INFO
elif level >= logging.DEBUG:
return cls.DEBUG
else:
return cls.DEFAULT
def _set_color_code(self, code):
ctypes.windll.kernel32.SetConsoleTextAttribute(self.output_handle, code)
if platform.system() == "Windows":
ColorStreamHandler = _WinColorStreamHandler
else:
ColorStreamHandler = _AnsiColorStreamHandler
_LOG_LEVEL = logging.DEBUG
_FORMAT = "%(asctime)s %(levelname)-8s %(message)s"
_DATE_FORMAT = "%H:%M:%S"
def get_logger( name="pyshgck", level=_LOG_LEVEL
, log_format=_FORMAT, date_format=_DATE_FORMAT
, into_stderr=True, into_log_file=None ):
logger = logging.getLogger(name)
logger.setLevel(level)
formatter = logging.Formatter(fmt=log_format, datefmt=date_format)
if into_stderr:
stream_handler = ColorStreamHandler()
stream_handler.setLevel(level)
stream_handler.setFormatter(formatter)
logger.addHandler(stream_handler)
if into_log_file is not None:
file_handler = logging.FileHandler(into_log_file, mode="w")
file_handler.setLevel(level)
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)
return logger
class Logger:
def log_d(self, message):
self.logger.debug(message)
def log_i(self, message):
self.logger.info(message)
def log_w(self, message):
self.logger.warning(message)
def log_e(self, message):
self.logger.error(message)
def log_c(self, message):
self.logger.critical(message)