IMPORTANT: I completely re-sketched the bot's code. I believe it to be superior now. You can check the old version at Paste #189293 | LodgeIt! .
BOT V2:
- What has changed?
Given the great number of functions and work-arounds existent in the last code, I decided to make a version 2. Now, we use classes (sweet!), providing a much cleaner, understandable and sane code. Thus far, this version is (function-wise) simpler than the other, having only a basic commands suit (act and say), plus a group permissions class. But, but, it's easier to understand and improve! At least I hope so. Let's get to the code:
- The code:
Code:# -*- coding: utf-8 -*- import socket, dbm, random, sys, time ########################### Editing below this is necessary: HOST = "irc.freenode.net" PORT = 6667 NICK = "FailBot" USERNAME = "FailBot" REALNAME = "FailBot" CHANNELS = ["##devil"] bot_prefix = "!" import_plugins = "BasicCommands(), UserLevel(), Admin()" #must be classes ############################ Editing beyond here is not necessary. storage = dbm.open("botv2", "c") if "password" not in storage.keys(): set_passwd = raw_input("Set an ident password, put 0 if none: ") storage["password"] = set_passwd set_admin = raw_input("Set a nick to be the admin. DO NOT leave blank: ") storage["users_Owners"] = set_admin class IrcMessage: '''Parses the IRC message, defining msg.prefix, msg.command, and msg.args''' def __init__(self, msg): self.prefix, self.command, self.args = self.parsemsg(msg) def parsemsg(self, s): trailing = 0 prefix = 0 if s[0] == ":": s = s[1:].split(' ', 1) prefix = s[0] s = s[1] if " :" in s: s = s.split(" :", 1) trailing = s[1] s = s[0] args = s.split() command = args.pop(0) if trailing != 0: args.append(trailing) return prefix, command, args class Privmsg: '''Parses a PRIVMSG on IRC, defining msg.nick, msg.channel, msg.message, msg.bot_command, and msg.bot_command_args. Also contains methods: reply (send message into channel), reply_act(send a /me into channel).''' def __init__(self, msg): try: self.nick = msg.prefix.split("!", 1)[0] self.channel = msg.args[0] self.message = msg.args[1] self.bot_command = self.message.split()[0] if not self.bot_command == self.message.split(): self.bot_command_args = self.message.split()[1:] self.true = 1 except: self.true = 0 def reply(self, msg): socket.send("PRIVMSG %s :%s\r\n" % (self.channel, msg)) def reply_act(self, msg): socket.send("PRIVMSG %s :\x01ACTION %s\x01\r\n" % (self.channel, msg)) class BasicCommands: '''A basic suit of commands. Include: <bot prefix>say (to say stuff in the channel) and <bot prefix>act (for /me). ''' def say(self, privmsg): if has_permission("users_BasicCommands", privmsg.nick) : privmsg.reply(" ".join(privmsg.bot_command_args)) else : privmsg.reply("Permission denied.") def act(self, privmsg): if has_permission("users_BasicCommands", privmsg.nick) : privmsg.reply_act(" ".join(privmsg.bot_command_args)) else : privmsg.reply("Permission denied.") class UserLevel: '''An user level plugin. ''' groups = [x for x in storage.keys() if x.startswith("users_")] def set_group(self, privmsg): groups_edited = [] for grp in privmsg.bot_command_args[2:] : if privmsg.bot_command_args[1] == '+' and has_permission("users_Owners", privmsg.nick) : if storage.get(grp, "") : usr_list = storage[grp].split() usr_list.append(privmsg.bot_command_args[0]) storage[grp] = " ".join(usr_list) groups_edited.append(grp) else : storage[grp] = privmsg.bot_command_args[0] groups_edited.append(grp) self.groups.append(grp) elif privmsg.bot_command_args[1] == '-' and has_permission("users_Owners", privmsg.nick) and storage.get(grp, "") : usr_list = storage[grp].split() if privmsg.bot_command_args[0] in usr_list : usr_list.remove(privmsg.bot_command_args[0]) storage[grp] = " ".join(usr_list) groups_edited.append(grp) if privmsg.bot_command_args[1] == '+': privmsg.reply("Added user %s to groups: %s" % (privmsg.bot_command_args[0], ", ".join(groups_edited))) elif privmsg.bot_command_args[1] == '-': privmsg.reply("Removed user %s from groups: %s" % (privmsg.bot_command_args[0], ", ".join(groups_edited))) def read_group(self, privmsg): if privmsg.bot_command_args[0] == '*' : privmsg.reply("Groups: %s" % ", ".join(self.groups)) else : if privmsg.bot_command_args[0] in self.groups : privmsg.reply("People in the group %s: %s" % (privmsg.bot_command_args[0], ", ".join(storage[privmsg.bot_command_args[0]].split()))) else : privmsg.reply("Group %s not found" % privmsg.bot_command_args[0]) def del_group(self, privmsg): grps_removed = [] grps_notfound = [] if has_permission("users_Owners", privmsg.nick) : for grp in privmsg.bot_command_args : if grp in self.groups : self.groups.remove(grp) del storage[grp] grps_removed.append(grp) else : grps_notfound.append(grp) privmsg.reply("Removed groups: %s. Could not find groups: %s." % (", ".join(grps_removed), ", ".join(grps_notfound))) else : privmsg.reply("Permission denied.") class Admin: def quit(self, privmsg): if has_permission("users_Owners", privmsg.nick): privmsg.reply("Quitting...") socket.close() sys.exit() else: privmsg.reply("Permission denied.") def sendraw(self, privmsg): if has_permission("users_Owners", privmsg.nick): socket.send(" ".join(privmsg.bot_command_args) + '\n') else: privmsg.reply("Permission denied.") def mode(self, privmsg): if has_permission("users_Owners", privmsg.nick): socket.send("MODE %s " % privmsg.channel + " ".join(privmsg.bot_command_args) + '\n') else: privmsg.reply("Permission denied.") def topic(self, privmsg): if has_permission("users_Owners", privmsg.nick): socket.send("TOPIC %s :" % privmsg.channel + " ".join(privmsg.bot_command_args) + '\n') else: privmsg.reply("Permission denied.") def kick(self, privmsg): if has_permission("users_Owners", privmsg.nick): socket.send("KICK %s %s :%s\n" % (privmsg.channel, privmsg.bot_command_args[0], " ".join(privmsg.bot_command_args[1:]))) else: privmsg.reply("Permission denied.") def has_permission(group, user): if "UserLevel()" in import_plugins.split(", ") : try : if user in storage["users_Owners"].split() : return True elif user in storage[group].split() : return True else : return False except : return False else : return True exec("plugins = %s" % import_plugins) #INITIAL HANDSHAKE socket = socket.socket() socket.connect((HOST, PORT)) if not storage["password"] == "0": socket.send("PASS :%s\r\n" % (storage["password"])) time.sleep(3) socket.send("NICK %s\r\n" % (NICK)) socket.send("USER %s * * :%s\r\n" % (USERNAME, REALNAME)) for channel in CHANNELS: socket.send("JOIN %s\r\n" % (channel)) #END OF HANDSHAKE recv = "" while True: recv = recv + socket.recv(4096) s = recv.split("\r\n") recv = s.pop() for msg in s: print msg msg = IrcMessage(msg) if msg.command == "PRIVMSG": privmsg = Privmsg(msg) for plugin in plugins: if privmsg.true == 1: if hasattr(plugin, privmsg.bot_command[1:]) and privmsg.message.startswith(bot_prefix) : command = getattr(plugin, privmsg.bot_command[1:]) try : command(privmsg) except SystemExit : raise except : privmsg.reply("Error calling function.") elif msg.command == "PING": socket.send("PONG\r\n") elif msg.command == "KICK" and NICK in msg.args: socket.send("JOIN %s\n" % msg.args[0])
- How to run:
Running it is quite simple, obvious and straightforward. There are no mysteries like in the last version, really. Just edit whatever you need and enter information accordingly.
- How to edit:
Basically, what you need to do do is create a class. And the command to call a method via irc is simply !name, so, if we have:
Typing !test on the channel will call Test.test. What is important to notice is the methods will be called with privmsg as an argument, and privmsg is an instance of the class Privmsg (what a surprise). Important things to know:Code:class Test: def test(self, privmsg): privmsg.reply("Success!")
privmsg.nick = nick of whoever typed the message which triggered the method
privmsg.bot_command = the actual command. ex, in !kill pedro3005, !kill is the bot_command.
privmsg.bot_command_args = A LIST of the arguments after bot_command split by spaces.
privmsg.channel = the channel the message was sent on (although you usually won't have to worry about this)
privmsg.message = the actual message. I.E., inthe actual message will be "a".Code::pedro3005~pedro@unaffiliated/pedro3005 PRIVMSG #channel :a
privmsg.reply = this sends a message into the channel that triggered the method. I.E., privmsg.reply("I am here")
privmsg.reply_act = this is the same as above, only it is an action.
So, simply write a class and add it to the import_plugins list on the configuration area. REMEMBER that the method will always be called with privmsg as an argument, so never try to take more than one argument!
- On the group permission system:
The way this works is we have various groups, and these groups have access to all methods inside a class. So for instance, the group users_BasicCommands will have access to all the BasicCommand class methods. Whenever naming a group for this, ALWAYS precede it with users_. That is important! So, to check if a certain user has permission to access a certain class, I wrote the function has_permission(group, user). What it does is firstly check if the group permission system is enabled. If not, it will return TRUE. Then, it checks if the user is either in the owners group, users_Owners, or in the group the function was called with. If yes, it returns TRUE. If not, it will return FALSE.
So, pragmatically, what you need to know about it:
!set_group <user> <+/-> users_Group1 users_Group2
!read_group <group/*> (* will list all groups, and if you specify a group it will list everyone on it)
!del_group users_Group1
Users on the users_Owners group have access to ALL commands. I wish to improve the code to this class (it looks rather ugly), so any tips are welcomed.
EDIT: Some bug fixes, added Admin class (some sweet new functions).
Last edited by pedro3005; 04-06-2010 at 09:25 PM. Reason: bugs fixed
Yes, they're very fun to make-- you should try it some time!
I am now in the arduous process of reinventing the code, since I learned about Classes.
This will be good though, I'm making things more independent and nicely separated. Maybe the code will also be easier to understand.
It's just that I have been really busy with school and don't have much time for python, but I'll keep working on it. AFAIK though, the code presented above, whilst ugly (let's admit that), works.
New version presented, let's check it out![]()
Also check out Twisted's IRCClient. Very easy to use/extend once you get your head around the event-driven nature of Twisted.
Yes, I started the yet again redevelopment of this bot. It's over at launchpad and uses Twisted.
Link: https://launchpad.net/failbot
Pedro3005,
I love your bot, very interesting use of classes. However, I cannot seem to define a class that will let me send a message to a specific channel that the bot is in from another channel? i.e. a specified channel through a message. Can you help?
There are currently 1 users browsing this thread. (0 members and 1 guests)
Bookmarks