Python Forum
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Basic IRC bot with socket
#1
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()
Output:
<metulburr> : <metulburr> help <metulburr> :help <mybot> you called me? <metulburr> :test <metulburr> :yo <mybot> Yo-Yo
And of course once you do this you can build on this to do whatever. Such as...
https://github.com/metulburr?page=1&q=bo...C%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':
            pass
rejoin 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.text
keep 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))
Recommended Tutorials:
Reply
#2
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?
Reply
#3
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.
Recommended Tutorials:
Reply


Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020