Files
fulpstation/bot/minibot.py
oranges e0c07371e1 Minibot improvements in logging, signals and connection (#22700)
Minibot now logs messages recieved to a file instead of to the terminal,
this may help prevent some crashes caused by outputting to the windows
terminal

Minibot now listens for sigkill/sigterm and attempts to gracefully shut
down instead of just looping forever, this is entirely for ease of
debugging in a terminal

Minibot does not attempt to connect forever and will give up after 4
tries with an error
2017-02-13 11:15:23 +01:00

165 lines
5.1 KiB
Python
Executable File

#!/usr/bin/env python3
# This bot was made by tkdrg.
# Ask #coderbus@irc.rizon.net if this breaks.
# See LICENSE-bot_folder.txt for the license of the files in this folder.
from config import *
import collections
import time
import pickle
import socket
import sys
import threading
import logging
import logging.handlers as handlers
import signal
global irc
# Set to false when we've been killed
running = True
# times we've attempted to connect to server
con_attempts = 0
## Set up a logger object
logger = logging.getLogger('minibot')
logger.setLevel(logging.DEBUG)
# create a file handler (rolls over midnight, keeps 7 days of log
handler = handlers.TimedRotatingFileHandler('minibot.log', when='midnight', backupCount=7)
# most verbose
handler.setLevel(logging.DEBUG)
#only send errors/notifications to the terminal
iohandler = logging.StreamHandler()
iohandler.setLevel(logging.INFO)
# create a logging format
#time - name - level - message (string)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s', datefmt='%Y-%m-%d %H:%M')
handler.setFormatter(formatter)
iohandler.setFormatter(formatter)
#finally attach them to the logger object
logger.addHandler(handler)
logger.addHandler(iohandler)
def setup_irc_socket():
global irc, running, con_attempts, logger
s = socket.socket()
s.settimeout(240)
#why not reuse running here? because we want to break this loop if someone sigkills us
connected = False
while running and con_attempts < 3 and not connected:
try:
s.connect((server, port))
except socket.error:
logger.exception("Unable to connect to server {0}:{1}, attempting to reconnect in 20 seconds, Attempt number:{2}".format(server, port, con_attempts))
con_attempts += 1
time.sleep(20)
continue
logger.info("Connection established to server {0}:{1}.".format(server, port))
connected = True
if connected:
s.send(bytes("NICK {0}\r\n".format(nick), "UTF-8"))
s.send(bytes("USER {0} {1} {2} :{3}\r\n".format(ident, server, name, realname), "UTF-8"))
else:
logger.error("Unable to connect, shutting down")
running = False
return s
def setup_nudge_socket():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(("", 45678)) # localhost:45678
s.listen(5)
logger.info("Nudge socket up and listening")
return s
def nudge_handler():
global irc, running, con_attempts, logger
nudge = setup_nudge_socket()
message_queue = collections.deque()
while running:
if len(message_queue):
message = message_queue.popleft()
else:
try:
s, ip = nudge.accept()
except:
logger.exception("Nudge socket lost, attempting to reopen.")
nudge = setup_nudge_socket()
continue
rawdata = s.recv(1024)
s.close()
data = pickle.loads(rawdata)
logger.debug(data)
if data["ip"][0] == "#":
message = "{0} :AUTOMATIC ANNOUNCEMENT : {1}\r\n".format(data["ip"], str(" ".join(data["data"])))
else:
message = "{0} :AUTOMATIC ANNOUNCEMENT : {1} | {2}\r\n".format(defaultchannel, data["ip"], str(" ".join(data["data"])))
try:
irc.send(bytes("PRIVMSG {0}".format(message), "UTF-8"))
except:
logger.exception("Nudge received without IRC socket, appending to queue.")
logger.debug("Message: {0}".format(message))
message_queue.append(message)
def irc_handler():
global irc, running, con_attempts, logger
while running:
try:
buf = irc.recv(1024).decode("UTF-8").split("\n")
for i in buf:
logger.debug(i)
if i[0:4] == "PING":
irc.send(bytes("PONG {0}\r\n".format(i[5:]), "UTF-8"))
else:
l = i.split(" ")
if len(l) < 2:
continue
elif l[1] == "001":
logger.info("connected and registered, identifing and joining channels")
irc.send(bytes("PRIVMSG NickServ :IDENTIFY {0}\r\n".format(password), "UTF-8"))
time.sleep(1)
for channel in channels:
irc.send(bytes("JOIN {0}\r\n".format(channel), "UTF-8"))
elif l[1] == "477":
logger.error("Error: Nickname was not registered when joining {0}. Reauthing and retrying...".format(l[3]))
irc.send(bytes("PRIVMSG NickServ :IDENTIFY {0}\r\n".format(password), "UTF-8"))
time.sleep(5)
irc.send(bytes("JOIN {0}\r\n".format(l[3]), "UTF-8"))
elif l[1] == "433":
logger.error("Error: Nickname already in use. Attempting to use alt nickname if available, sleeping 60s otherwise...")
if(altnick):
irc.send(bytes("NICK {0}\r\n".format(altnick), "UTF-8"))
else:
time.sleep(60)
irc = setup_irc_socket()
except InterruptedError as e:
logger.exception("Interrupted, probably killed.")
continue
except:
logger.exception("Lost connection to IRC server.")
irc = setup_irc_socket()
def signal_handler(signum, frame):
global irc, running, con_attempts, logger
logger.info("Recieved term kill, closing")
running = False
if __name__ == "__main__":
#listen to signals (quit on ctrl c or kill from OS)
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
signal.signal(signal.SIGQUIT, signal_handler)
irc = setup_irc_socket()
t = threading.Thread(target=nudge_handler)
t.daemon = True
t.start()
irc_handler()