#Todo:
#
#
#4. Control panel for bot.
#
# send(messagetype, target, subtarget, message)
import socket
import sys
from time import sleep
import random
chambered_round = 0
#Maintains connection to server
#Allows messages to be sent by other methods/classes
class irc_connection:
def __init__(self):
try:
#Get the target server(s) and nickname(s) from the config file ircbot.cfg
configfile = open("fullmetalmadda.cfg", "r")
self.servers = configfile.readline().partition("=")[2].split(";")
configfile.close()
self.connection = socket.socket ( socket.AF_INET, socket.SOCK_STREAM )
except:
#Config file could not be opened, bail out
print("Could not read config file fullmetalmadda.cfg. Time to die.")
sys.exit(1)
if(len(self.servers) == 0):
#No servers? What do you want the poor bot to do?
print("No servers found on line 1 of fullmetalmadda.cfg.")
print("First line of file should read:")
print("servers=irc.something.net:6667;irc.somethingelse.net:6667;irc.andsoon.etc:6667")
sys.exit(2)
for server in self.servers:
try:
#Are we connected?
if(self.connection.getpeername()):
#Yes, we don't need to keep trying
break
except:
#No, we should try to connect to this server
#try:
server = server.rstrip("\n").split(":")
self.connection.connect( (server[0], int(server[1])) )
#except:
#We can't connect to this server. Move on.
#pass
def readbuffer(self):
data = self.connection.recv(2048)
return str(data, "ascii").split("\n")
def sendraw(self,message):
#Send a raw message- must be sent as bytes
try:
self.connection.send(message)
except:
#Probably not encoded correctly, attempt a fix
self.connection.send(bytes(message, "ascii"))
def sendaction(self, target, message):
self.connection.send(bytes("PRIVMSG " + target + " :\x01ACTION " + message + "\x01\r\n", "ascii"))
def send(self,messagetype, target, subtarget, message):
#Send a message of a specific type
if(messagetype == "PONG"):
self.connection.send(bytes(messagetype + " " + message + "\r\n", "ascii"))
elif(messagetype == "JOIN"):
self.connection.send(bytes(messagetype + " :" + target + "\r\n", "ascii"))
elif(messagetype == "KICK"):
self.connection.send(bytes(messagetype + " " + target + " " + subtarget + " :" + message + " \r\n", "ascii"))
elif(messagetype == "PART"):
self.connection.send(bytes(messagetype + " " + target + " :" + message + "\r\n", "ascii"))
elif(messagetype == "QUIT"):
self.connection.send(bytes(messagetype + " :" + message + "\r\n", "ascii"))
elif(messagetype == "PRIVMSG" or messagetype == "NOTICE"):
self.connection.send(bytes(messagetype + " " + target + " :" + message + "\r\n", "ascii"))
elif(messagetype == "INVITE"):
#Peculiar format to this one- INVITE user :#channel
self.connection.send(bytes(messagetype + " " + subtarget + " :" + target + "\r\n", "ascii"))
elif(messagetype == "NICK"):
self.connection.send(bytes(messagetype + " " + target + "\r\n", "ascii"))
elif(messagetype == "USER"):
self.connection.send(bytes(messagetype + " " + target + " " + subtarget + " * :" + message + "\r\n", "ascii"))
elif(messagetype == "MODE"):
self.connection.send(bytes(messagetype + " " + target + " " + message + " " + subtarget + "\r\n", "ascii"))
else:
#Probably a typo. For the moment we'll just silently ignore it
pass
#Class for processing messages from IRC
class irc_message:
#Method to initialise message processing class
def __init__(self, data, conn, mynick):
self.data = data.rstrip("\r").rstrip("\n")
self.nickname = ""
self.fullname = ""
self.hostaddress = ""
self.messagetype = ""
self.message = ""
self.target = ""
self.channel = ""
self.irc_connection = conn
self.my_nickname = mynick
procdata = data.partition(" ")
#Get the details of the message sender
userdetails = procdata[0].partition("!")
if(userdetails[1] == ""):
#Server, use placeholders for nickname and fullname
self.nickname = "#:server:#"
self.fullname = self.nickname
self.hostaddress = userdetails[2].rstrip()
else:
#User, get full details
self.nickname = userdetails[0].lstrip(":").rstrip()
self.userdetails = userdetails[2].partition("@")
self.fullname = userdetails[0].rstrip()
self.hostaddress = userdetails[2].rstrip()
#Get the message type- PING is a special case
if(procdata[0].rstrip() == "PING"):
self.messagetype = "PING"
else:
procdata = procdata[2].partition(" ")
self.messagetype = procdata[0].rstrip()
#Process the message based on its type
if(self.messagetype == "PING"):
#Server checking client is still responsive. Automatically respond
self.message = procdata[2].rstrip()
self.target = "#:self:#"
self.channel = self.target
self.irc_connection.send("PONG", "", "", self.message)
elif(self.messagetype == "433"):
#Server response indicating nickname already in use.
self.message = procdata[2].rstrip()
self.target = "#:self:#"
self.channel = self.target
elif(self.messagetype == "JOIN"):
#Someone joined a channel
self.message = ""
self.target = procdata[2].lstrip(":").rstrip()
self.channel = self.target
elif(self.messagetype == "KICK"):
#Someone got kicked
procdata = procdata[2].partition(" ")
self.message = procdata[2].partition(":")[2].rstrip()
self.target = procdata[2].partition(":")[0].rstrip()
self.channel = procdata[0].rstrip()
elif(self.messagetype == "QUIT" or self.messagetype == "PART"):
#Someone left a channel or the network
procdata = procdata[2].partition(" ")
self.message = procdata[2].lstrip(":").rstrip()
self.target = procdata[0].rstrip()
self.channel = self.target
elif(self.messagetype == "PRIVMSG" or self.messagetype == "NOTICE"):
#Someone is communicating with the client or a channel
procdata = procdata[2].partition(" ")
self.message = procdata[2].lstrip(":").rstrip()
self.target = procdata[0].rstrip()
if(self.target.find("#") > -1 or self.target.find("&") > -1):
#It's public communication on a channel
self.channel = self.target
else:
#It's private communication
self.channel = "#:PRIVATE:#"
elif(self.messagetype == "INVITE"):
#Someone has sent an invite to somewhere
procdata = procdata[2].partition(" ")
self.message = ""
self.target = procdata[0].rstrip()
self.channel = procdata[2].lstrip(":").rstrip()
else:
#This message type is unknown
self.message = procdata[2].rstrip()
self.target = "#:UNKNOWN:#"
self.channel = self.target
def reply(self,response):
#Define how to respond to each type of action
if(self.messagetype == "NOTICE"):
#These should not be subject to an auto-reply, so we will not define a reply method for them
#If they are to be replied to, it must be done explicitly as a new message
pass
elif(self.messagetype == "KICK"):
if(self.target == self.my_nickname):
#Someone just kicked the client. A reply should rejoin then send a message to the channel
self.irc_connection.send("JOIN", self.channel, "", "")
self.irc_connection.send("PRIVMSG", self.channel, "", response)
else:
#Someone else just got kicked from a channel. Our default response will be a message to the channel
self.irc_connection.send("PRIVMSG", self.channel, "", response)
elif(self.messagetype == "PRIVMSG"):
#Someone is talking to us or to a channel we're in. Respond to them/the channel
if(self.channel == "#:PRIVATE:#"):
self.irc_connection.send("PRIVMSG", self.nickname, "", response)
else:
self.irc_connection.send("PRIVMSG", self.target, "", response)
elif(self.messagetype == "JOIN"):
#We saw someone join a channel, respond to the channel they're on
self.irc_connection.send("PRIVMSG", self.nickname, "", response)
elif(self.messagetype == "PART" or self.messagetype == "QUIT"):
#We saw someone leave a channel we can see, or the network itself. Respond to the channel they were in.
self.irc_connection.send("PRIVMSG", self.channel, "", response)
elif(self.messagetype == "INVITE"):
#We saw an invite to a channel
#Just in case some quirk of our status or the server allows us to see other invites,
#we check to make sure the invite is to us
if(self.target == self.my_nickname):
#Default response is to join te channel then send a message
self.irc_connection.send("JOIN", self.channel, "", "")
self.irc_connection.send("PRIVMSG", self.channel, "", response)
if __name__ == "__main__":
messagequeue = []
irc = irc_connection()
user_defined = False
nickname_set = False
channels_joined = False
nickname_index = 0
mynick = ""
#Get nickname(s) to use and channel(s) to join
configfile = open("fullmetalmadda.cfg", "r")
#First line is servers, ignore it
configfile.readline()
nicknames = configfile.readline().partition("=")[2].split(";")
channels = configfile.readline().partition("=")[2].split(";")
configfile.close()
if(len(nicknames) == 0):
#No nickname? Sadly we can't be the nameless one.
print("No nicknames found on line 2 of fullmetalmadda.cfg.")
print("Second line of file should read:")
print("nicknames=FullMetalMadda;AnotherNick;Yetanothernick;etcetcnick")
sys.exit(3)
if(len(channels) == 0):
#No channels defined. Non-critical, but print warning
print("No channels found on line 3 of fullmetalmadda.cfg.")
print("Third line of file should read:")
print("channels=#channel1;#channel2")
print("No channels will be joined as none have been specified.")
channels_joined = True
while True:
if(len(messagequeue) == 0):
data = irc.readbuffer()
for msg in data:
msg.rstrip("\r")
if(msg != ""):
messagequeue.append(msg)
else:
message = irc_message(messagequeue.pop(), irc, mynick)
print("NICK:" + message.nickname + " TYPE:" + message.messagetype + " TARGET:" + message.target + " CHANNEL:" + message.channel + " MESSAGE:" + message.message + ":")
print(message.data)
if(message.messagetype == "433"):
#Nickname already in use, use next one
nickname_index+=1
nickname_set = False
if(nickname_index > len(nicknames)):
#All nickname options in use, bomb out
print("All nicknames specified in fullmetalmadda.cfg are in use on the network.")
print("Exiting.")
sys.exit(4)
#Check logoon process has been completed
if(not nickname_set):
irc.send("NICK", nicknames[nickname_index], "", "")
nickname_set = True
mynick = nicknames[nickname_index]
print("Nick:" + mynick)
elif(not user_defined):
irc.send("USER", nicknames[nickname_index], "8", "Python IRC bot")
user_defined = True
elif(not channels_joined):
for channel in channels:
irc.send("JOIN", channel, "", "")
channels_joined = True
else:
if(message.messagetype == "PRIVMSG" and message.message == "!roll"):
message.reply(str(random.randint(1,6)))
if(message.messagetype == "KICK"):
message.reply("test")
if(message.messagetype == "PRIVMSG" and message.message == "kick"):
irc.send("KICK",message.channel,message.nickname,"Fine, knock yourself out.")
#Auto Ops
allowedopslist = ['Oot', 'syn', 'ryll', 'rince']
if(message.messagetype == "JOIN"and message.nickname in allowedops):
irc.send("MODE", message.channel, "+o")
#Comical command responses
if(message.messagetype == "PRIVMSG" and message.message == "!boobs"):
message.reply("boing")
if(message.messagetype == "PRIVMSG" and message.message == "!rickroll"):
rickroll=['Never gonna give you up','Never gonna let you down']
message.reply(random.sample(rickroll,1))
if(message.messagetype == "PRIVMSG" and message.message == "!donk"):
donk = ['Put A Donk On It!', 'Electro', 'Bassline', 'Thats sick That Is']
message.reply(random.sample(donk,1))
#Conditional trigger
if(message.messagetype == "PRIVMSG" and message.message == "Hehe"):
chambered_round = 1
if(chambered_round == 1 and message.nickname == "Skei"):
chambered_round = 0
irc.send("KICK", message.channel, message.nickname, "What does this button do?")