diff options
Diffstat (limited to 'bot/MeetBot/plugin.py')
-rw-r--r-- | bot/MeetBot/plugin.py | 302 |
1 files changed, 302 insertions, 0 deletions
diff --git a/bot/MeetBot/plugin.py b/bot/MeetBot/plugin.py new file mode 100644 index 0000000..e7a621a --- /dev/null +++ b/bot/MeetBot/plugin.py @@ -0,0 +1,302 @@ +### +# 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): + """<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): + """<channel> <network> <saveit=True> + + 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): + """<text> + + 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: <command> <line of the command> + 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: |