Update meetbot to work under python3

Code changes to make meetbot work under python3 with limnoria.
No functional changes.

Change-Id: Ic35da2bf94b32c04651d852736cdce1d956f02e0
This commit is contained in:
Don Talton 2020-03-19 10:35:37 -07:00
parent 36a94f9d97
commit 7a1e26418b
8 changed files with 121 additions and 109 deletions

View File

@ -35,6 +35,7 @@ here. This should describe *what* the plugin does.
import supybot
import supybot.world as world
import imp
# Use this for the version of this plugin. You may wish to put a CVS keyword
# in here if you're keeping the plugin in CVS or some similar system.
@ -50,14 +51,14 @@ __contributors__ = {}
# This is a url where the most recent plugin package can be downloaded.
__url__ = '' # 'http://supybot.com/Members/yourname/MeetBot/download'
import config
import plugin
reload(plugin) # In case we're being reloaded.
from . import config
from . import plugin
imp.reload(plugin) # In case we're being reloaded.
# Add more reloads here if you add third-party modules and want them to be
# reloaded when this plugin is reloaded. Don't forget to import them as well!
if world.testing:
import test
from . import test
Class = plugin.Class
configure = config.configure

View File

@ -37,10 +37,11 @@ import supybot.ircmsgs as ircmsgs
import time
import ircmeeting.meeting as meeting
import supybotconfig
from . import supybotconfig
import imp
# Because of the way we override names, we need to reload these in order.
meeting = reload(meeting)
supybotconfig = reload(supybotconfig)
meeting = imp.reload(meeting)
supybotconfig = imp.reload(supybotconfig)
if supybotconfig.is_supybotconfig_enabled(meeting.Config):
supybotconfig.setup_config(meeting.Config)
@ -100,13 +101,19 @@ class MeetBot(callbacks.Plugin):
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):
str(x)
irc.sendMsg(ircmsgs.topic(channel, x))
def _sendReply(x):
str(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,
@ -142,14 +149,14 @@ class MeetBot(callbacks.Plugin):
nick = irc.nick
channel = msg.args[0]
payload = msg.args[1]
Mkey = (channel,irc.network)
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)"
print(traceback.print_exc())
print("(above exception in outFilter, ignoring)")
return msg
# These are admin commands, for use by the bot owner when there
@ -170,7 +177,7 @@ class MeetBot(callbacks.Plugin):
Save all currently active meetings."""
numSaved = 0
for M in meeting_cache.iteritems():
for M in meeting_cache.items():
M.config.save()
irc.reply("Saved %d meetings."%numSaved)
savemeetings = wrap(savemeetings, ['admin'])
@ -261,46 +268,46 @@ class MeetBot(callbacks.Plugin):
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)
# 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

View File

@ -60,14 +60,14 @@ class WriterMap(registry.String):
writer_map[ext] = getattr(writers, writer)
self.setValue(writer_map)
def setValue(self, writer_map):
for e, w in writer_map.iteritems():
for e, w in writer_map.items():
if not hasattr(w, "format"):
raise ValueError("Writer %s must have method .format()"%
w.__name__)
self.value = writer_map
def __str__(self):
writers_string = [ ]
for ext, w in self.value.iteritems():
for ext, w in self.value.items():
name = w.__name__
writers_string.append("%s:%s"%(name, ext))
return " ".join(writers_string)
@ -81,7 +81,7 @@ class SupybotConfigProxy(object):
# We need to call the __init__ *after* we have rebound the
# method to get variables from the config proxy.
old_init = self.__C.__init__
new_init = types.MethodType(old_init.im_func, self, old_init.im_class)
new_init = types.MethodType(old_init.__func__, self, old_init.__self__.__class__)
new_init(*args, **kwargs)
def __getattr__(self, attrname):
@ -91,7 +91,7 @@ class SupybotConfigProxy(object):
if attrname in settable_attributes:
M = self.M
value = M._registryValue(attrname, channel=M.channel)
if not isinstance(value, (str, unicode)):
if not isinstance(value, str):
return value
# '.' is used to mean "this is not set, use the default
# value from the python config class.
@ -122,7 +122,7 @@ class SupybotConfigProxy(object):
# values). This will slow things down a little bit, but
# that's just the cost of duing business.
if hasattr(value, 'im_func'):
return types.MethodType(value.im_func, self, value.im_class)
return types.MethodType(value.__func__, self, value.__self__.__class__)
return value
@ -145,7 +145,7 @@ def setup_config(OriginalConfig):
continue
attr = getattr(OriginalConfig, attrname)
# Don't configure attributes that aren't strings.
if isinstance(attr, (str, unicode)):
if isinstance(attr, str):
attr = attr.replace('\n', '\\n')
# For a global value: conf.registerGlobalValue and remove the
# channel= option from registryValue call above.

View File

@ -69,12 +69,12 @@ class MeetBotTestCase(ChannelPluginTestCase):
groups = re.search(test[0], line).groups()
# Output pattern depends on input pattern
if isinstance(test[1], int):
print groups[test[1]-1], reply
print(groups[test[1]-1], reply)
assert re.search(re.escape(groups[test[1]-1]), reply),\
'line "%s" gives output "%s"'%(line, reply)
# Just match the given pattern.
else:
print test[1], reply
print(test[1], reply)
assert re.search(test[1], reply.decode('utf-8')), \
'line "%s" gives output "%s"'%(line, reply)

View File

@ -33,7 +33,7 @@ import os
import re
import time
import writers
from . import writers
#from writers import html, rst
import itertools
@ -84,7 +84,7 @@ class _BaseItem(object):
return replacements
def template(self, M, escapewith):
template = { }
for k,v in self.get_replacements(M, escapewith).iteritems():
for k,v in self.get_replacements(M, escapewith).items():
if k not in ('itemtype', 'line', 'topic',
'url', 'url_quoteescaped',
'nick', 'time', 'link', 'anchor'):

View File

@ -35,10 +35,11 @@ import re
import stat
import textwrap
import writers
import items
reload(writers)
reload(items)
from . import writers
from . import items
import imp
imp.reload(writers)
imp.reload(items)
__version__ = "0.1.4"
@ -104,10 +105,10 @@ class Config(object):
input_codec = 'utf-8'
output_codec = 'utf-8'
# Functions to do the i/o conversion.
def enc(self, text):
return text.encode(self.output_codec, 'replace')
def dec(self, text):
return text.decode(self.input_codec, 'replace')
# def enc(self, text):
# return text.encode(self.output_codec, 'replace')
# def dec(self, text):
# return text.decode(self.input_codec, 'replace')
# Write out select logfiles
update_realtime = True
# CSS configs:
@ -132,14 +133,14 @@ class Config(object):
self.M = M
self.writers = { }
# Update config values with anything we may have
for k,v in extraConfig.iteritems():
for k,v in extraConfig.items():
setattr(self, k, v)
if hasattr(self, "init_hook"):
self.init_hook()
if writeRawLog:
self.writers['.log.txt'] = writers.TextLog(self.M)
for extension, writer in self.writer_map.iteritems():
for extension, writer in self.writer_map.items():
self.writers[extension] = writer(self.M)
self.safeMode = safeMode
def filename(self, url=False):
@ -219,9 +220,9 @@ class Config(object):
# If it doesn't, then it's assumed that the write took
# care of writing (or publishing or emailing or wikifying)
# it itself.
if isinstance(text, unicode):
text = self.enc(text)
if isinstance(text, (str, unicode)):
# if isinstance(text, str):
# text = self.enc(text)
if isinstance(text, str):
# Have a way to override saving, so no disk files are written.
if getattr(self, "dontSave", False):
pass
@ -281,7 +282,7 @@ else:
# First source of config: try just plain importing it
try:
import meetingLocalConfig
meetingLocalConfig = reload(meetingLocalConfig)
meetingLocalConfig = imp.reload(meetingLocalConfig)
if hasattr(meetingLocalConfig, 'Config'):
LocalConfig = meetingLocalConfig.Config
except ImportError:
@ -291,7 +292,7 @@ else:
fname = os.path.join(dirname, "meetingLocalConfig.py")
if os.access(fname, os.F_OK):
meetingLocalConfig = { }
execfile(fname, meetingLocalConfig)
exec(compile(open(fname, "rb").read(), fname, 'exec'), meetingLocalConfig)
LocalConfig = meetingLocalConfig["Config"]
break
if LocalConfig is not None:
@ -491,9 +492,9 @@ class MeetingCommands(object):
m = 'Voted on "%s?" Results are' % self._voteTopic
self.reply(m)
self.do_showvote(**kwargs)
for k,s in self._votes.iteritems():
vote = self._voteOptions[map(unicode.lower,
self._voteOptions).index(k)]
for k,s in self._votes.items():
vote = self._voteOptions[list(map(str.lower,
self._voteOptions)).index(k)]
m += ", %s: %s" % (vote, len(s))
m = items.Vote(nick=nick, line=m, **kwargs)
self.additem(m)
@ -505,7 +506,7 @@ class MeetingCommands(object):
"""Vote for specific voting topic option."""
if self._voteTopic is None: return
vote = line.lower()
if vote in map(unicode.lower, self._voteOptions):
if vote in list(map(str.lower, self._voteOptions)):
oldvote = self._voters.get(nick)
if oldvote is not None:
self._votes[oldvote].remove(nick)
@ -520,13 +521,13 @@ class MeetingCommands(object):
def do_showvote(self, **kwargs):
"""Show intermediate vote results."""
if self._voteTopic is None: return
for k, s in self._votes.iteritems():
for k, s in self._votes.items():
# Attempt to print all the names while obeying the 512 character
# limit. Would probably be better to calculate message overhead and
# determine wraps()s width argument based on that.
ms = textwrap.wrap(", ".join(s), 400)
vote = self._voteOptions[map(unicode.lower,
self._voteOptions).index(k)]
vote = self._voteOptions[list(map(str.lower,
self._voteOptions)).index(k)]
for m2 in ms:
m1 = "%s (%s): " % (vote, len(s))
self.reply(m1 + m2)
@ -559,7 +560,7 @@ class Meeting(MeetingCommands, object):
self.config = Config(self, writeRawLog=writeRawLog, safeMode=safeMode,
extraConfig=extraConfig)
if oldtopic:
self.oldtopic = self.config.dec(oldtopic)
self.oldtopic = oldtopic
else:
self.oldtopic = None
self.lines = [ ]
@ -582,15 +583,15 @@ class Meeting(MeetingCommands, object):
def reply(self, x):
"""Send a reply to the IRC channel."""
if hasattr(self, '_sendReply') and not self._lurk:
self._sendReply(self.config.enc(x))
self._sendReply(x)
else:
print "REPLY:", self.config.enc(x)
print("REPLY:", x)
def topic(self, x):
"""Set the topic in the IRC channel."""
if hasattr(self, '_setTopic') and not self._lurk:
self._setTopic(self.config.enc(x))
self._setTopic(x)
else:
print "TOPIC:", self.config.enc(x)
print("TOPIC:", x)
def settopic(self):
"The actual code to set the topic"
if self._meetingTopic:
@ -614,8 +615,8 @@ class Meeting(MeetingCommands, object):
linenum = self.addrawline(nick, line, time_)
if time_ is None: time_ = time.localtime()
nick = self.config.dec(nick)
line = self.config.dec(line)
# nick = self.config.dec(nick)
# line = self.config.dec(line)
# Handle any commands given in the line.
matchobj = self.config.command_RE.match(line)
@ -636,8 +637,8 @@ class Meeting(MeetingCommands, object):
def addrawline(self, nick, line, time_=None):
"""This adds a line to the log, bypassing command execution.
"""
nick = self.config.dec(nick)
line = self.config.dec(line)
# nick = self.config.dec(nick)
# line = self.config.dec(line)
self.addnick(nick)
line = line.strip(' \x01') # \x01 is present in ACTIONs
# Setting a custom time is useful when replying logs,
@ -736,7 +737,7 @@ if __name__ == '__main__':
filename = m.group(1)
else:
filename = os.path.splitext(fname)[0]
print 'Saving to:', filename
print('Saving to:', filename)
channel = '#'+os.path.basename(sys.argv[2]).split('.')[0]
M = Meeting(channel=channel, owner=None,
@ -760,5 +761,5 @@ if __name__ == '__main__':
M.addline(nick, "ACTION "+line, time_=time_)
#M.save() # should be done by #endmeeting in the logs!
else:
print 'Command "%s" not found.'%sys.argv[1]
print('Command "%s" not found.'%sys.argv[1])

View File

@ -38,7 +38,7 @@ import time
# Needed for testing with isinstance() for properly writing.
#from items import Topic, Action
import items
from . import items
# Data sanitizing for various output methods
def html(text):
@ -73,7 +73,7 @@ def makeNickRE(nick):
return re.compile('\\b'+re.escape(nick)+'\\b', re.IGNORECASE)
def MeetBotVersion():
import meeting
from . import meeting
if hasattr(meeting, '__version__'):
return ' '+meeting.__version__
else:
@ -121,12 +121,12 @@ class _BaseWriter(object):
'MeetBotVersion':MeetBotVersion(),
}
def iterNickCounts(self):
nicks = [ (n,c) for (n,c) in self.M.attendees.iteritems() ]
nicks = [ (n,c) for (n,c) in self.M.attendees.items() ]
nicks.sort(key=lambda x: x[1], reverse=True)
return nicks
def iterActionItemsNick(self):
for nick in sorted(self.M.attendees.keys(), key=lambda x: x.lower()):
for nick in sorted(list(self.M.attendees.keys()), key=lambda x: x.lower()):
nick_re = makeNickRE(nick)
def nickitems(nick_re):
for m in self.M.minutes:
@ -323,23 +323,23 @@ class _CSSmanager(object):
# Stylesheet specified
if getattr(self.M.config, 'cssEmbed_'+name, True):
# external stylesheet
css = file(css_fname).read()
css = open(css_fname, 'r').read()
return self._css_head%css
else:
# linked stylesheet
css_head = ('''<link rel="stylesheet" type="text/css" '''
'''href="%s">'''%cssfile)
return css_head
except Exception, exc:
except Exception as exc:
if not self.M.config.safeMode:
raise
import traceback
traceback.print_exc()
print "(exception above ignored, continuing)"
print("(exception above ignored, continuing)")
try:
css_fname = os.path.join(os.path.dirname(__file__),
'css-'+name+'-default.css')
css = open(css_fname).read()
css = open(css_fname, 'r').read()
return self._css_head%css
except:
if not self.M.config.safeMode:
@ -471,9 +471,9 @@ class HTMLlog2(_BaseWriter, _CSSmanager):
'nick':html(m.group('nick')),
'line':html(m.group('line')),})
continue
print l
print m.groups()
print "**error**", l
print(l)
print(m.groups())
print("**error**", l)
css = self.getCSS(name='log')
return html_template%{'pageTitle':"%s log"%html(M.channel),
@ -850,7 +850,7 @@ class ReST(_BaseWriter):
# Action Items, by person (This could be made lots more efficient)
ActionItemsPerson = [ ]
for nick in sorted(M.attendees.keys(), key=lambda x: x.lower()):
for nick in sorted(list(M.attendees.keys()), key=lambda x: x.lower()):
nick_re = makeNickRE(nick)
headerPrinted = False
for m in M.minutes:
@ -956,7 +956,7 @@ class Text(_BaseWriter):
ActionItemsPerson = [ ]
ActionItemsPerson.append(self.heading('Action items, by person'))
numberAssigned = 0
for nick in sorted(M.attendees.keys(), key=lambda x: x.lower()):
for nick in sorted(list(M.attendees.keys()), key=lambda x: x.lower()):
nick_re = makeNickRE(nick)
headerPrinted = False
for m in M.minutes:
@ -1084,7 +1084,7 @@ class MediaWiki(_BaseWriter):
ActionItemsPerson = [ ]
ActionItemsPerson.append(self.heading('Action items, by person'))
numberAssigned = 0
for nick in sorted(M.attendees.keys(), key=lambda x: x.lower()):
for nick in sorted(list(M.attendees.keys()), key=lambda x: x.lower()):
nick_re = makeNickRE(nick)
headerPrinted = False
for m in M.minutes:
@ -1190,7 +1190,7 @@ class PmWiki(MediaWiki, object):
return '%s %s\n'%('!'*(level+1), name)
def replacements(self):
#repl = super(PmWiki, self).replacements(self) # fails, type checking
repl = MediaWiki.replacements.im_func(self)
repl = MediaWiki.replacements.__func__(self)
repl['pageTitleHeading'] = self.heading(repl['pageTitle'],level=0)
return repl

3
requirements.txt Normal file
View File

@ -0,0 +1,3 @@
limnoria==2020.1.31
pkg-resources==0.0.0
Pygments==2.6.1