add a logger

This commit is contained in:
Romain H 2022-02-27 15:15:18 +01:00 committed by Romain Hv
parent d9894fbd87
commit 93ab327955
1 changed files with 61 additions and 50 deletions

View File

@ -6,11 +6,16 @@ import re
import select
import os.path
import configparser
import logging
from datetime import datetime, timedelta, timezone
from typing import Generator
from typing import Generator, Tuple, List
import feedparser
LOGGER = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO,
format="%(asctime)s %(levelname)s %(threadName)s %(name)s %(message)s")
# Help configuration.
HELP_LIST = ["help", "faq", "aide"]
HELLO_LIST = ["hello", "yo", "bonjour", "salut"]
@ -21,7 +26,7 @@ if len(sys.argv) == 2:
CONFIG_FILENAME = sys.argv[1]
if os.path.isfile(CONFIG_FILENAME):
print(f"Using configuration file: {CONFIG_FILENAME}")
LOGGER.info('Using configuration file: %s', CONFIG_FILENAME)
CONFIG = configparser.ConfigParser()
CONFIG.read(CONFIG_FILENAME)
@ -38,7 +43,7 @@ if os.path.isfile(CONFIG_FILENAME):
PROJECT_CHANNEL_3 = CONFIG.get("IRCSection", "irc.projects.3.channel")
else:
print("Missing configuration file.")
LOGGER.error("Missing configuration file.")
sys.exit()
@ -50,7 +55,7 @@ class Project:
self.redmine_next = datetime.now(tz=timezone.utc)
self.redmine_latest = datetime.now(tz=timezone.utc)
def redmine(self) -> Generator[str, None, None]:
def generate_messages(self) -> Generator[str, None, None]:
now = datetime.now(tz=timezone.utc)
if now < self.redmine_next:
return
@ -73,7 +78,7 @@ class Project:
self.redmine_latest = latest_new
def generate_command_regex(options):
def generate_command_regex(options: List[str]) -> str:
pattern = "("
for option in options:
pattern += option
@ -83,19 +88,17 @@ def generate_command_regex(options):
return pattern
def parse_irc_messages(irc_msg):
def parse_irc_messages(irc_msg: str) -> Tuple[str, str, str]:
try:
actor = irc_msg.split(":")[1].split("!")[0]
try:
target = irc_msg.split(":")[1].split(" ")[2]
except Exception as exc:
print("Exception raised to irc message parsing:")
print(exc)
except Exception:
LOGGER.exception("Exception raised to irc message parsing")
target = None
return " ".join(irc_msg.split()), actor, target
except Exception as exc:
print("Exception raised to irc message parsing:")
print(exc)
except Exception:
LOGGER.exception("Exception raised to irc message parsing")
return None, None, None
@ -106,7 +109,7 @@ def parse_redmine_datetime(redmine_date: str) -> datetime:
class Bot:
def __init__(self, server, bot_nick):
def __init__(self, server: str, bot_nick: str):
self.bot_nick = bot_nick
self.hello_regex = re.compile(generate_command_regex(HELLO_LIST), re.I)
self.help_regex = re.compile(generate_command_regex(HELP_LIST), re.I)
@ -114,25 +117,28 @@ class Bot:
self.projects = []
self.irc_socket = None
def send(self, msg: str):
def send_raw(self, msg: str) -> None:
self.irc_socket.send(msg.encode("utf-8", "ignore"))
def connect(self):
def send_privmsg(self, channel: str, message: str) -> None:
self.send_raw(f"PRIVMSG {channel} :{message}\n")
def connect(self) -> None:
self.irc_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.irc_socket.connect((self.server, 6667))
self.send(f"USER {self.bot_nick} {self.bot_nick} {self.bot_nick} :Robot Agir April"
".\n") # bot authentication
self.send(f"NICK {self.bot_nick}\n") # Assign the nick to the bot.
self.send_raw(f"USER {self.bot_nick} {self.bot_nick} {self.bot_nick} :Robot Agir April"
f".\n") # bot authentication
self.send_raw(f"NICK {self.bot_nick}\n") # Assign the nick to the bot.
if not PASSWORD.strip() and REGISTERED is True:
self.send("PRIVMSG NickServ :IDENTIFY {self.botnick} {password}")
self.send_privmsg("NickServ", f"IDENTIFY {self.bot_nick} {PASSWORD}")
def add_project(self, project):
self.send("JOIN {} \n".format(project.channel)) # Joins channel
def add_project(self, project: str) -> None:
self.send_raw("JOIN {} \n".format(project.channel)) # Joins channel
self.projects.append(project)
def get_project(self, name):
def get_project(self, name: str) -> str:
for project in self.projects:
if name[0] != '#' and project.name == name:
return project
@ -141,7 +147,7 @@ class Bot:
return None
# Main loop
def loop(self):
def loop_forever(self) -> None:
last_read = datetime.utcnow()
while True: # Loop forever
@ -159,32 +165,32 @@ class Bot:
raise Exception('timeout: nothing to read on socket since 10 minutes')
# Responds to server Pings.
def pong(self, irc_msg):
def pong(self, irc_msg: str) -> None:
response = f"PONG :{irc_msg.split('PING :')[1]}\n"
self.send(response)
self.send_raw(response)
self._check_global_updates()
def _check_global_updates(self):
print('Checking projects updates....')
def _check_global_updates(self) -> None:
LOGGER.info("Checking all projects updates...")
for project in self.projects:
for message in project.redmine():
self.send(f"PRIVMSG {project.channel} :{message}\n")
for message in project.generate_messages():
self.send_privmsg(project.channel, message)
def _check_project_updates(self, channel_name: str):
print('Checking projects updates....')
def _check_project_updates(self, channel_name: str) -> None:
LOGGER.info("Checking project of channel %s updates...", channel_name)
project = self.get_project(channel_name)
if not project:
print(f'Cannot find project for channel {channel_name}')
LOGGER.error("Cannot find project for channel %s", channel_name)
return
for message in project.redmine():
self.send(f"PRIVMSG {project.channel} :{message}\n")
self.send_privmsg(project.channel, message)
# Parses messages and responds to them appropriately.
def message_response(self, irc_msg, actor, channel):
def message_response(self, irc_msg: str, actor: str, channel: str) -> None:
# If someone talks to (or refers to) the bot.
if (':!' in irc_msg or self.bot_nick.lower() in irc_msg.lower()) and \
actor != self.bot_nick and \
@ -194,41 +200,46 @@ class Bot:
if self.help_regex.search(irc_msg):
self.bot_help(channel)
if 'refresh' in irc_msg.lower():
self.send(f"PRIVMSG {channel} :Raffraîchissement en cours\n")
self.send_privmsg(channel, "Raffraîchissement en cours")
self._check_project_updates(channel)
self.send(f"PRIVMSG {channel} :Fait !\n")
self.send_privmsg(channel, "Fait !")
# If the server pings us then we've got to respond!
if irc_msg.find("PING :") != -1:
self.pong(irc_msg)
# Responds to a user that inputs "Hello Mybot".
def bot_hello(self, channel, greeting):
self.send(f"PRIVMSG {channel} :{greeting}\n")
def bot_hello(self, channel: str, greeting: str) -> None:
self.send_privmsg(channel, greeting)
# Explains what the bot is when queried.
def bot_help(self, channel):
self.send(f"PRIVMSG {channel} :Bonjour, je suis un bot qui reconnaît les options !help, !refresh et !bonjour\n")
project_name = self.get_project(channel).name
self.send(f"PRIVMSG {channel} :Périodiquement, je vais afficher les actualités de "
f"http://agir.april.org/projects/{project_name}.\n")
def bot_help(self, channel: str) -> None:
self.send_privmsg(channel, "Bonjour, je suis un bot qui reconnaît les options !help, !refresh et !bonjour")
project = self.get_project(channel)
if not project:
LOGGER.warning("Project of channel %s not found", channel)
return
self.send_privmsg(channel, f"Périodiquement, je vais afficher les actualités "
f"de http://agir.april.org/projects/{project.name}.")
# Reads the messages from the server and adds them to the Queue and prints
# them to the console. This function will be run in a thread, see below.
def msg_handler(self): # pragma: no cover (this excludes this function from testing)
def msg_handler(self) -> str: # pragma: no cover (this excludes this function from testing)
new_msg = self.irc_socket.recv(2048) # receive data from the server
if not new_msg:
print("Empty recv. It seems Ive lost my mind. I stop to be reborn.")
LOGGER.error("Empty recv. It seems Ive lost my mind. I stop to be reborn.")
sys.exit(7)
else:
new_msg = new_msg.decode("utf-8").strip('\n\r') # removing any unnecessary linebreaks
if new_msg != '' and new_msg.find("PING :") == -1:
print(datetime.now().isoformat() + " " + new_msg)
LOGGER.debug(new_msg)
return new_msg
def main():
print(datetime.now().isoformat() + " redmine bot starting…")
def main() -> None:
LOGGER.info("redmine bot starting…")
redmine_bot = Bot(DEFAULT_SERVER, DEFAULT_NICKNAME)
redmine_bot.connect()
if PROJECT_ID_1 and PROJECT_CHANNEL_1:
@ -237,8 +248,8 @@ def main():
redmine_bot.add_project(Project(PROJECT_ID_2, PROJECT_CHANNEL_2))
if PROJECT_ID_3 and PROJECT_CHANNEL_3:
redmine_bot.add_project(Project(PROJECT_ID_3, PROJECT_CHANNEL_3))
return redmine_bot.loop()
redmine_bot.loop_forever()
if __name__ == "__main__":
sys.exit(main())
main()