### # Copyright (c) 2009, Richard Darst # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### import supybot.utils as utils from supybot.commands import * import supybot.plugins as plugins import supybot.ircutils as ircutils import supybot.callbacks as callbacks import supybot.ircmsgs as ircmsgs import time import ircmeeting.meeting as meeting import supybotconfig # Because of the way we override names, we need to reload these in order. meeting = reload(meeting) supybotconfig = reload(supybotconfig) if supybotconfig.is_supybotconfig_enabled(meeting.Config): supybotconfig.setup_config(meeting.Config) meeting.Config = supybotconfig.get_config_proxy(meeting.Config) # By doing this, we can not lose all of our meetings across plugin # reloads. But, of course, you can't change the source too # drastically if you do that! try: meeting_cache except NameError: meeting_cache = {} try: recent_meetings except NameError: recent_meetings = [ ] class MeetBot(callbacks.Plugin): """Add the help for "@plugin help MeetBot" here This should describe *how* to use this plugin.""" def __init__(self, irc): self.__parent = super(MeetBot, self) self.__parent.__init__(irc) # Instead of using real supybot commands, I just listen to ALL # messages coming in and respond to those beginning with our # prefix char. I found this helpful from a not duplicating logic # standpoint (as well as other things). Ask me if you have more # questions. # This captures all messages coming into the bot. def doPrivmsg(self, irc, msg): nick = msg.nick channel = msg.args[0] payload = msg.args[1] network = irc.msg.tags['receivedOn'] # The following is for debugging. It's excellent to get an # interactive interperter inside of the live bot. use # code.interact instead of my souped-up version if you aren't # on my computer: #if payload == 'interact': # from rkddp.interact import interact ; interact() # Get our Meeting object, if one exists. Have to keep track # of different servers/channels. # (channel, network) tuple is our lookup key. Mkey = (channel,network) M = meeting_cache.get(Mkey, None) # Start meeting if we are requested if payload[:13] == '#startmeeting': if M is not None: irc.error("Can't start another meeting, one is in progress.") return # This callback is used to send data to the channel: def _setTopic(x): irc.sendMsg(ircmsgs.topic(channel, x)) def _sendReply(x): irc.sendMsg(ircmsgs.privmsg(channel, x)) def _channelNicks(): return irc.state.channels[channel].users M = meeting.Meeting(channel=channel, owner=nick, oldtopic=irc.state.channels[channel].topic, writeRawLog=True, setTopic = _setTopic, sendReply = _sendReply, getRegistryValue = self.registryValue, safeMode=True, channelNicks=_channelNicks, network=network, ) meeting_cache[Mkey] = M recent_meetings.append( (channel, network, time.ctime())) if len(recent_meetings) > 10: del recent_meetings[0] # If there is no meeting going on, then we quit if M is None: return # Add line to our meeting buffer. M.addline(nick, payload) # End meeting if requested: if M._meetingIsOver: #M.save() # now do_endmeeting in M calls the save functions del meeting_cache[Mkey] def outFilter(self, irc, msg): """Log outgoing messages from supybot. """ # Catch supybot's own outgoing messages to log them. Run the # whole thing in a try: block to prevent all output from # getting clobbered. try: if msg.command in ('PRIVMSG'): # Note that we have to get our nick and network parameters # in a slightly different way here, compared to doPrivmsg. nick = irc.nick channel = msg.args[0] payload = msg.args[1] Mkey = (channel,irc.network) M = meeting_cache.get(Mkey, None) if M is not None: M.addrawline(nick, payload) except: import traceback print traceback.print_exc() print "(above exception in outFilter, ignoring)" return msg # These are admin commands, for use by the bot owner when there # are many channels which may need to be independently managed. def listmeetings(self, irc, msg, args): """ List all currently-active meetings.""" reply = "" reply = ", ".join(str(x) for x in sorted(meeting_cache.keys()) ) if reply.strip() == '': irc.reply("No currently active meetings.") else: irc.reply(reply) listmeetings = wrap(listmeetings, ['admin']) def savemeetings(self, irc, msg, args): """ Save all currently active meetings.""" numSaved = 0 for M in meeting_cache.iteritems(): M.config.save() irc.reply("Saved %d meetings."%numSaved) savemeetings = wrap(savemeetings, ['admin']) def addchair(self, irc, msg, args, channel, network, nick): """ Add a nick as a chair to the meeting.""" Mkey = (channel,network) M = meeting_cache.get(Mkey, None) if not M: irc.reply("Meeting on channel %s, network %s not found"%( channel, network)) return M.chairs.setdefault(nick, True) irc.reply("Chair added: %s on (%s, %s)."%(nick, channel, network)) addchair = wrap(addchair, ['admin', "channel", "something", "nick"]) def deletemeeting(self, irc, msg, args, channel, network, save): """ Delete a meeting from the cache. If save is given, save the meeting first, defaults to saving.""" Mkey = (channel,network) if Mkey not in meeting_cache: irc.reply("Meeting on channel %s, network %s not found"%( channel, network)) return if save: M = meeting_cache.get(Mkey, None) import time M.endtime = time.localtime() M.config.save() del meeting_cache[Mkey] irc.reply("Deleted: meeting on (%s, %s)."%(channel, network)) deletemeeting = wrap(deletemeeting, ['admin', "channel", "something", optional("boolean", True)]) def recent(self, irc, msg, args): """ List recent meetings for admin purposes. """ reply = [] for channel, network, ctime in recent_meetings: Mkey = (channel,network) if Mkey in meeting_cache: state = ", running" else: state = "" reply.append("(%s, %s, %s%s)"%(channel, network, ctime, state)) if reply: irc.reply(" ".join(reply)) else: irc.reply("No recent meetings in internal state.") recent = wrap(recent, ['admin']) def pingall(self, irc, msg, args, message): """ Send a broadcast ping to all users on the channel. An message to be sent along with this ping must also be supplied for this command to work. """ nick = msg.nick channel = msg.args[0] payload = msg.args[1] # We require a message to go out with the ping, we don't want # to waste people's time: if channel[0] != '#': irc.reply("Not joined to any channel.") return if message is None: irc.reply("You must supply a description with the `pingall` command. We don't want to go wasting people's times looking for why they are pinged.") return # Send announcement message irc.sendMsg(ircmsgs.privmsg(channel, message)) # ping all nicks in lines of about 256 nickline = '' nicks = sorted(irc.state.channels[channel].users, key=lambda x: x.lower()) for nick in nicks: nickline = nickline + nick + ' ' if len(nickline) > 256: irc.sendMsg(ircmsgs.privmsg(channel, nickline)) nickline = '' irc.sendMsg(ircmsgs.privmsg(channel, nickline)) # Send announcement message irc.sendMsg(ircmsgs.privmsg(channel, message)) pingall = wrap(pingall, [optional('text', None)]) def __getattr__(self, name): """Proxy between proper supybot commands and # MeetBot commands. This allows you to use MeetBot: instead of the typical #command version. However, it's disabled by default as there are some possible unresolved issues with it. To enable this, you must comment out a line in the main code. It may be enabled in a future version. """ # First, proxy to our parent classes (__parent__ set in __init__) try: return self.__parent.__getattr__(name) except AttributeError: pass # Disabled for now. Uncomment this if you want to use this. raise AttributeError if not hasattr(meeting.Meeting, "do_"+name): raise AttributeError def wrapped_function(self, irc, msg, args, message): channel = msg.args[0] payload = msg.args[1] #from fitz import interactnow ; reload(interactnow) #print type(payload) payload = "#%s %s"%(name,message) #print payload import copy msg = copy.copy(msg) msg.args = (channel, payload) self.doPrivmsg(irc, msg) # Give it the signature we need to be a callable supybot # command (it does check more than I'd like). Heavy Wizardry. instancemethod = type(self.__getattr__) wrapped_function = wrap(wrapped_function, [optional('text', '')]) return instancemethod(wrapped_function, self, MeetBot) Class = MeetBot # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: