IRC on a Higher Level Concluded

If you’ve been following this series of articles closely, then you already have the skills necessary to develop applications that communicate through IRC. The Python-IRCLib library is easy to learn and use, so putting it to work should not be a difficult task at all. However, the library contains a few more gems that are worth taking a look at, and these gems can make your development process go even more smoothly.

SimpleIRCClient

In the first article of this series, I explained how to catch and respond to events in an application. However, you probably noticed that the code behind this was a bit messy and long. First, we have to define a handler function that responds to the triggered event. Then, we need to wire our handler function to our IRC object, using the add_global_handler method, which accepts a string corresponding to the event we want to work with and the name of the handler function. The code doesn’t look pretty at all, but, fortunately, the library contains a shortcut to this all: the SimpleIRCClient class.

The SimpleIRCClient class can be subclassed to create a functional IRC client. It eliminates many of the steps normally required. All you have to do is create an instance of the SimpleIRCClient and call the connect method to get started. From there, you can access the methods of ServerConnection. After that, all that’s left is calling the start method to accept incoming data. Let’s create a simple client that joins a given channel and sits there:

import irclib

# Connection information
network = ‘irc.freenode.net’
port = 6667
channel = ‘#irclib’
nick = ‘PyTest’
name = ‘Python Test’

# Subclass SimpleIRCClient
class ClientClass ( irclib.SimpleIRCClient ):

   pass

# Create an instance of ClientClass
client = ClientClass()

# Connect
client.connect ( network, port, nick, ircname = name )

# Join the specified channel
client.connection.join ( channel )

# Start
client.start()

As you can see, this object-oriented approach to creating a client is very simple to employ. In case you don’t immediately see the advantage of using the SimpleIRCClient class, however, let’s move on to the next topic now that we’ve established a connection. Events are, as I’ve stated several times in the previous articles, crucial to most applications of purpose. The SimpleIRCClient really shines when it comes to events, allowing you to create methods of your subclass that handle events. These methods come in the form of on_event, and they are called automatically when the appropriate event is triggered. For example, here is an application that logs messages in the file “channelLog.txt”:

import irclib

# Connection information
network = ‘irc.freenode.net’
port = 6667
channel = ‘#irclib’
nick = ‘PyTest’
name = ‘Python Test’

# Open a file to store the logs in
# We will append data to this file ( “a” )
logFile = open ( ‘channelLog.txt’, ‘a’ )

# Subclass SimpleIRCClient
class ClientClass ( irclib.SimpleIRCClient ):

   # Handle private messages
   def on_pubmsg ( self, connection, event ):

      # Write to the log
      source = event.source().split ( ‘!’ ) [ 0 ]
      text = event.arguments() [ 0 ]
      logFile.write ( source + ‘-> ‘ + text + ‘n’ )

# Create our client and connect
client = ClientClass()
client.connect ( network, port, nick, ircname = name )
client.connection.join ( channel )

# Start the client
client.start()

As you can see from the above example, the handler method is structured the exact same way as a normal handler, except that it’s a method and its name corresponds to the event we want to handle. We omit the add_global_handler method, compacting our code.

Any event will work with the SimpleIRCClient class. You are certainly not limited to the common ones. Here’s an application that gathers information on clients joining the application’s channel through the use of the client-to-client protocol:

import irclib
import time

# Connection variables
network = ‘irc.freenode.net’
port = 6667
channel = ‘#irclib’
nick = ‘PyTest’
name = ‘Python Test’

# Our client class
class QueryClient ( irclib.SimpleIRCClient ):

   # Query users who join the channel
   def on_join ( self, connection, event ):

      source = event.source().split ( ‘!’ ) [ 0 ]
      connection.ctcp ( ‘TIME’, source )
      connection.ctcp ( ‘VERSION’, source )
      connection.ctcp ( ‘USERINFO’, source )
      connection.ctcp ( ‘PING’, source, str ( time.time() ) )
     

   # Handle CTCP replies
   def on_ctcpreply ( self, connection, event ):

      source = event.source().split ( ‘!’ ) [ 0 ]
      command = event.arguments() [ 0 ].upper()

      # Display the user’s local time
      if command == ‘TIME':
         print source + ‘-> Local time: ‘ + event.arguments()
[ 1 ]

      # Display the user’s verison
      elif command == ‘VERSION':
         print source + ‘-> Verison: ‘ + event.arguments() [ 1 ]

      # Display the user’s information
      elif command == ‘USERINFO':
         print source + ‘-> Info: ‘ + event.arguments() [ 1 ]

      # Display the user’s ping
      elif command == ‘PING':
         print source + ‘-> Ping: ‘ + str ( time.time() – float ( event.arguments() [ 1 ] ) ) + ‘ seconds’

# Create the client and connect
client = QueryClient()
client.connect ( network, port, nick, ircname = name )
client.connection.join ( channel )

# Loop
client.start()

Even in a more complex application, the code is neat and organized.

{mospagebreak title=SingleServerIRCBot}

At the very beginning of this short series, I mentioned a utility that allowed the easy creation of bot applications. This utility comes in the form of a class: SingleServerIRCBot, contained in the module ircbot. You can create bots that do any number of things, from just keeping statistics on the users of the channel to accepting commands from privileged users and performing tasks based on those commands.

Let’s start by building a simple bot that records how many lines a user sends to the bot’s channel. The user statistics will be stored in a dictionary, with the key being the user’s name and the value being the number of lines the user has sent to the channel. Users will be able to send a command to the bot asking it how many lines he or she has sent to the channel. The bot will retrieve the proper information and send the user it through a private message:

import ircbot

# Connection information
network = ‘irc.freenode.net’
port = 6667
channel = ‘#irclib’
nick = ‘PyTest’
name = ‘Python Test’

# Create a dictionary to store statistics in
statistics = {}

# Create our bot class
class StatBot ( ircbot.SingleServerIRCBot ):

   # Join the channel when welcomed
   def on_welcome ( self, connection, event ):

      connection.join ( channel )

   # React to channel messages
   def on_pubmsg ( self, connection, event ):

      source = event.source().split ( ‘!’ ) [ 0 ]

      # Check to see if the user has queried us
      if event.arguments() [ 0 ].upper() == ‘!STATBOT':

         # Check to see if the user is in the dictionary
         if statistics.has_key ( source ):

            # Message the user his or her statistics
            connection.privmsg ( source, ‘You have sent ‘ + str
( statistics [ source ] ) + ‘ lines.’ )

         # Message the user saying that we have no record
         else:
            connection.privmsg ( source, ‘I have no record of
you.’ )

      # A regular message has been sent to us
      else:

         # Add the user to the dictionary is necessary
         if not statistics.has_key ( source ):
            statistics [ source ] = 0

         # Add a line
         statistics [ source ] = statistics [ source ] + 1

# Create the bot
bot = StatBot ( [( network, port )], nick, name )
bot.start()

Let’s dissect our bot and examine it. We start by importing the ircbot module and defining the connection information. There are no suprises there, though you should note what module we are importing now. We then create a dictionary to store user statistics. As I stated earlier, the key will be the user’s name, and the value will be the number of lines the user has sent to the channel the bot is in.

Next, we subclass SingleServerIRCBot to form the StatBot class. In the StatBot class, we define two methods that correspond with event names. The first method is on_welcome. When the network sends our bot a welcome message, we will join the specified channel. The second method is on_pubmsg. When this method is called, we check to see whether the public message is equal to “!StatBot” (ignoring case). If it is, we return the number of lines recorded from the source, assuming that the source has a record. If the bot has no record for the source, the bot sends a message saying so. If the public message does not equal “!StatBot”, then we modify the source’s record, adding the source to the dictionary if he or she is not already in it.

Finally, at the bottom of the program, we create an instance of the StatBot class, passing it three arguments. The first is a list containing a single server tuple, the second is the nickname and the third is the real name. We then start the bot up.

As far as handling events go, you have undoubtedly noticed that the SingleServerIRCBot class handles events similar to how the SimpleIRCClient class handles them. Methods are created that correspond with events, and those methods are automatically called when the event is triggered.

If our bot gets disconnected from the server, it will rejoin the network automatically after a sixty-second timeout. We can modify this timeout if we wish. All we have to do is pass an additional argument when creating an instance of our class. For example, this is what we would do if we wanted to change the timeout to two minutes:


bot = StatBot ( [( network, port )], nick, name, reconnection_interval = 120 )

 

This feature is useful because we won’t always be watching our bot’s activities. This is also why we join the channel in the on_welcome method. In case our bot does get disconnected, we want it to join the channel again.

However, if we want our bot to disconnect and stay disconnected, we simply call the die method:


self.die()

 

We can pass an optional message to the die method as well:


self.die ( ‘Terminated.’ )

 

{mospagebreak title=Channel Information}

The SingleServerIRCBot class contains some tools that allow us to gather information about a channel because it maintains a list of channels it is currently in. The utilities are provided by the Channel class, and we can access them in our bot.

Let’s create an example that uses a few of these tools. The bot we construct will join a channel and send us some basic information about the channel when we request it by messaging “!ChanInfo”:

import ircbot
import time

# Connection information
network = ‘irc.freenode.net’
port = 6667
channel = ‘#irclib’
nick = ‘PyTest’
name = ‘Python Test’

# Create our bot class
class ExampleBot ( ircbot.SingleServerIRCBot ):

   # When welcomed, join the channel and perform the actions mentioned
   def on_welcome ( self, connection, event ):

      connection.join ( channel )

   def on_pubmsg ( self, connection, event ):

      if event.arguments() [ 0 ].upper() == ‘!CHANINFO':

         source = event.source().split ( ‘!’ ) [ 0 ]
         channelObject = self.channels [ channel ]

         # Message some statistics
         connection.privmsg ( source, ‘Moderated: ‘ + str ( channelObject.is_moderated() ) )
         connection.privmsg ( source, ‘Number of users: ‘ + str ( len ( channelObject.users() ) ) )
         connection.privmsg ( source, ‘Channel operators: ‘ + str ( len (channelObject.opers() ) ) )

# Create our bot
bot = ExampleBot ( [( network, port )], nick, name )
bot.start()

Most of the above is already familiar to you, so I’ll only explain the new content. In the on_pubmsg method, we define channelObject, which is the Channel object that matches channel. From there, we access three methods. The is_moderated method checks to see whether the channel is moderated. The users method returns a list of users in the channel. The opers method returns a list of operators.

Of course, there are a number of other Channel methods that can gather information for our bots. Let’s take a look at those methods. Notice how we can get a list of all users and a list of operators in a channel. We can also get a list of voiced users:


voiced = self.channels [ channel ].users()

We can also get information on specific users. To start with, we can check whether a channel has a certain user. A boolean value is returned:


hasUser = self.channels [ channel ].has_user ( ‘User_Name’ )

If we need to know whether a user has channel operator permissions, we can use the is_oper method to get a boolean value:


isOper = self.channels [ channel ].is_oper ( ‘User_Name’ )

We can check whether users are voiced or not, too:


isVoiced = self.channels [ channel ].is_voiced ( ‘User_Name’ )

It may be useful to your application if it knows the channel’s modes. We can test for certain modes with the Channel object. For example, let’s say we wanted to see if the channel was moderated:


isModerated = self.channels [ channel ].has_mode (‘m’ )

Of course, you saw the is_moderated method being used before. It is an alternative to the method used above, and the Channel object contains a few more of these. For example, let’s say we wanted to check to see if the channel is invite-only:


isInvite = self.channels [ channel ].is_invite_only()

We can also check to see if the channel is secret:


isSecret = self.channels [ channel ].is_secret()

Some channels put a limit on the number of users that can be in a channel. We can check to see whether a channel has such a limit:


isLimited = self.channels [ channel ].has_limit()

 

Many channels do not allow external messages. The has_allow_external_messages can be used to check this:


acceptsExternal = self.channels [ channel ].has_allow_external_messages()

Some channels have a password, or key, protecting access to the channel. We can check this:


hasKey = self.channels [ channel ].has_key()

Many channels choose to lock their topics. The has_topic_lock can tell us if a channel has such a lock in place:


hasLock = self.channels [ channel ].has_topic_lock()

{mospagebreak title=Something Practical}

Let’s take a look at something a bit more practical now: a full-fledged IRC bot capable of accepting commands from users. If that doesn’t sound interesting, then let’s take things one step futher: a bot that responds to users based on external scripts that can be loaded and reloaded on-the-fly.

Our bot will scan a directory, “commands”, for Python files. It will load these files as modules and store them in a dictionary. If a user prefixes the bot’s name with a dollar symbol ( for example, “$PyBot” ), then the bot will examine the incoming message for a second argument. If a second argument is present, it will be compared to the dictionary of modules. If a module’s name matches the command, the index function of the module will be called, and the connection and event type will be passed. Also, if we receive a CTCP command whose name matches a special password, the bot will rescan. Let’s translate this to Python:

import glob
import ircbot
import imp

# Bot scan password
password = ‘d3vsh3d0652′

# Connection informatoin
network = ‘irc.freenode.net’
port = 6667
channel = ‘#irclib’
nick = ‘PyBot’
name = ‘Python Bot’

# We’ll store the commands here
commands = {}

# Scan the “commands” directory and load the modules
def scan():
   commands.clear()
   for moduleSource in glob.glob ( ‘commands/*.py’ ):
      name = moduleSource.replace ( ‘.py’, ” ).replace ( ‘\’,
‘/’ ).split ( ‘/’ ) [ 1 ].upper()
      handle = open ( moduleSource )
      module = imp.load_module ( ‘COMMAND’, handle, ( ‘commands/’
+ moduleSource ), ( ‘.py’, ‘r’, imp.PY_SOURCE ) )
      commands [ name ] = module

# Create our bot class
class ModularBot ( ircbot.SingleServerIRCBot ):

   # Join a channel when welcomed
   def on_welcome ( self, connection, event ):
      connection.join ( channel )

   # Listen to public messages
   # If the user says our name, prefixed with “$”, then we act
   def on_pubmsg ( self, connection, event ):
      if event.arguments() [ 0 ].split() [ 0 ].upper() == ( ‘$’ +
nick.upper() ):

          # See if the user specified a valid command
          # If so, call the module
          if len ( event.arguments() [ 0 ].split() ) == 1:
             pass
          elif commands.has_key ( event.arguments() [ 0 ].split()
[ 1 ].upper() ):
             commands [ event.arguments() [ 0 ].split()
[ 1 ].upper() ].index ( connection, event )

   # Listen to CTCP messages for the scan password
   # If we get it, rescan
   def on_ctcp ( self, connection, event ):
      if event.arguments() [ 0 ] == password.upper():
         scan()

# Scan for commands
scan()

# Create the bot and run it
bot = ModularBot ( [( network, port )], nick, name )
bot.start()

There are no suprises, the bot works exactly in the way I explained above. Now, all that’s left is giving the bot a few commands to work with. To begin, let’s start with an “about” command. To implement this, we simply create a file called “about.py” and place it in the “commands” directory, filling an index function with the appropriate information:

def index ( connection, event ):
   connection.privmsg ( event.source().split ( ‘!’ ) [ 0 ], ‘I am
an IRC bot built in Python.’ )
   connection.privmsg ( event.source().split ( ‘!’ ) [ 0 ], ‘I am
modular and can load command modules on the fly.’ )

The module will be automatically loaded when the bot is started up. Alternatively, we can have it reload the module by using this command in an IRC client:

/ctcp PyBot d3vsh3d0652

Pretty neat, huh? Of course, we can create commands that accept additional arguments as well. Consider a command that accepts a number and then attempts to square it:

def index ( connection, event ):

   # Do we have an extra argument?
   # If not, complain
   if len ( event.arguments() [ 0 ].split() ) == 2:
      connection.privmsg ( event.source().split ( ‘!’ ) [ 0 ],
‘Please include a number.’ )

   # Try to square the number and return it to the user
   else:
      try:
         number = float ( event.arguments() [ 0 ].split() [ 2 ] )
         number = number ** 2
         connection.privmsg ( event.source().split ( ‘!’ ) [ 0 ],
str ( number ) )
      except:
         pass

Conclusion

While working directly with the IRC protocol can get the job done, development can be messy and slow. The Python-IRCLib library exists between your applications and the IRC protocol, allowing you to take an object-oriented approach to the IRC protocol. It ensures that the result is quickly put together, compact and clean.

The library allows you to work with the protocol on several levels. In this article, we explored the top-most level of interaction: objects that act as clients, automating much of the dirty or otherwise repetitive work. You now have the skill necessary to develop a wide variety of IRC applications fit for just about any task you can think of –- whether it is recording statistics about users or accepting commands and interacting with users.

We also built a modular IRC bot. Feel free to use it and modify it as you see fit. If you wish to share any modifications or modules to the bot –- or information about any IRC project –- then please e-mail me because I would love to hear about it. One simple modification to the bot might be a help command, which lists the supported commands or calls a certain method of the modules.

There is one thing you should remember though: bots are nice, but the niceness can quickly wear off. Never build an overly-annoying application, and never seek to use a bot for malicious purposes.

[gp-comments width="770" linklove="off" ]

antalya escort bayan antalya escort bayan Antalya escort diyarbakir escort