Skip to content
This repository

How To Create a Phenny Module

This file was created by Michael Yanovich from The Ohio State University Open Source Club.

Originally created 2010/02/24

Table of Contents

Creating a phenny module is simple and fun! This document is laid out in the following format,

  1. How To Get Started
  2. Create A Module
  3. Functions in a Module
  4. Commands, Rules, Events, and Priorities (Oh my!)
  5. Documenting Your Module
  6. Built-In Functions, Constants, and Variables
  7. Add Global Variables
  8. Further Reading
  9. Author's Comments

1. How to Get Started

Before you can get started you need to have a fork of phenny on your computer. You can get phenny here. You can also obtain my fork (if you wish), jenni.

Regardless of which fork you have you can create modules for either or. I won't go through the instructions of installing phenny since those are already included in both phenny packages.

Also I'm assuming you know the complete basics about Phenny, (ie: what language she is written in; how to set her up; how to config her, etc.)

2. Create A Module

The very first step in creating a module is to open up your text editor of choice and save the file in your phenny/modules folder. (Assuming you haven't changed the default location of where modules will be placed for phenny.) As far as I am aware there really isn't a convention already in place for how to name your files. The way I recommend doing it is to summarize what the goal of the whole file will accomplish or does. Later one you will see that if you have a collection of functions you might want to name the file for what the collection collectively does or accomplishes.

Once you have saved your file you now have created a file that phenny can "reload" in a chat room with the admin commands. So for example lets say you have created a file hello_world.py without restarting phenny you can simply go into a chat room she is sitting in and run the command "phenny: reload hello_world" but change "phenny" to whatever her nick is on the server you are running her on.

NOTICE: When you reload a module in the chat room or via private message you do NOT specify the extension.

FOLLOW ALONG: Now go create a "hello_world.py" file in your modules folder and then reload it in your chat room. If it were successful you will see a message along these lines:

nick: module 'hello_world.py' from '/home/nick/phenny/modules/hello_world.py' (version: 2010-02-18 02:00:00)

Now what this all means is that it has successfully loaded the module from your path where phenny is located at. The version is the time and date that the file was last modify. The 'nick' mentioned above is your irc nick that you are using to administer phenny.

NOTE: You can use anything that is included in your current library of Python that is locally installed on the computer in which you are running phenny. Which means you can use modules, such as MATH, PICKLE, SQLITE3, etc.. if they are normally available locally in your Python PATH directories.

3. Functions In A Module

Now if you have ever done any programming before, especially in python then you'll probably know what a function is, if not I would recommend you pick up a book about programming (or try several great e-books) to learn about the wonderful world of programming.

Now the way functions work in Python is like this:

def function_name(myvar1, myvar2):

"def" is how you declare that this is a function and obviously "function_name" is the name of the function. Please try to keep your function names descriptive and short so if you share your code it will be easy to understand by fellow programmers.

Also "myvar1" and "myvar2" are the incoming variables into this function. However, for most of the functions you'll be creating with phenny it will look like this:

def helloworld(phenny, input):

Here you need to always (unless you know what you are doing) include "phenny" and "input." "phenny" brings into scope the variables and accessor you'll need in order for phenny to do what you want her to do. The "input" variable is what listens and brings into scope certain variables that are usually used to trigger the function. You'll see in the next section on how to 'trigger' a function. I'll also show an example of a working module you can use to test.

4. Commands, Rules, and Priorities (Oh My!)

Ok this is where, in my opinion, the most fun starts. Using commands you can get phenny to actually compute a given function and "say" things in the irc chat room. I'm going to display an example of a fully working module and I'll break down the pieces of what each thing does so you'll better understand what exactly is going on.

def helloworld(phenny, input):
        phenny.say("Hello world!")
helloworld.commands = ['hello']
helloworld.priority = 'medium'

Figure 5-1

If you saved the above code into a hello_world.py file in your modules directory of phenny and then loaded as I mentioned above then why type .hello in the chat room she is currently sitting in your phenny bot should she should say "Hello world!" in the chat room.

The first important thing (and new thing) is the phenny.say command. 'phenny.say' will basically take any string or python variable that can be printed natively to stdout into the chat room. You can even have her say variables that of type string, int, etc.. (eg: phenny.say(mystr) )

The key thing about this example is helloworld.commands = ['hello'] This basically tells phenny to keep an eye out for anything that begins with a ".hello" regardless of what is following it. You could try typing

.hello 125

Figure 5-2

and you'll still get your nice message of "Hello world!" I'll show later where you can actually pass those extra stuff into your functions and actually use them to manipulate data.

Another key thing to note about figure 5-1 is the last line. This isn't required for every function but it helps phenny keep things in order when a bunch of things are going on. This is really handy when she is being used by several different people at the same time. Basically inside phenny's guts she runs all commands in the following order based on priority. High, Medium, then low. All high commands will execute before all medium commands, and all medium commands will execute before all low commands. But this order is only done when there are commands being requested to be executed. Unless you build a function that is of utmost importance to the operation of phenny, you'll most likely want to give the function a medium priority. If you are doing something really obscure then you can get away with a low priority.

RULES I've saved the best for last!

Rules can be tricky, because they aren't as easy as "commands." Because they differ in the way they offer more flexibility and you can monitor more messages for lines in a chatroom than with commands. Let's say you built a function that logs the current channel you are in. But you don't know how to have phenny compute your function for every line in the room you can do this using rules.

A key concept to remember is that you never want to have a function have both a "command" and a "rule" defined because there could be an example where a certain phrase someone enters could trigger the function twice so your output or even data you may be manipulating may be done more times than you want it to be done.

The most powerful way to use rules are to use them in the form of regular expressions. Using regular expressions you actually have more power of what "types" of things you are looking for in each line in the room. For every message in a chat room on irc that is sent that line is checked against your rule. If your rule would apply to a line, then your function is called, but if your rule doesn't match a line in a room it silently fails. (There is no error message displayed, your function simply does not execute.)

Here is a great example of a "rule" in action.

def interjection(phenny, input):
        phenny.say(input.nick + '!')
interjection.rule = r'$nickname!'
interjection.priority = 'high'
interjection.thread = False

Figure 5-3

There a few new things in this example. First off is the use of a 'rule' to call the function. The r' means that the string of code you enter is to be interpreted as a raw string. In Python this means that the escape sequence are different. This makes it easier for you to enter regular expressions without having to double up on some of your characters. Another thing to note, the use of $nickname, is a global variable built inside phenny that refers to whatever nickname that you have set for phenny in your configuration file. Therefore the regular expression rule is keeping an eye out for anyone who types just 'phenny!' in the channel where phenny could be the nick of your clone of phenny.

Another thing to point out is the use of interjection.thread = False. This is a cool feature the creator (sbp) has added into phenny. Out of the box, phenny is multi-threaded so technically it can use two cores on a dual-core machine. But the best part is, you can specifically tell a given function not to be "multi-threaded." There are numerous occasions where this may be necessary.

The best tip I can give regarding rules is to take a look at the modules that come with phenny out of the box specifically the ping.py and if you are using the phenny_osu fork the resp.py module.

EVENTS Events are basically 'triggers' that will execute your function when the specified event that you listed occurs. A perfect example is to have a function execute every time someone joins a channel.

def message_join(phenny, input):
        ...
message_join.event = 'JOIN'
message_join.rule = r'.*'

Figure 5-4

You can use either JOIN, PART, NICK, QUIT, INVITE and PRIVMSG. JOIN watches for join commands from the IRC server. PART looks for people leaving a channel that phenny is currently in. NICK looks for people in the room that have changed their nicks, QUIT looks for anyone who has quit the network, INVITE looks for invite requests to a channel and PRIVMSG is the default and it looks for any message sent to the channel or to phenny.

NOTE: One thing to note here, you need to have a rule that matches anything, which is basically '.*' or the event will not match anything.

5. Documenting Your Module

This is probably the most important part of creating a module. This allows you to make your module easy to understand to other people and it also allows you to create examples for other people to see how to use your modules in the chat room!

First off the first 7 lines of your document should be used to create a description based off of how the author started it. Here is an example of how to start off your brand new module!

"""
quote.py - Phenny Quote Module
Copyright 2010, Michael Yanovich
Licensed under the Eiffel Forum License 2.

http://inamidst.com/phenny/
"""

Figure 6-1

Now you don't necessarily have to do everything exactly the same as I and sbp have done but it is highly recommend, especially if you would like to create your very own fork of the phenny project.

The second line right after the three double quotes, represents the name of the file and a brief title of the file. Then the next line identifies the copyright, if you choose to have one, then your name and possibly a url to your site. The fourth line represents the type of license you would like to license your module as. Phenny, as a whole, is licensed under the Eiffel Forum License 2. After reading up on it I prefer to license my own modules under this. You make choose any license you would like for the modules that you create.

Then the final line before the last three double quotes, is a URL to the original creator's site. I would highly recommend keeping this there, because the author (sbp) deserves as much credit as he does for creating phenny completely from scratch. Plus it is good to give credit in the open source community!

DOCUMENTING YOUR FUNCTIONS

This part is in my opinion very important, especially if you have several functions in your module file. You should add a doctype to your main function. To do this you simply go to the line right below the declaration of your function and using three double quotes ( """ ) type in a brief description of what this function does.

For example:

def addpoint(phenny, input):
    """.addpoint nick - Adds 1 point to the score system
    for nick."""
    ...

Figure 6-2

A nice but not necessary thing to include in your functions that you want to present to the world is the .example property. This displays an example you set, when someone messages phenny "help command."

For example:

def join(phenny, input):
    ...
join.example = '.join #example or .join #example key'

Figure 6-3

Here in Figure 6-3, when a user messages phenny, "help join" phenny will respond saying ".join #example or .join #example key"

Next at the very end of your document you are going to want to add the following lines of code. This is a small python hack, if you will, that prevents some weird things from happening if you tried to run the module directly inside of python and not inside phenny. I won't get into the specifics as they can get really confusing if you don't completely understand Python.

if __name__ == "__main__":
    print __doc__.strip()

Figure 6-4

6. Built-In Functions, Constants, And Variables

Here is an incomplete and growing list of built-in functions that phenny has access to that you wouldn't normally find in other Python programs.

This section is expected to grow with time.

NOTE: These only work inside a function that has the two variables, 'phenny', and 'input' passed to them. For example declare a function like:

def myfunction (phenny, input):

And inside that function you'll have access to all variables listed below.

You can access variables from the config file by doing the follow: phenny.config.name_of_variable. Using this pattern if you want to access the channels you connect to upon launch you can do phenny.config.channels, and this will return a list of channels that phenny connects to upon launching.

phenny.nick
will return the string that represents the current nick of phenny running, according to your configuration file.
phenny.say(myvar)
will print the contents of the variable "myvar" to the chatroom
phenny.write(['JOIN'], channel)
this makes phenny say a JOIN command to the IRC server to the join specified channel.
input.admin
is a variable that is a boolean of whether or not the current nick is in the admin list.
input.group()
This provides the text of the entire line that was caught by either a command or rule for the function.
input.nick
contains the nick of the person that sent a message that triggered the function.
input.sender
contains a string of what channel a message was sent from this includes the # at the beginning of the channel name.
f.rule = r'(.*)'
this rule will trigger the function 'f' every time a message is sent to the channel.

7. Add Global Variables

If you've been wondering how to have a global variable so you don't have to save passwords or api keys in the modules themselves, then this section is for you. The way this is accomplish is we put these "sensitive" variables in the default.py file in the .phenny folder. But to accecss this you need to edit the 'guts' of phenny. This is NOT recommended for new users unless you really know how phenny operates or you at least understand the basic idea of classes in programming. I will not go over classes here.

The first place we'll stop (assuming you've already started on your module) is in the bot.py file. In this file scroll down a way until you see a nice list like the following:

s = unicode.__new__(cls, text)
s.sender = origin.sender
s.nick = origin.nick
s.event = event
s.bytes = bytes
s.match = match
s.group = match.group
s.groups = match.groups
s.args = args
s.admin = origin.nick in self.config.admins
s.owner = origin.nick == self.config.owner
s.logchans = self.config.logged_channels
s.twitter_username = self.config.twitter_username
s.twitter_password = self.config.twitter_password
s.b = self.config.a

Figure 8-1

Ok what you see here may differ from what is in your bot.py file. This is because the above is from my fork. I personally added the last three to the above list for some modules I made. The way you add something to this list is you create a variable in your default.py, we'll call it "a" then you come on over to the above section in bot.py and do s.b = self.config.a

Now what that line does is this it takes the variable 'a' from default.py and then assigns it to s.b. Then if you want to call that variable 'a' from your module you'll call it by "input.b" and it'll pass it to your module. I personally I haven't tested whether it's passed by reference or if it's passed by value. I would be willing to bet it's passed by value to prevent changes from your config.

That's pretty much it to creating global variables accessible in any module. Like I said above please do NOT attempt this if you need an absolutely stable version of phenny and don't know what you are doing.

NOTE: Again to access these 'global' variables in phenny you need to pass 'input' to the function in order to use these.

8. Further Reading

If you are new to Python and have programming experience in some other language I would highly recommend the e-book Dive Into Python. It can be found for free at Dive Into Python.

Regardless if you know Python or are comfortable with Python you should still read up on Python. I am always learning new and fantastic things that one can do with Python. It is truly an amazing language!

9. Author's Comments

If you have any comments or questions, please don't hesitate to ask!

You can also reach Sean B. Palmer via his website, inamidst.

Also you can reach Sean B. Palmer in #sbp and I frequent several channels including #sbp and #jenni.

Something went wrong with that request. Please try again.