Source

IRC Bot Commander / irc-cmdr.py

#!/usr/bin/env python
################################################################################
## NAME: irc-cmdr.py 
## DATE: $Date: 2011-03-07 17:59:12 -0800 (Mon, 07 Mar 2011) $
## REVISION: $Rev: 3 $
## AUTHOR: strangl3r
## WEBSITE: http://strangl3r.com
## EMAIL: strangl3r@hushmail.com
## LICENSE: BSD http://www.opensource.org/licenses/bsd-license.php
################################################################################
from socket import gethostname;
from optparse import OptionParser
import socket
import sys
import string
import os
import time
import fileinput
import commands
import datetime
import ssl

## Start defaults
hostname = gethostname()
default_nick = "irc_cmdr_"+hostname
default_server = "eu.undernet.org"
default_port = 6667
default_chan = "#testing"
default_realname = "irc_cmdr"
default_owner = "strangl3r"
default_buffer_size = 4096
## End defaults

code_version = "1.02.1.7"
d = datetime.datetime.now()
date = d.isoformat()
ver = sys.version.split(' ')[0].split(".")
major=ver[:1]
minor=ver[1:2]
version="%s.%s"%(major[0],minor[0])
if version < 2.4: 
    print "Please upgrade to python 2.4+"
    sys.exit(1)

def parse_options():
    usage = "usage: "
    parser = OptionParser(usage=usage)
    parser.add_option("-n", "--nick", dest="nick", default=default_nick, help="nickname (default: assface)")
    parser.add_option("-s", "--server", dest="server", default=default_server, help="server (default: eu.undernet.org)")
    parser.add_option("-p", "--port", dest="port", default=default_port, help="server port (default: 6667)")
    parser.add_option("-c", "--chan", dest="chan", default=default_chan, help="channel (default: #datastrangler)")
    parser.add_option("-r", "--realname", dest="realname", default=default_realname, help="realname (default: irc-cmdr)")
    parser.add_option("-o", "--owner", dest="owner", default=default_owner, help="bot owner's nick (default: strangl3r)")
    parser.add_option("-b", "--buffer", dest="buffer_size", default=default_buffer_size, help="data buffer (default: 4096)")
    parser.add_option("--verbose", dest="verbose", action="store_true", help="verbose output (default: disabled)")
    parser.add_option("--ssl", dest="ssl_enable", action="store_true", help="enable ssl connection (default: disabled)")
    return parser.parse_args()

def header():
    print '''   _                            _    
  (_)________    ________  ____//___
 / // __/ __/___/ __/  ' |/_  // __/
/_//_/  \__/    \__/_/_/_|_,_//_/   

ver %s
http://strangl3r.com                               
strangl3r@hushmail.com'''%(code_version)

def console(str,val):
    # print status messages via the following conditions. 
    # "0" messages always go to STDOUT regardless of verbose setting
    # "1" messages are STDOUT verbose messages
    # "2" messages are non-formatted and always printed to STDOUT
    if val == 2:
        print str
    if verbose == True and val == 1:
        print "STDOUT-VERBOSE::%s"%(str)
    if val == 0:
        print "STDOUT::%s"%(str)

def parsemsg(msg): 
    complete = msg[1:].split(':',1) 
    info = complete[0].split(' ')
    sender = info[0].split('!')
    msgpart = complete[1].rstrip()
    msg = complete[1].strip(nick +",").lstrip().rstrip()
    cmdstr = msg.lstrip("`")
    console("complete: %s"%(complete),1)
    console("info: %s"%(info),1)
    console("sender: %s"%(sender),1)
    console("msgpart: %s"%(msgpart),1)
    console("msg: %s"%(msg),1)
    console("cmdstr: %s"%(cmdstr),1)
    return [sender,msg,cmdstr]

def clicmd(s): 
    # execute via the command line if we match to allowed commands (currently execs all)
    # then return output of command to owner
    # we output to a string instead of file so that we aren't writing to disk for various reasons
    global irc
    cmd=s.replace('cli ','') 
    cmd=cmd.lstrip().rstrip() 
    retcode, output = syscmd(cmd)
    if retcode != 0:
        irc.send('PRIVMSG '+chan+' :'+owner+', Command failed: code %i.\r\n'%(retcode))
        return False
    counter = 0
    total = len(output.splitlines())
    for line in output.splitlines():
        console("sending output to chan.",0)
        line = line.replace('\n','')        
        try:
            if counter < 9: #chunk the output so we aren't disconn'd w/ value: 32
                irc.send('PRIVMSG '+chan+' :'+line+'\r\n') 
                counter += 1
            else:
                irc.send('PRIVMSG '+chan+' :'+owner+', Pausing to prevent flood.\r\n')
                time.sleep(30)
                irc.send('PRIVMSG '+chan+' :'+line+'\r\n')
                counter = 0

        except socket.error, (value,message):
            console("message: %s, value: %i"%(message,value),0)
            if value == 32: #if broken pipe / flood, reconnect. not tested.
                console("Busted pipe. Waiting 5 seconds to reconnect so we're not throttled.",0)
                time.sleep(5)
                try:
                    irc = initirc()
                    irc.send('PRIVMSG '+chan+' :'+owner+', '+line+'\r\n')
                except:
                    console("failed to reconnect. confusion.",0)

        time.sleep(.5) # wait interval to prevent quick flood
    irc.send('PRIVMSG '+chan+' :'+owner+', total lines: %i\r\n'%(total))
    return 0

def syscmd(command):
    console("Running system command: %s"% (command),1)    
    start = datetime.datetime.now()
    retcode, output = commands.getstatusoutput(command)
    end = datetime.datetime.now()
    timing = end - start
    console("Total compute time: %s"%(timing),1)
    if retcode != 0:
        console("System command: %s code: %i [FAILED]"%(command,retcode),1)
        return retcode,output
    else:
        console("System command: [OK]",1)
        console("Returning: %i, %s"%(retcode, output),1)
        return retcode,output

def initirc():
    # create the IRC connection. We use a function so it can be called
    # again if we need to re-open it later due to errors or disconnects
    try:
        if options.ssl_enable == True:
            s = socket.socket ( socket.AF_INET, socket.SOCK_STREAM )
            s.connect ((server,port))
            irc = ssl.wrap_socket(s) 
        else:
            irc = socket.socket ( socket.AF_INET, socket.SOCK_STREAM )
            irc.connect ((server,port))

        console("Connection successful!",0)
        print irc.recv (buffer_size)

    except socket.error, (value,message):
        console("error message: %s, value: %i"%(message,value),0)        
        sys.exit(value)

    irc.send ('NICK %s \r\n'%(nick))
    irc.send ('USER %s %s %s :%s\r\n'%(nick, hostname, "bla", realname))
    irc.send ('JOIN %s\r\n'%(chan))
    return irc

def main():
    global server, port, nick, chan, hostname, owner, irc
    irc.send("PRIVMSG %s :%s, operational status: [READY]\r\n"%(chan,owner))
    while True:
        data = irc.recv(buffer_size) 
        if data.find('PRIVMSG %s :%s,'%(chan,nick))!=-1:
            #we received data for our nick so we parse and operate on it
            d = parsemsg(data) 
            sender = d[0]
            msg = d[1]
            cmdstr = d[2]
            if sender[0] == owner:
                for a in msg[0]:
                    if a[0] == "`":    
                        cmd = cmdstr.split(" ") 
                        #console("rec-cmd-list: %s"%(cmd))

                        if cmd[0]=='test': 
                            irc.send("PRIVMSG %s :%s, test string: [OK]\r\n"%(chan,owner))
                    
                        if cmd[0]=='help':
                            irc.send("PRIVMSG %s :available commands:\n<test> simple response test \n<oper [chan]> move bot to specified channel \n<oper [nick]> change bot's nick \n<oper [owner]> change bot's owner \n<cli [commands]> tell bot to execute [commands] on its host \n<cli help> list bot's available cli commands.\r\n"%(chan))

                        if cmd[0]=='oper': 
                            #non-cli operations for bot: reconfiguration, node management, etc
                            console('PRIVMSG %s :%s, oper: %s\r\n'%(chan,owner,cmd),1)
                            irc.send('PRIVMSG %s :%s, oper: %s\r\n'%(chan,owner,cmd))
                            if cmd[1] == "chan": #join new channel
                                try:
                                    chan = cmd[2] ##@@ this is only changing the posting channel. if we want
                                    # this to work we need to tear down and join the new channel instead of
                                    # just changing our global variable for writing PRIVMSG
                                except:
                                    irc.send('PRIVMSG %s :%s, I need a dest chan.\r\n'%(chan,owner))

                            if cmd[1] == "nick": #change nick
                                try:
                                    irc.send ('/NICK %s\r\n'%(cmd[2]))
                                except:
                                    irc.send('PRIVMSG %s :%s, I need a dest nick.\r\n'%(chan,owner))
                                
                            if cmd[1] == "owner": #change owner
                                try:
                                    owner = cmd[2]
                                except:
                                    irc.send('PRIVMSG %s :%s, I need a dest owner.\r\n'%(chan,owner))

                        if cmd[0]=='cli': 
                            #send commands to cli processor 
                            clicmd(cmdstr)

        #do some ping pong keepalive
        if data.find ( 'PING' ) != -1:
            irc.send ( 'PONG ' + data.split() [ 1 ] + '\r\n' )

        #quit on request by oper
        if data.find ( '!%s quit'%(nick) ) != -1: 
            irc.send ( 'PRIVMSG %s :quitting\r\n'%(chan) )
            irc.send ( 'QUIT\r\n' )

        #if kicked, rejoin after 60 seconds
        if data.find ( 'KICK' ) != -1: 
            time.sleep(60)
            irc.send ( 'JOIN %s\r\n'%(chan) )

        #respond to test string
        if data.find ( "hello %s"%(nick) ) != -1 or data.find ( "%s, hello"%(nick) ) != -1:
            irc.send ( 'PRIVMSG %s :waiting for something interesting...\r\n'%(chan) )

        console(data,1)

if __name__ == "__main__":
    header()
    (options, args) = parse_options()
    server = options.server
    port = int(options.port)
    nick = options.nick
    chan = options.chan
    realname = options.realname
    owner = options.owner
    buffer_size = options.buffer_size
    ssl_enable = False
    try:
        verbose = options.verbose
    except:
        verbose = False
        
    if ssl_enable == False:
        ssl_r = "False"
    if ssl_enable == True:
        ssl_r = "True"

    console("----------------------",2)
    console("hostname: "+hostname,2)
    console("server: "+server,2)
    console("port: "+str(port),2)
    console("ssl: "+ssl_r,2)
    console("nick: "+nick,2)
    console("chan: "+chan,2)
    console("owner: "+owner,2)
    console("buffer: "+str(buffer_size)+" bytes",2)
    console("----------------------",2)
    
    irc = initirc()
    try:
        main()
    except KeyboardInterrupt:
        console("Keyboard interrupt",0)
        console("Shutting down at user request.",0)
        sys.exit(0)
    except:
        sys.exit(1)
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.