edmond: init
This commit is contained in:
commit
c109a0116b
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
config.json
|
11
Pipfile
Normal file
11
Pipfile
Normal 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
20
Pipfile.lock
generated
Normal 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
6
config.json.example
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"host": "irc.freenode.net",
|
||||
"port": 6667,
|
||||
"nick": "edm0nd",
|
||||
"channels": ["#idi0crates"]
|
||||
}
|
0
edmond/__init__.py
Normal file
0
edmond/__init__.py
Normal file
20
edmond/__main__.py
Normal file
20
edmond/__main__.py
Normal 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
47
edmond/bot.py
Normal 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
9
edmond/config.py
Normal 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
170
edmond/log.py
Normal 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)
|
Loading…
Reference in a new issue