Basic IRC bot with socket - Printable Version +- Python Forum (https://python-forum.io) +-- Forum: General (https://python-forum.io/forum-1.html) +--- Forum: Tutorials (https://python-forum.io/forum-4.html) +---- Forum: Networking Tutorials (https://python-forum.io/forum-36.html) +---- Thread: Basic IRC bot with socket (/thread-436.html) |
Basic IRC bot with socket - metulburr - Oct-11-2016 This is the basic layout of an IRC bot with the socket module. The docstrings specify each method. The end result is a bot that connects to the channel and is able to respond to basic commands import socket class IRCBot: def __init__(self, **kwargs): self.settings = { 'host':"irc.freenode.net", 'port':6667, 'channel':"#robgraves", 'contact': ":", 'nick':"mybot", 'ident':'mybot', 'realname':'mybot' } self.add_kwargs(kwargs) self.sock = self.irc_conn() self.main_loop() def add_kwargs(self, kwargs): ''' add keyword args as class attributes. This allows you to change the settings based on dict arg, and not have to hard code it in. The settings keys become this class' attributes. And the value becomes the value for those attributes. AKA self.nick = "mybot" etc. IRCbot(**{nick:"mybot2"}) IRCbot(**{nick:"mybot3"}) ''' for kwarg in kwargs: if kwarg in self.settings: self.settings[kwarg] = kwargs[kwarg] else: raise AttributeError("{} has no keyword: {}".format(self.__class__.__name__, kwarg)) self.__dict__.update(self.settings) def irc_conn(self): ''' connect to server/port channel, send nick/user ''' sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) print('connecting to "{0}/{1}"'.format(self.host, self.port)) sock.connect((self.host, self.port)) print('sending NICK "{}"'.format(self.nick)) sock.send("NICK {0}\r\n".format(self.nick).encode()) sock.send("USER {0} {0} bla :{0}\r\n".format( self.ident,self.host, self.realname).encode()) print('joining {}'.format(self.channel)) sock.send(str.encode('JOIN '+self.channel+'\n')) return sock def main_loop(self): ''' The main loop to keep the program running and waiting for commands ''' while True: self.parse_data() self.ping_pong() self.check_command() def get_user(self, stringer): '''get username from data string''' start = stringer.find('~') end = stringer.find('@') user = stringer[start +1:end] return user def parse_data(self): ''' get server data and parse it based on each message/command in irc ''' data=self.sock.recv(1042) #recieve server messages data = data.decode('utf-8') #data decoded self.data = data.strip('\n\r') #data stripped try: self.operation = data.split()[1] #get operation ie. JOIN/QUIT/PART/etc. textlist = data.split()[3:] text = ' '.join(textlist) self.text = text[1:] #content of each message self.addrname = self.get_user(data) #get address name self.username = data[:data.find('!')][1:] #get username self.cmd = self.text.split()[0][1:] except IndexError: #startup data has different layout than normal pass def ping_pong(self): ''' The server pings and anything that does not pong back gets kicked ''' try: if self.data[:4] == 'PING': self.send_operation('PONG') except TypeError: #startup data pass def send_operation(self, operation=None, msg=None, username=None): ''' the specific string structure of sending an operation and private message to one user ''' if msg is None: #send ping pong operation self.sock.send('{0} {1}\r\n'.format(operation, self.channel).encode()) elif msg != None: #send private msg to one username self.sock.send('PRIVMSG {0} :{1}\r\n'.format(self.username,msg).encode()) def say(self, string): ''' send string to channel...the equivalent to print() in the IRC channel ''' self.sock.send('PRIVMSG {0} :{1}\r\n'.format(self.channel, string).encode()) def check_command(self): ''' check each and every message for a command ''' if self.text[:1] == self.contact: #respond to only contact code to not respond to all messages if self.cmd == "help": self.say("you called me?") elif self.cmd == "yo": self.say("Yo-Yo") bot = IRCBot() And of course once you do this you can build on this to do whatever. Such as...https://github.com/metulburr?page=1&q=bot&tab=repositories&utf8=%E2%9C%93&utf8=%E2%9C%93&q=IRC Below are some modifications examples you can add to do specific things. This was wrote a long time ago...so i dont remember to a T everything...but some magic numbers usually chop the string up to get the wanted part. You dont have to do it the same of course. One thing to note that is not in this previous code is the fact that the IRC will either kick you for flooding a channel or flush your message. What i mean by this is your bot may get kicked by sending a string that is of 1000 characters or so. This is not normal...but could happen depending on what the bot needs to output. To avoid this you can just split your message up into segments. For example.... if len(str(string)) > 500: #protect from kicked for flooding s1 = sep_space(string[:500]) self.sock.send('PRIVMSG {0} :{1}\r\n'.format(self.channel, s1).encode()) s2 = sep_space(string[len(s1):1000]) self.sock.send('PRIVMSG {0} :{1}\r\n'.format(self.channel, s2).encode()) else: self.sock.send('PRIVMSG {0} :{1}\r\n'.format(self.channel, string).encode())Testing for people join or leaving a channel: def upon_leave(self): '''when someone leaves the channel''' if self.operation == 'QUIT' or self.operation == 'PART': pass def upon_join(self): '''when someone joins the channel''' if self.operation == 'JOIN': passrejoin a channel when an op likes to have fun and kick your bot. Unless they banned your bot. def rejoin(self): '''rejoin when kicked''' if self.operation == 'KICK': if (self.text.split()[-1][1:]) == self.nick: self.sock.send(str.encode('JOIN '+self.channel+'\n')) self.insult() else: self.sock.send(str.encode('JOIN '+self.channel+'\n'))And be we warned of using eval(). For example if you eval a message, a message could contain a line of python code instead...executing it. And who knows what that line of code is. :naughty: keep a log of what people say... if self.operation == 'PRIVMSG' or self.operation == 'ACTION': if self.text[0] == '\x01': action = self.text[1:-1].split()[1:] action = ' '.join(action) self.last_said[self.username] = '*{} {}'.format(self.username, action) else: self.last_said[self.username] = self.textkeep track of when people were last on... def seen(self, name=None): '''display last seen person's time and statement''' try: a = self.last_seen[name] b = datetime.datetime.now().replace(microsecond=0) diff = str(b - a) diff = diff.split(':') diff = '{} hr {} min {} sec'.format(diff[0], diff[1], diff[2]) said = ''.join(name + '\'s last statement: ' + self.last_said[name]) self.say('{} was last seen {} ago: {}'.format( name, diff, said )) except KeyError: self.say('{} has had no activity since I have been on'.format(name)) RE: Basic IRC bot with socket - micseydel - Oct-12-2016 Is there an easy way to test/play with something like this locally without having to connect to a channel where you might bother someone? RE: Basic IRC bot with socket - metulburr - Oct-12-2016 you can join any channel you want. If it doesnt exist and no one is in a channel name...you would be the first person there (founder)...and then of course the bot when it joins. /join #blabbadabbadingdong#robgraves channel is where i test all my bots...its my brothers channel. Most people there are logged in from their server and wont even notice activity. But usually they will join in testing the bot too if they are active. Which is useful for multiplayer game bots. EDIT: I think there are actual channels set aside too for bot testing with tons of people in them. |