5f8bdbeef1
This is particularly useful if the chair forgot to end the meeting or has been net split out and the nick is no longer useable. Change-Id: Id5c0c11ec94898f3a79ac253a9d804baec79a85d
309 lines
12 KiB
Python
309 lines
12 KiB
Python
###
|
|
# 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."
|
|
" Use #endmeeting first.")
|
|
return
|
|
name = payload[13:].strip()
|
|
if not name:
|
|
irc.error("A meeting name is required, e.g., "
|
|
"'#startmeeting Marketing Committee'")
|
|
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:
|