irc_bot
Module for the IRC bot.
Connecting, sending and receiving messages and doing custom actions.
Keeping a log and reading incoming material.
1#! /usr/bin/env python3 2# -*- coding: utf-8 -*- 3 4""" 5Module for the IRC bot. 6 7Connecting, sending and receiving messages and doing custom actions. 8 9Keeping a log and reading incoming material. 10""" 11import logging 12import os 13import shutil 14import socket 15 16import chardet 17 18from bot import Bot 19 20LOG = logging.getLogger("bot") 21 22class IrcBot(Bot): 23 """Bot implementing the IRC protocol""" 24 def __init__(self): 25 super().__init__() 26 self.CONFIG = { 27 "server": None, 28 "port": 6667, 29 "channel": None, 30 "nick": "marvin", 31 "realname": "Marvin The All Mighty dbwebb-bot", 32 "ident": None, 33 "dirIncoming": "incoming", 34 "dirDone": "done", 35 "lastfm": None, 36 } 37 38 # Socket for IRC server 39 self.SOCKET = None 40 41 def connectToServer(self): 42 """Connect to the IRC Server""" 43 44 # Create the socket & Connect to the server 45 server = self.CONFIG["server"] 46 port = self.CONFIG["port"] 47 48 if server and port: 49 self.SOCKET = socket.socket() 50 LOG.info("Connecting: %s:%d", server, port) 51 self.SOCKET.connect((server, port)) 52 else: 53 LOG.error("Failed to connect, missing server or port in configuration.") 54 return 55 56 # Send the nick to server 57 nick = self.CONFIG["nick"] 58 if nick: 59 msg = f'NICK {nick}\r\n' 60 self.sendMsg(msg) 61 else: 62 LOG.info("Ignore sending nick, missing nick in configuration.") 63 64 # Present yourself 65 realname = self.CONFIG["realname"] 66 self.sendMsg(f'USER {nick} 0 * :{realname}\r\n') 67 68 # This is my nick, i promise! 69 ident = self.CONFIG["ident"] 70 if ident: 71 self.sendMsg(f'PRIVMSG nick IDENTIFY {ident}\r\n') 72 else: 73 LOG.info("Ignore identifying with password, ident is not set.") 74 75 # Join a channel 76 channel = self.CONFIG["channel"] 77 if channel: 78 self.sendMsg(f'JOIN {channel}\r\n') 79 else: 80 LOG.info("Ignore joining channel, missing channel name in configuration.") 81 82 def sendPrivMsg(self, message, channel): 83 """Send and log a PRIV message""" 84 if channel == self.CONFIG["channel"]: 85 self.MSG_LOG.debug("%s <%s> %s", channel, self.CONFIG["nick"], message) 86 87 msg = f"PRIVMSG {channel} :{message}\r\n" 88 self.sendMsg(msg) 89 90 def sendMsg(self, msg): 91 """Send and occasionally print the message sent""" 92 LOG.debug("SEND: %s", msg.rstrip("\r\n")) 93 self.SOCKET.send(msg.encode()) 94 95 def decode_irc(self, raw, preferred_encs=None): 96 """ 97 Do character detection. 98 You can send preferred encodings as a list through preferred_encs. 99 http://stackoverflow.com/questions/938870/python-irc-bot-and-encoding-issue 100 """ 101 if preferred_encs is None: 102 preferred_encs = ["UTF-8", "CP1252", "ISO-8859-1"] 103 104 changed = False 105 enc = None 106 res = None 107 for enc in preferred_encs: 108 try: 109 res = raw.decode(enc) 110 changed = True 111 break 112 except Exception: 113 pass 114 115 if not changed: 116 try: 117 enc = chardet.detect(raw)['encoding'] 118 res = raw.decode(enc) 119 except Exception: 120 res = raw.decode(enc, 'ignore') 121 122 return res 123 124 def receive(self): 125 """Read incoming message and guess encoding""" 126 lines = None 127 try: 128 buf = self.SOCKET.recv(2048) 129 lines = self.decode_irc(buf) 130 lines = lines.split("\n") 131 buf = lines.pop() 132 except Exception as err: 133 LOG.error("Error reading incoming message %s", err) 134 135 return lines 136 137 def readincoming(self): 138 """ 139 Read all files in the directory incoming, send them as a message if 140 they exists and then move the file to directory done. 141 """ 142 if not os.path.isdir(self.CONFIG["dirIncoming"]): 143 return 144 145 listing = os.listdir(self.CONFIG["dirIncoming"]) 146 147 for infile in listing: 148 filename = os.path.join(self.CONFIG["dirIncoming"], infile) 149 150 with open(filename, "r", encoding="UTF-8") as f: 151 for msg in f: 152 self.sendPrivMsg(msg, self.CONFIG["channel"]) 153 154 try: 155 shutil.move(filename, self.CONFIG["dirDone"]) 156 except Exception: 157 LOG.warning("Failed to move %s to %s. Deleting.", filename, self.CONFIG["dirDone"]) 158 os.remove(filename) 159 160 def mainLoop(self): 161 """For ever, listen and answer to incoming chats""" 162 while 1: 163 # Check in any in the incoming directory 164 self.readincoming() 165 166 for line in self.receive(): 167 LOG.debug(line) 168 words = line.strip().split() 169 170 if not words: 171 continue 172 173 self.checkIrcActions(words) 174 self.checkMarvinActions(words) 175 176 def begin(self): 177 """Start the bot""" 178 self.connectToServer() 179 self.mainLoop() 180 181 def checkIrcActions(self, words): 182 """ 183 Check if Marvin should take action on any messages defined in the 184 IRC protocol. 185 """ 186 if words[0] == "PING": 187 self.sendMsg(f"PONG {words[1]}\r\n") 188 189 if words[1] == 'INVITE': 190 self.sendMsg(f'JOIN {words[3]}\r\n') 191 192 def checkMarvinActions(self, words): 193 """Check if Marvin should perform any actions""" 194 if words[1] == 'PRIVMSG' and words[2] == self.CONFIG["channel"]: 195 self.MSG_LOG.debug("%s <%s> %s", 196 words[2], 197 words[0].split(":")[1].split("!")[0], 198 " ".join(words[3:])) 199 200 if words[1] == 'PRIVMSG': 201 raw = ' '.join(words[3:]) 202 row = self.tokenize(raw) 203 204 if self.CONFIG["nick"] in row: 205 for action in self.ACTIONS: 206 msg = action(row) 207 if msg: 208 self.sendPrivMsg(msg, words[2]) 209 break 210 else: 211 for action in self.GENERAL_ACTIONS: 212 msg = action(row) 213 if msg: 214 self.sendPrivMsg(msg, words[2]) 215 break
23class IrcBot(Bot): 24 """Bot implementing the IRC protocol""" 25 def __init__(self): 26 super().__init__() 27 self.CONFIG = { 28 "server": None, 29 "port": 6667, 30 "channel": None, 31 "nick": "marvin", 32 "realname": "Marvin The All Mighty dbwebb-bot", 33 "ident": None, 34 "dirIncoming": "incoming", 35 "dirDone": "done", 36 "lastfm": None, 37 } 38 39 # Socket for IRC server 40 self.SOCKET = None 41 42 def connectToServer(self): 43 """Connect to the IRC Server""" 44 45 # Create the socket & Connect to the server 46 server = self.CONFIG["server"] 47 port = self.CONFIG["port"] 48 49 if server and port: 50 self.SOCKET = socket.socket() 51 LOG.info("Connecting: %s:%d", server, port) 52 self.SOCKET.connect((server, port)) 53 else: 54 LOG.error("Failed to connect, missing server or port in configuration.") 55 return 56 57 # Send the nick to server 58 nick = self.CONFIG["nick"] 59 if nick: 60 msg = f'NICK {nick}\r\n' 61 self.sendMsg(msg) 62 else: 63 LOG.info("Ignore sending nick, missing nick in configuration.") 64 65 # Present yourself 66 realname = self.CONFIG["realname"] 67 self.sendMsg(f'USER {nick} 0 * :{realname}\r\n') 68 69 # This is my nick, i promise! 70 ident = self.CONFIG["ident"] 71 if ident: 72 self.sendMsg(f'PRIVMSG nick IDENTIFY {ident}\r\n') 73 else: 74 LOG.info("Ignore identifying with password, ident is not set.") 75 76 # Join a channel 77 channel = self.CONFIG["channel"] 78 if channel: 79 self.sendMsg(f'JOIN {channel}\r\n') 80 else: 81 LOG.info("Ignore joining channel, missing channel name in configuration.") 82 83 def sendPrivMsg(self, message, channel): 84 """Send and log a PRIV message""" 85 if channel == self.CONFIG["channel"]: 86 self.MSG_LOG.debug("%s <%s> %s", channel, self.CONFIG["nick"], message) 87 88 msg = f"PRIVMSG {channel} :{message}\r\n" 89 self.sendMsg(msg) 90 91 def sendMsg(self, msg): 92 """Send and occasionally print the message sent""" 93 LOG.debug("SEND: %s", msg.rstrip("\r\n")) 94 self.SOCKET.send(msg.encode()) 95 96 def decode_irc(self, raw, preferred_encs=None): 97 """ 98 Do character detection. 99 You can send preferred encodings as a list through preferred_encs. 100 http://stackoverflow.com/questions/938870/python-irc-bot-and-encoding-issue 101 """ 102 if preferred_encs is None: 103 preferred_encs = ["UTF-8", "CP1252", "ISO-8859-1"] 104 105 changed = False 106 enc = None 107 res = None 108 for enc in preferred_encs: 109 try: 110 res = raw.decode(enc) 111 changed = True 112 break 113 except Exception: 114 pass 115 116 if not changed: 117 try: 118 enc = chardet.detect(raw)['encoding'] 119 res = raw.decode(enc) 120 except Exception: 121 res = raw.decode(enc, 'ignore') 122 123 return res 124 125 def receive(self): 126 """Read incoming message and guess encoding""" 127 lines = None 128 try: 129 buf = self.SOCKET.recv(2048) 130 lines = self.decode_irc(buf) 131 lines = lines.split("\n") 132 buf = lines.pop() 133 except Exception as err: 134 LOG.error("Error reading incoming message %s", err) 135 136 return lines 137 138 def readincoming(self): 139 """ 140 Read all files in the directory incoming, send them as a message if 141 they exists and then move the file to directory done. 142 """ 143 if not os.path.isdir(self.CONFIG["dirIncoming"]): 144 return 145 146 listing = os.listdir(self.CONFIG["dirIncoming"]) 147 148 for infile in listing: 149 filename = os.path.join(self.CONFIG["dirIncoming"], infile) 150 151 with open(filename, "r", encoding="UTF-8") as f: 152 for msg in f: 153 self.sendPrivMsg(msg, self.CONFIG["channel"]) 154 155 try: 156 shutil.move(filename, self.CONFIG["dirDone"]) 157 except Exception: 158 LOG.warning("Failed to move %s to %s. Deleting.", filename, self.CONFIG["dirDone"]) 159 os.remove(filename) 160 161 def mainLoop(self): 162 """For ever, listen and answer to incoming chats""" 163 while 1: 164 # Check in any in the incoming directory 165 self.readincoming() 166 167 for line in self.receive(): 168 LOG.debug(line) 169 words = line.strip().split() 170 171 if not words: 172 continue 173 174 self.checkIrcActions(words) 175 self.checkMarvinActions(words) 176 177 def begin(self): 178 """Start the bot""" 179 self.connectToServer() 180 self.mainLoop() 181 182 def checkIrcActions(self, words): 183 """ 184 Check if Marvin should take action on any messages defined in the 185 IRC protocol. 186 """ 187 if words[0] == "PING": 188 self.sendMsg(f"PONG {words[1]}\r\n") 189 190 if words[1] == 'INVITE': 191 self.sendMsg(f'JOIN {words[3]}\r\n') 192 193 def checkMarvinActions(self, words): 194 """Check if Marvin should perform any actions""" 195 if words[1] == 'PRIVMSG' and words[2] == self.CONFIG["channel"]: 196 self.MSG_LOG.debug("%s <%s> %s", 197 words[2], 198 words[0].split(":")[1].split("!")[0], 199 " ".join(words[3:])) 200 201 if words[1] == 'PRIVMSG': 202 raw = ' '.join(words[3:]) 203 row = self.tokenize(raw) 204 205 if self.CONFIG["nick"] in row: 206 for action in self.ACTIONS: 207 msg = action(row) 208 if msg: 209 self.sendPrivMsg(msg, words[2]) 210 break 211 else: 212 for action in self.GENERAL_ACTIONS: 213 msg = action(row) 214 if msg: 215 self.sendPrivMsg(msg, words[2]) 216 break
Bot implementing the IRC protocol
42 def connectToServer(self): 43 """Connect to the IRC Server""" 44 45 # Create the socket & Connect to the server 46 server = self.CONFIG["server"] 47 port = self.CONFIG["port"] 48 49 if server and port: 50 self.SOCKET = socket.socket() 51 LOG.info("Connecting: %s:%d", server, port) 52 self.SOCKET.connect((server, port)) 53 else: 54 LOG.error("Failed to connect, missing server or port in configuration.") 55 return 56 57 # Send the nick to server 58 nick = self.CONFIG["nick"] 59 if nick: 60 msg = f'NICK {nick}\r\n' 61 self.sendMsg(msg) 62 else: 63 LOG.info("Ignore sending nick, missing nick in configuration.") 64 65 # Present yourself 66 realname = self.CONFIG["realname"] 67 self.sendMsg(f'USER {nick} 0 * :{realname}\r\n') 68 69 # This is my nick, i promise! 70 ident = self.CONFIG["ident"] 71 if ident: 72 self.sendMsg(f'PRIVMSG nick IDENTIFY {ident}\r\n') 73 else: 74 LOG.info("Ignore identifying with password, ident is not set.") 75 76 # Join a channel 77 channel = self.CONFIG["channel"] 78 if channel: 79 self.sendMsg(f'JOIN {channel}\r\n') 80 else: 81 LOG.info("Ignore joining channel, missing channel name in configuration.")
Connect to the IRC Server
83 def sendPrivMsg(self, message, channel): 84 """Send and log a PRIV message""" 85 if channel == self.CONFIG["channel"]: 86 self.MSG_LOG.debug("%s <%s> %s", channel, self.CONFIG["nick"], message) 87 88 msg = f"PRIVMSG {channel} :{message}\r\n" 89 self.sendMsg(msg)
Send and log a PRIV message
91 def sendMsg(self, msg): 92 """Send and occasionally print the message sent""" 93 LOG.debug("SEND: %s", msg.rstrip("\r\n")) 94 self.SOCKET.send(msg.encode())
Send and occasionally print the message sent
96 def decode_irc(self, raw, preferred_encs=None): 97 """ 98 Do character detection. 99 You can send preferred encodings as a list through preferred_encs. 100 http://stackoverflow.com/questions/938870/python-irc-bot-and-encoding-issue 101 """ 102 if preferred_encs is None: 103 preferred_encs = ["UTF-8", "CP1252", "ISO-8859-1"] 104 105 changed = False 106 enc = None 107 res = None 108 for enc in preferred_encs: 109 try: 110 res = raw.decode(enc) 111 changed = True 112 break 113 except Exception: 114 pass 115 116 if not changed: 117 try: 118 enc = chardet.detect(raw)['encoding'] 119 res = raw.decode(enc) 120 except Exception: 121 res = raw.decode(enc, 'ignore') 122 123 return res
Do character detection. You can send preferred encodings as a list through preferred_encs. http://stackoverflow.com/questions/938870/python-irc-bot-and-encoding-issue
125 def receive(self): 126 """Read incoming message and guess encoding""" 127 lines = None 128 try: 129 buf = self.SOCKET.recv(2048) 130 lines = self.decode_irc(buf) 131 lines = lines.split("\n") 132 buf = lines.pop() 133 except Exception as err: 134 LOG.error("Error reading incoming message %s", err) 135 136 return lines
Read incoming message and guess encoding
138 def readincoming(self): 139 """ 140 Read all files in the directory incoming, send them as a message if 141 they exists and then move the file to directory done. 142 """ 143 if not os.path.isdir(self.CONFIG["dirIncoming"]): 144 return 145 146 listing = os.listdir(self.CONFIG["dirIncoming"]) 147 148 for infile in listing: 149 filename = os.path.join(self.CONFIG["dirIncoming"], infile) 150 151 with open(filename, "r", encoding="UTF-8") as f: 152 for msg in f: 153 self.sendPrivMsg(msg, self.CONFIG["channel"]) 154 155 try: 156 shutil.move(filename, self.CONFIG["dirDone"]) 157 except Exception: 158 LOG.warning("Failed to move %s to %s. Deleting.", filename, self.CONFIG["dirDone"]) 159 os.remove(filename)
Read all files in the directory incoming, send them as a message if they exists and then move the file to directory done.
161 def mainLoop(self): 162 """For ever, listen and answer to incoming chats""" 163 while 1: 164 # Check in any in the incoming directory 165 self.readincoming() 166 167 for line in self.receive(): 168 LOG.debug(line) 169 words = line.strip().split() 170 171 if not words: 172 continue 173 174 self.checkIrcActions(words) 175 self.checkMarvinActions(words)
For ever, listen and answer to incoming chats
182 def checkIrcActions(self, words): 183 """ 184 Check if Marvin should take action on any messages defined in the 185 IRC protocol. 186 """ 187 if words[0] == "PING": 188 self.sendMsg(f"PONG {words[1]}\r\n") 189 190 if words[1] == 'INVITE': 191 self.sendMsg(f'JOIN {words[3]}\r\n')
Check if Marvin should take action on any messages defined in the IRC protocol.
193 def checkMarvinActions(self, words): 194 """Check if Marvin should perform any actions""" 195 if words[1] == 'PRIVMSG' and words[2] == self.CONFIG["channel"]: 196 self.MSG_LOG.debug("%s <%s> %s", 197 words[2], 198 words[0].split(":")[1].split("!")[0], 199 " ".join(words[3:])) 200 201 if words[1] == 'PRIVMSG': 202 raw = ' '.join(words[3:]) 203 row = self.tokenize(raw) 204 205 if self.CONFIG["nick"] in row: 206 for action in self.ACTIONS: 207 msg = action(row) 208 if msg: 209 self.sendPrivMsg(msg, words[2]) 210 break 211 else: 212 for action in self.GENERAL_ACTIONS: 213 msg = action(row) 214 if msg: 215 self.sendPrivMsg(msg, words[2]) 216 break
Check if Marvin should perform any actions