From 7a1e26418bbf2c35b68dcb0a546623af0056e7f4 Mon Sep 17 00:00:00 2001 From: Don Talton Date: Thu, 19 Mar 2020 10:35:37 -0700 Subject: [PATCH] Update meetbot to work under python3 Code changes to make meetbot work under python3 with limnoria. No functional changes. Change-Id: Ic35da2bf94b32c04651d852736cdce1d956f02e0 --- MeetBot/__init__.py | 9 ++-- MeetBot/plugin.py | 101 +++++++++++++++++++++------------------ MeetBot/supybotconfig.py | 12 ++--- MeetBot/test.py | 4 +- ircmeeting/items.py | 4 +- ircmeeting/meeting.py | 67 +++++++++++++------------- ircmeeting/writers.py | 30 ++++++------ requirements.txt | 3 ++ 8 files changed, 121 insertions(+), 109 deletions(-) create mode 100644 requirements.txt diff --git a/MeetBot/__init__.py b/MeetBot/__init__.py index 5a43105..879c669 100644 --- a/MeetBot/__init__.py +++ b/MeetBot/__init__.py @@ -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 diff --git a/MeetBot/plugin.py b/MeetBot/plugin.py index 217cff5..208216b 100644 --- a/MeetBot/plugin.py +++ b/MeetBot/plugin.py @@ -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: - 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: + # 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 diff --git a/MeetBot/supybotconfig.py b/MeetBot/supybotconfig.py index d4921a9..36c65f5 100644 --- a/MeetBot/supybotconfig.py +++ b/MeetBot/supybotconfig.py @@ -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. diff --git a/MeetBot/test.py b/MeetBot/test.py index 28dba8a..b29a4dd 100644 --- a/MeetBot/test.py +++ b/MeetBot/test.py @@ -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) diff --git a/ircmeeting/items.py b/ircmeeting/items.py index 6b04964..29da06b 100644 --- a/ircmeeting/items.py +++ b/ircmeeting/items.py @@ -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'): diff --git a/ircmeeting/meeting.py b/ircmeeting/meeting.py index 6b9d2ff..ffb4ca6 100644 --- a/ircmeeting/meeting.py +++ b/ircmeeting/meeting.py @@ -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]) diff --git a/ircmeeting/writers.py b/ircmeeting/writers.py index f214ea5..3756ee6 100644 --- a/ircmeeting/writers.py +++ b/ircmeeting/writers.py @@ -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 = (''''''%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 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..72aef9a --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +limnoria==2020.1.31 +pkg-resources==0.0.0 +Pygments==2.6.1