Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-limnoria for openSUSE:Factory checked in at 2022-07-15 13:52:26 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-limnoria (Old) and /work/SRC/openSUSE:Factory/.python-limnoria.new.1523 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-limnoria" Fri Jul 15 13:52:26 2022 rev:26 rq:989193 version:2022.07.03 Changes: -------- --- /work/SRC/openSUSE:Factory/python-limnoria/python-limnoria.changes 2022-04-21 15:48:52.932330563 +0200 +++ /work/SRC/openSUSE:Factory/.python-limnoria.new.1523/python-limnoria.changes 2022-07-15 13:52:43.903568340 +0200 @@ -1,0 +2,7 @@ +Thu Jul 14 13:53:25 UTC 2022 - Atri Bhattacharya <badshah...@gmail.com> + +- Update to version 2022-07-03: + * PluginDownloader: replace automatic 2to3 step with a simple + warning. + +------------------------------------------------------------------- Old: ---- limnoria-2022.03.19.tar.gz New: ---- limnoria-2022.07.03.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-limnoria.spec ++++++ --- /var/tmp/diff_new_pack.Uzum38/_old 2022-07-15 13:52:44.383568496 +0200 +++ /var/tmp/diff_new_pack.Uzum38/_new 2022-07-15 13:52:44.387568497 +0200 @@ -18,9 +18,9 @@ %define skip_python2 1 %define appname limnoria -%define srcver 2022-03-19 +%define srcver 2022-07-03 Name: python-limnoria -Version: 2022.03.19 +Version: 2022.07.03 Release: 0 Summary: A modified version of Supybot (an IRC bot and framework) License: BSD-3-Clause @@ -87,6 +87,7 @@ %python_clone -a %{buildroot}%{_mandir}/man1/supybot-plugin-doc.1 %python_clone -a %{buildroot}%{_mandir}/man1/supybot-test.1 %python_clone -a %{buildroot}%{_mandir}/man1/supybot-wizard.1 +%python_clone -a %{buildroot}%{_mandir}/man1/supybot-reset-password.1 %python_clone -a %{buildroot}%{_bindir}/supybot %python_clone -a %{buildroot}%{_bindir}/supybot-adduser %python_clone -a %{buildroot}%{_bindir}/supybot-botchk @@ -107,7 +108,7 @@ %{python_install_alternative supybot supybot-adduser supybot-botchk supybot-plugin-create supybot-plugin-doc supybot-reset-password supybot-test supybot-wizard supybot.1 supybot-adduser.1 supybot-botchk.1 supybot-plugin-create.1 - supybot-plugin-doc.1 supybot-test.1 supybot-wizard.1 + supybot-plugin-doc.1 supybot-reset-password.1 supybot-test.1 supybot-wizard.1 } %postun @@ -131,6 +132,7 @@ %python_alternative %{_mandir}/man1/supybot-botchk.1 %python_alternative %{_mandir}/man1/supybot-plugin-create.1 %python_alternative %{_mandir}/man1/supybot-plugin-doc.1 +%python_alternative %{_mandir}/man1/supybot-reset-password.1 %python_alternative %{_mandir}/man1/supybot-test.1 %python_alternative %{_mandir}/man1/supybot-wizard.1 ++++++ limnoria-2022.03.19.tar.gz -> limnoria-2022.07.03.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Limnoria-master-2022-03-19/man/supybot-reset-password.1 new/Limnoria-master-2022-07-03/man/supybot-reset-password.1 --- old/Limnoria-master-2022-03-19/man/supybot-reset-password.1 1970-01-01 01:00:00.000000000 +0100 +++ new/Limnoria-master-2022-07-03/man/supybot-reset-password.1 2022-06-23 22:31:17.000000000 +0200 @@ -0,0 +1,35 @@ +.\" Process this file with +.\" groff -man -Tascii supybot-reset-password.1 +.\" +.TH SUPYBOT-RESET-PASSWORD 1 "JUNE 2022" +.SH NAME +supybot-reset-password \- Changes a user's password in a Supybot users.conf file +.SH SYNOPSIS +.B supybot-reset-password +.RI [ options ] " users.conf +.SH DESCRIPTION +.B supybot-reset-password +changes a user's password in a Supybot users.conf file +.SH OPTIONS +.TP +.B \-\^\-version +Show version of program. +.TP +.BR \-h ", " \-\^\-help +Show summary of options. +.TP +.BR \-u " NAME" "\fR,\fP \-\^\-username=" NAME +Specifies the username to use for the new user. +.TP +.BR \-p " PASSWORD" "\fR,\fP \-\^\-password=" PASSWORD +Specifies the new password to use for the new user. +.SH "SEE ALSO" +.IR python (1), +.IR supybot (1), +.IR supybot-adduser (1) +.SH AUTHOR +This manual page was originally written by Valentin Lorentz +<progval plus limnoria at progval dot net>. Permission is granted to copy, +distribute and/or modify this document under the terms of the Supybot +license, a BSD-style license. + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Limnoria-master-2022-03-19/plugins/AutoMode/plugin.py new/Limnoria-master-2022-07-03/plugins/AutoMode/plugin.py --- old/Limnoria-master-2022-03-19/plugins/AutoMode/plugin.py 2022-03-17 22:29:10.000000000 +0100 +++ new/Limnoria-master-2022-07-03/plugins/AutoMode/plugin.py 2022-06-23 22:31:17.000000000 +0200 @@ -144,7 +144,7 @@ break try: do('op') - if 'h' in irc.state.supported['prefix']: + if 'h' in irc.state.supported.get('prefix', ''): do('halfop') except Continue: return diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Limnoria-master-2022-03-19/plugins/BadWords/config.py new/Limnoria-master-2022-07-03/plugins/BadWords/config.py --- old/Limnoria-master-2022-03-19/plugins/BadWords/config.py 2022-03-17 22:29:10.000000000 +0100 +++ new/Limnoria-master-2022-07-03/plugins/BadWords/config.py 2022-06-23 22:31:17.000000000 +0200 @@ -113,6 +113,9 @@ filtering. If it's True, however, it will interact poorly with other plugins that do coloring or bolding of text."""))) +conf.registerChannelValue(BadWords, 'selfCensor', + registry.Boolean(True, _("""Determines whether the bot will filter its own + messages."""))) conf.registerChannelValue(BadWords, 'kick', registry.Boolean(False, _("""Determines whether the bot will kick people with a warning when they use bad words."""))) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Limnoria-master-2022-03-19/plugins/BadWords/messages.pot new/Limnoria-master-2022-07-03/plugins/BadWords/messages.pot --- old/Limnoria-master-2022-03-19/plugins/BadWords/messages.pot 2022-03-17 22:29:10.000000000 +0100 +++ new/Limnoria-master-2022-07-03/plugins/BadWords/messages.pot 2022-06-23 22:31:17.000000000 +0200 @@ -81,18 +81,24 @@ #: config.py:117 msgid "" +"Determines whether the bot will filter its own\n" +" messages." +msgstr "" + +#: config.py:120 +msgid "" "Determines whether the bot will kick people with\n" " a warning when they use bad words." msgstr "" -#: config.py:120 +#: config.py:123 msgid "" "You have been kicked for using a word\n" " prohibited in the presence of this bot. Please use more appropriate\n" " language in the future." msgstr "" -#: config.py:122 +#: config.py:125 msgid "" "Determines the kick message used by the\n" " bot when kicking users for saying bad words." diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Limnoria-master-2022-03-19/plugins/BadWords/plugin.py new/Limnoria-master-2022-07-03/plugins/BadWords/plugin.py --- old/Limnoria-master-2022-03-19/plugins/BadWords/plugin.py 2022-03-17 22:29:10.000000000 +0100 +++ new/Limnoria-master-2022-07-03/plugins/BadWords/plugin.py 2022-06-23 22:31:17.000000000 +0200 @@ -104,9 +104,10 @@ self.lastModified = time.time() def outFilter(self, irc, msg): + channel = msg.channel if self.filtering and msg.command == 'PRIVMSG' \ - and (self.words() or self.phrases()): - channel = msg.channel + and (self.words() or self.phrases()) \ + and self.registryValue('selfCensor', channel, irc.network): self.updateRegexp(channel, irc.network) s = msg.args[1] if self.registryValue('stripFormatting'): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Limnoria-master-2022-03-19/plugins/Geography/plugin.py new/Limnoria-master-2022-07-03/plugins/Geography/plugin.py --- old/Limnoria-master-2022-03-19/plugins/Geography/plugin.py 2022-03-17 22:29:10.000000000 +0100 +++ new/Limnoria-master-2022-07-03/plugins/Geography/plugin.py 2022-06-23 22:31:17.000000000 +0200 @@ -110,6 +110,21 @@ _("Could not find the timezone of this location."), Raise=True ) + def _format_utc_offset(self, offset_seconds): + sign = "+" if offset_seconds >= 0 else "-" + + # Make modulos work as expected + offset_seconds = abs(offset_seconds) + + (offset_minutes, offset_seconds) = divmod(offset_seconds, 60) + (offset_hours, offset_minutes) = divmod(offset_minutes, 60) + offset = f"{offset_hours}:{offset_minutes:02}:{offset_seconds:02}" + + # hide seconds and minutes if they are zero + offset = re.sub("(:00)+$", "", offset) + + return f"UTC{sign}{offset}" + @wrap(["text"]) def timezone(self, irc, msg, args, query): """<location name to search> @@ -134,33 +149,28 @@ if timezone is None: continue - offset = str(datetime.datetime.now(tz=timezone).utcoffset()) - if not offset.startswith("-"): - offset = "+" + offset - - # hide seconds and minutes if they are zero - offset = re.sub("(:00)+$", "", offset) + offset_seconds = int( + datetime.datetime.now(tz=timezone).utcoffset().total_seconds()) + offset = self._format_utc_offset(offset_seconds) # Extract a human-friendly name, depending on the type of # the timezone object: if hasattr(timezone, "key"): # instance of zoneinfo.ZoneInfo - irc.reply(format("%s (currently UTC%s)", timezone.key, offset)) + irc.reply(format("%s (currently %s)", timezone.key, offset)) return elif hasattr(timezone, "zone"): # instance of pytz.timezone - irc.reply(format("%s (currently UTC%s)", timezone.zone, offset)) + irc.reply(format("%s (currently %s)", timezone.zone, offset)) return else: # probably datetime.timezone built from a constant offset try: - offset = timezone.utcoffset(now).seconds + offset = int(timezone.utcoffset(now).total_seconds()) except NotImplementedError: continue - hours = int(offset / 3600) - minutes = int(offset / 60 % 60) - irc.reply("UTC+%0.2i:%0.2i" % (hours, minutes)) + irc.reply(self._format_utc_offset(offset)) return irc.error( diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Limnoria-master-2022-03-19/plugins/Geography/test.py new/Limnoria-master-2022-07-03/plugins/Geography/test.py --- old/Limnoria-master-2022-03-19/plugins/Geography/test.py 2022-03-17 22:29:10.000000000 +0100 +++ new/Limnoria-master-2022-07-03/plugins/Geography/test.py 2022-06-23 22:31:17.000000000 +0200 @@ -68,41 +68,85 @@ @mock def testTimezonePytz(self): tz = pytz.timezone("Europe/Paris") - with patch.object(wikidata, "timezone_from_uri", return_value=tz): self.assertRegexp( "timezone Foo Bar", r"Europe/Paris \(currently UTC\+[12]\)" ) + tz = pytz.timezone("America/New_York") + with patch.object(wikidata, "timezone_from_uri", return_value=tz): + self.assertRegexp( + "timezone New York", r"America/New_York \(currently UTC-[45]\)" + ) + + tz = pytz.timezone("Canada/Newfoundland") + with patch.object(wikidata, "timezone_from_uri", return_value=tz): + self.assertRegexp( + "timezone Newfoundland", + r"Canada/Newfoundland \(currently UTC-[23]:30\)" + ) + + tz = pytz.timezone("Asia/Kolkata") + with patch.object(wikidata, "timezone_from_uri", return_value=tz): + self.assertRegexp( + "timezone Delhi", r"Asia/Kolkata \(currently UTC\+5:30\)" + ) + @skipIf(not zoneinfo, "Python is older than 3.9") @mock def testTimezoneZoneinfo(self): tz = zoneinfo.ZoneInfo("Europe/Paris") - with patch.object(wikidata, "timezone_from_uri", return_value=tz): self.assertRegexp( "timezone Foo Bar", r"Europe/Paris \(currently UTC\+[12]\)" ) + tz = zoneinfo.ZoneInfo("America/New_York") + with patch.object(wikidata, "timezone_from_uri", return_value=tz): + self.assertRegexp( + "timezone New York", r"America/New_York \(currently UTC-[45]\)" + ) + + tz = zoneinfo.ZoneInfo("Canada/Newfoundland") + with patch.object(wikidata, "timezone_from_uri", return_value=tz): + self.assertRegexp( + "timezone Newfoundland", + r"Canada/Newfoundland \(currently UTC-[23]:30\)" + ) + + tz = zoneinfo.ZoneInfo("Asia/Kolkata") + with patch.object(wikidata, "timezone_from_uri", return_value=tz): + self.assertRegexp( + "timezone Delhi", r"Asia/Kolkata \(currently UTC\+5:30\)" + ) + @skipIf(not zoneinfo, "Python is older than 3.9") @mock def testTimezoneAbsolute(self): tz = datetime.timezone(datetime.timedelta(hours=4)) - with patch.object(wikidata, "timezone_from_uri", return_value=tz): - self.assertResponse("timezone Foo Bar", "UTC+04:00") + self.assertResponse("timezone Foo Bar", "UTC+4") tz = datetime.timezone(datetime.timedelta(hours=4, minutes=30)) + with patch.object(wikidata, "timezone_from_uri", return_value=tz): + self.assertResponse("timezone Foo Bar", "UTC+4:30") + tz = datetime.timezone(datetime.timedelta(hours=-4, minutes=30)) with patch.object(wikidata, "timezone_from_uri", return_value=tz): - self.assertResponse("timezone Foo Bar", "UTC+04:30") + self.assertResponse("timezone Foo Bar", "UTC-3:30") @skipIf(not network, "Network test") def testTimezoneIntegration(self): self.assertRegexp( "timezone Metz, France", r"Europe/Paris \(currently UTC\+[12]\)" ) - self.assertResponse("timezone Saint-Denis, La R??union", "UTC+04:00") + self.assertResponse("timezone Saint-Denis, La R??union", "UTC+4") + self.assertRegexp( + "timezone Delhi", r"Asia/Kolkata \(currently UTC\+5:30\)" + ) + self.assertRegexp( + "timezone Newfoundland", r"UTC-[23]:30" + ) class GeographyLocaltimeTestCase(PluginTestCase): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Limnoria-master-2022-03-19/plugins/PluginDownloader/plugin.py new/Limnoria-master-2022-07-03/plugins/PluginDownloader/plugin.py --- old/Limnoria-master-2022-03-19/plugins/PluginDownloader/plugin.py 2022-03-17 22:29:10.000000000 +0100 +++ new/Limnoria-master-2022-07-03/plugins/PluginDownloader/plugin.py 2022-06-23 22:31:17.000000000 +0200 @@ -121,11 +121,11 @@ assert directory is not None, \ 'No valid directory in supybot.directories.plugins.' + possibly_incompatible = False try: assert archive.getmember(prefix + dirname).isdir(), \ 'This is not a valid plugin (it is a file, not a directory).' - run_2to3 = minisix.PY3 for file in archive.getmembers(): if file.name.startswith(prefix + dirname): extractedFile = archive.extractfile(file) @@ -140,42 +140,18 @@ os.mkdir(newFileName) else: with open(newFileName, 'ab') as fd: - reload_imported = False for line in extractedFile.readlines(): - if minisix.PY3: - if b'import reload' in line: - reload_imported = True - elif not reload_imported and \ - b'reload(' in line: - fd.write(b'from importlib import reload\n') - reload_imported = True + if file.name.endswith('__init__.py') and \ + line.startswith((b'import config', b'import plugin')): + possibly_incompatible = True fd.write(line) - if newFileName.endswith('__init__.py'): - with open(newFileName) as fd: - lines = list(filter(lambda x:'import plugin' in x, - fd.readlines())) - if lines and lines[0].startswith('from . import'): - # This should be already Python 3-compatible - run_2to3 = False finally: archive.close() del archive - if run_2to3: - try: - import lib2to3 - except ImportError: - return _('Plugin is probably not compatible with your ' - 'Python version (3.x) and could not be converted ' - 'because 2to3 is not installed.') - import subprocess - fixers = [] - subprocess.Popen(['2to3', '-wn', os.path.join(directory, plugin)]) \ - .wait() - return _('Plugin was designed for Python 2, but an attempt to ' - 'convert it to Python 3 has been made. There is no ' - 'guarantee it will work, though.') - else: - return _('Plugin successfully installed.') + if possibly_incompatible: + return _('Plugin installed. However, it may be incompatible with ' + 'Python 3 and require manual code changes to work correctly.') + return _('Plugin successfully installed.') def getInfo(self, plugin): archive = self._download(plugin) @@ -201,65 +177,11 @@ 'progval', 'Supybot-plugins' ), - 'quantumlemur': GithubRepository( - 'quantumlemur', - 'Supybot-plugins', - ), - 'stepnem': GithubRepository( - 'stepnem', - 'supybot-plugins', - ), - 'code4lib-snapshot':GithubRepository( - 'code4lib', - 'supybot-plugins', - 'Supybot-plugins-20060723', - ), - 'code4lib-edsu': GithubRepository( - 'code4lib', - 'supybot-plugins', - 'edsu-plugins', - ), - 'code4lib': GithubRepository( - 'code4lib', - 'supybot-plugins', - 'plugins', - ), - 'nanotube-bitcoin': GithubRepository( - 'nanotube', - 'supybot-bitcoin-' - 'marketmonitor', - ), - 'mtughan-weather': GithubRepository( - 'mtughan', - 'Supybot-Weather', - ), 'SpiderDave': GithubRepository( 'SpiderDave', 'spidey-supybot-plugins', 'Plugins', ), - 'doorbot': GithubRepository( - 'hacklab', - 'doorbot', - ), - 'boombot': GithubRepository( - 'nod', - 'boombot', - 'plugins', - ), - 'mailed-notifier': GithubRepository( - 'tbielawa', - 'supybot-mailed-notifier', - ), - 'pingdom': GithubRepository( - 'rynop', - 'supyPingdom', - 'plugins', - ), - 'scrum': GithubRepository( - 'amscanne', - 'supybot-scrum', - ), 'Hoaas': GithubRepository( 'Hoaas', 'Supybot-plugins' @@ -268,23 +190,11 @@ 'nyuszika7h', 'limnoria-plugins' ), - 'nyuszika7h-old': GithubRepository( - 'nyuszika7h', - 'Supybot-plugins' - ), - 'resistivecorpse': GithubRepository( - 'resistivecorpse', - 'supybot-plugins' - ), 'frumious': GithubRepository( 'frumiousbandersnatch', 'sobrieti-plugins', 'plugins', ), - 'jonimoose': GithubRepository( - 'Jonimoose', - 'Supybot-plugins', - ), 'skgsergio': GithubRepository( 'skgsergio', 'Limnoria-plugins', diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Limnoria-master-2022-03-19/plugins/PluginDownloader/test.py new/Limnoria-master-2022-07-03/plugins/PluginDownloader/test.py --- old/Limnoria-master-2022-03-19/plugins/PluginDownloader/test.py 2022-03-17 22:29:10.000000000 +0100 +++ new/Limnoria-master-2022-07-03/plugins/PluginDownloader/test.py 2022-06-23 22:31:17.000000000 +0200 @@ -29,11 +29,9 @@ ### import os -import sys import shutil from supybot.test import * -import supybot.utils.minisix as minisix pluginsPath = '%s/test-plugins' % os.getcwd() @@ -62,7 +60,7 @@ def testRepolist(self): self.assertRegexp('repolist', '(.*, )?progval(, .*)?') - self.assertRegexp('repolist', '(.*, )?quantumlemur(, .*)?') + self.assertRegexp('repolist', '(.*, )?jlu5(, .*)?') self.assertRegexp('repolist progval', '(.*, )?AttackProtector(, .*)?') def testInstallprogval(self): @@ -76,44 +74,19 @@ self.assertRegexp('plugindownloader install progval Darcs', 'Error:.*not available.*supybot.commands.allowShell') - def testInstallQuantumlemur(self): - self.assertError('plugindownloader install quantumlemur AttackProtector') - self.assertNotError('plugindownloader install quantumlemur Listener') - self.assertError('plugindownloader install quantumlemur AttackProtector') - self._testPluginInstalled('Listener') - - def testInstallStepnem(self): - self.assertNotError('plugindownloader install stepnem Freenode') - self._testPluginInstalled('Freenode') - - def testInstallNanotubeBitcoin(self): - self.assertNotError('plugindownloader install nanotube-bitcoin GPG') - self._testPluginInstalled('GPG') - - def testInstallMtughanWeather(self): - self.assertNotError('plugindownloader install mtughan-weather ' - 'WunderWeather') - self._testPluginInstalled('WunderWeather') - - def testInstallSpiderDave(self): - self.assertNotError('plugindownloader install SpiderDave Pastebin') - self._testPluginInstalled('Pastebin') - def testInstallNonAsciiInit(self): self.assertNotError('plugindownloader install Hoaas DuckDuckGo') self._testPluginInstalled('DuckDuckGo') + def testInstallLegacyWarning(self): + self.assertRegexp('plugindownloader install frumious Codepoints', + 'may be incompatible') + def testInfo(self): self.assertResponse('plugindownloader info progval Twitter', 'Advanced Twitter plugin for Supybot, with capabilities ' 'handling, and per-channel user account.') - if minisix.PY3: - def test_2to3(self): - self.assertRegexp('plugindownloader install SpiderDave Pastebin', - 'convert') - self.assertNotError('load Pastebin') - if not network: class PluginDownloaderTestCase(PluginTestCase): pass diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Limnoria-master-2022-03-19/plugins/Time/plugin.py new/Limnoria-master-2022-07-03/plugins/Time/plugin.py --- old/Limnoria-master-2022-03-19/plugins/Time/plugin.py 2022-03-17 22:29:10.000000000 +0100 +++ new/Limnoria-master-2022-07-03/plugins/Time/plugin.py 2022-06-23 22:31:17.000000000 +0200 @@ -28,10 +28,11 @@ # POSSIBILITY OF SUCH DAMAGE. ### +import re import sys import time TIME = time # For later use. -from datetime import datetime +from datetime import datetime, timedelta, timezone import supybot.conf as conf import supybot.log as log @@ -194,25 +195,35 @@ elapsed = wrap(elapsed, ['int']) @internationalizeDocstring - def tztime(self, irc, msg, args, timezone): + def tztime(self, irc, msg, args, tz): """<region>/<city> (or <region>/<state>/<city>) Takes a city and its region, and returns its local time. This command uses the IANA Time Zone Database.""" - try: - timezone = utils.time.iana_timezone(timezone) - except utils.time.UnknownTimeZone: - irc.error(_('Unknown timezone')) - except utils.time.MissingTimezoneLibrary: - irc.error(_( - 'Timezone-related commands are not available. ' - 'Your administrator need to either upgrade Python to ' - 'version 3.9 or greater, or install pytz.')) - except utils.time.TimezoneException as e: - irc.error(e.args[0]) + parsed_utc_tz = re.match( + "UTC(?P<hours>[-+][0-9]+)(:(?P<minutes>[0-6][0-9]))?", tz) + if parsed_utc_tz: + groups = parsed_utc_tz.groupdict() + tz = timezone(timedelta( + hours=int(groups["hours"]), + minutes=int(groups["minutes"] or "00") + )) else: - format = self.registryValue("format", msg.channel, irc.network) - irc.reply(datetime.now(timezone).strftime(format)) + try: + tz = utils.time.iana_timezone(tz) + except utils.time.UnknownTimeZone: + irc.error(_('Unknown timezone'), Raise=True) + except utils.time.MissingTimezoneLibrary: + irc.error(_( + 'Timezone-related commands are not available. ' + 'Your administrator need to either upgrade Python to ' + 'version 3.9 or greater, or install pytz.'), + Raise=True) + except utils.time.TimezoneException as e: + irc.error(e.args[0], Raise=True) + + format = self.registryValue("format", msg.channel, irc.network) + irc.reply(datetime.now(tz).strftime(format)) tztime = wrap(tztime, ['text']) def ddate(self, irc, msg, args, year=None, month=None, day=None): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Limnoria-master-2022-03-19/plugins/Time/test.py new/Limnoria-master-2022-07-03/plugins/Time/test.py --- old/Limnoria-master-2022-03-19/plugins/Time/test.py 2022-03-17 22:29:10.000000000 +0100 +++ new/Limnoria-master-2022-07-03/plugins/Time/test.py 2022-06-23 22:31:17.000000000 +0200 @@ -97,6 +97,8 @@ self.assertNotError('tztime Europe/Paris') self.assertNotError('tztime America/Indiana/Knox') self.assertNotError('tztime UTC') + self.assertNotError('tztime UTC+10') + self.assertNotError('tztime UTC+5:30') self.assertError('tztime Europe/Gniarf') @skipIf(not has_dateutil, 'python-dateutil is missing') diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Limnoria-master-2022-03-19/plugins/Web/plugin.py new/Limnoria-master-2022-07-03/plugins/Web/plugin.py --- old/Limnoria-master-2022-03-19/plugins/Web/plugin.py 2022-03-17 22:29:10.000000000 +0100 +++ new/Limnoria-master-2022-07-03/plugins/Web/plugin.py 2022-06-23 22:31:17.000000000 +0200 @@ -151,7 +151,8 @@ size = conf.supybot.protocols.http.peekSize() parsed_url = utils.web.urlparse(url) - if parsed_url.netloc.endswith(('youtube.com', '.youtube.com')): + if parsed_url.netloc == 'youtube.com' \ + or parsed_url.netloc.endswith(('.youtube.com')): # there is a lot of Javascript before the <title> size = 409600 if parsed_url.netloc in ('reddit.com', 'www.reddit.com', 'new.reddit.com'): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Limnoria-master-2022-03-19/scripts/supybot-plugin-create new/Limnoria-master-2022-07-03/scripts/supybot-plugin-create --- old/Limnoria-master-2022-03-19/scripts/supybot-plugin-create 2022-03-17 22:29:10.000000000 +0100 +++ new/Limnoria-master-2022-07-03/scripts/supybot-plugin-create 2022-06-23 22:31:17.000000000 +0200 @@ -92,13 +92,10 @@ from supybot import utils, plugins, ircutils, callbacks from supybot.commands import * -try: - from supybot.i18n import PluginInternationalization - _ = PluginInternationalization('%s') -except ImportError: - # Placeholder that allows to run the plugin on a bot - # without the i18n module - _ = lambda x: x +from supybot.i18n import PluginInternationalization + + +_ = PluginInternationalization('%s') class %s(callbacks.Plugin): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Limnoria-master-2022-03-19/scripts/supybot-reset-password new/Limnoria-master-2022-07-03/scripts/supybot-reset-password --- old/Limnoria-master-2022-03-19/scripts/supybot-reset-password 2022-03-17 22:29:10.000000000 +0100 +++ new/Limnoria-master-2022-07-03/scripts/supybot-reset-password 2022-06-23 22:31:17.000000000 +0200 @@ -51,7 +51,7 @@ help='username for the user.') parser.add_option('-p', '--password', action='store', default='', dest='password', - help='password for the user.') + help='new password for the user.') (options, args) = parser.parse_args() if len(args) != 1: parser.error('Specify the users.conf file you\'d like to use. ' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Limnoria-master-2022-03-19/setup.py new/Limnoria-master-2022-07-03/setup.py --- old/Limnoria-master-2022-03-19/setup.py 2022-03-17 22:29:10.000000000 +0100 +++ new/Limnoria-master-2022-07-03/setup.py 2022-06-23 22:31:17.000000000 +0200 @@ -216,6 +216,7 @@ ('share/man/man1', ['man/supybot-botchk.1']), ('share/man/man1', ['man/supybot-wizard.1']), ('share/man/man1', ['man/supybot-adduser.1']), + ('share/man/man1', ['man/supybot-reset-password.1']), ('share/man/man1', ['man/supybot-plugin-doc.1']), ('share/man/man1', ['man/supybot-plugin-create.1']), ], diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Limnoria-master-2022-03-19/src/__init__.py new/Limnoria-master-2022-07-03/src/__init__.py --- old/Limnoria-master-2022-03-19/src/__init__.py 2022-03-17 22:29:10.000000000 +0100 +++ new/Limnoria-master-2022-07-03/src/__init__.py 2022-06-23 22:31:17.000000000 +0200 @@ -33,10 +33,7 @@ from . import i18n -builtins = (__builtins__ if isinstance(__builtins__, dict) else __builtins__.__dict__) -builtins['supybotInternationalization'] = i18n.PluginInternationalization() from . import utils -del builtins['supybotInternationalization'] (__builtins__ if isinstance(__builtins__, dict) else __builtins__.__dict__)['format'] = utils.str.format diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Limnoria-master-2022-03-19/src/callbacks.py new/Limnoria-master-2022-07-03/src/callbacks.py --- old/Limnoria-master-2022-03-19/src/callbacks.py 2022-03-17 22:29:10.000000000 +0100 +++ new/Limnoria-master-2022-07-03/src/callbacks.py 2022-06-23 22:31:17.000000000 +0200 @@ -256,6 +256,10 @@ # no harm in doing this extra check, in case a plugin is replying # across network (as it may happen with '@network command'). ret.server_tags['+draft/reply'] = msg.server_tags['msgid'] + if msg.channel and not irc.isChannel(ret.args[0]): + # If replying in non-channel to a channel message, use the tag + # defined in https://github.com/ircv3/ircv3-specifications/pull/498 + ret.server_tags["+draft/channel-context"] = msg.channel return ret def error(*args, **kwargs): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Limnoria-master-2022-03-19/src/conf.py new/Limnoria-master-2022-07-03/src/conf.py --- old/Limnoria-master-2022-03-19/src/conf.py 2022-03-17 22:29:10.000000000 +0100 +++ new/Limnoria-master-2022-07-03/src/conf.py 2022-06-23 22:31:17.000000000 +0200 @@ -911,6 +911,10 @@ ValidDriverModule('default', _("""Determines what driver module the bot will use. Current, the only (and default) driver is Socket."""))) +registerGlobalValue(supybot.drivers, 'minReconnectWait', + registry.PositiveFloat(10.0, _("""Determines the minimum time the bot will + wait before attempting to reconnect to an IRC server."""))) + registerGlobalValue(supybot.drivers, 'maxReconnectWait', registry.PositiveFloat(300.0, _("""Determines the maximum time the bot will wait before attempting to reconnect to an IRC server. The bot may, of diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Limnoria-master-2022-03-19/src/drivers/Socket.py new/Limnoria-master-2022-07-03/src/drivers/Socket.py --- old/Limnoria-master-2022-03-19/src/drivers/Socket.py 2022-03-17 22:29:10.000000000 +0100 +++ new/Limnoria-master-2022-07-03/src/drivers/Socket.py 2022-06-23 22:31:17.000000000 +0200 @@ -92,7 +92,7 @@ return ret def resetDelay(self): - self.currentDelay = 10.0 + self.currentDelay = conf.supybot.drivers.minReconnectWait() def _getNextServer(self): oldServer = getattr(self, 'currentServer', None) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Limnoria-master-2022-03-19/src/i18n.py new/Limnoria-master-2022-07-03/src/i18n.py --- old/Limnoria-master-2022-03-19/src/i18n.py 2022-03-17 22:29:10.000000000 +0100 +++ new/Limnoria-master-2022-07-03/src/i18n.py 2022-06-23 22:31:17.000000000 +0200 @@ -43,9 +43,11 @@ IN_MSGID = 2 WAITING_FOR_MSGSTR = 3 IN_MSGSTR = 4 +NEXT_IS_FUZZY = 5 MSGID = 'msgid "' MSGSTR = 'msgstr "' +FUZZY = '#, fuzzy' currentLocale = 'en' @@ -68,10 +70,12 @@ """Imports the conf into this module""" global conf conf = __import__('supybot.conf').conf + class Languages(conf.registry.OnlySomeStrings): + validStrings = ['de', 'en', 'es', 'fi', 'fr', 'it'] conf.registerGlobalValue(conf.supybot, 'language', - conf.registry.String(currentLocale, """Determines the bot's default - language if translations exist. Currently supported are 'de', 'en', - 'es', 'fi', 'fr' and 'it'.""")) + Languages(currentLocale, """Determines the bot's default + language if translations exist. Currently supported are: %s""" + % ', '.join(Languages.validStrings))) conf.supybot.language.addCallback(reloadLocalesIfRequired) def getPluginDir(plugin_name): @@ -111,7 +115,6 @@ i18nClasses = weakref.WeakValueDictionary() internationalizedCommands = weakref.WeakValueDictionary() -internationalizedFunctions = [] # No need to know their name def reloadLocalesIfRequired(): global currentLocale @@ -122,12 +125,13 @@ reloadLocales() def reloadLocales(): + import supybot.utils as utils + for pluginClass in i18nClasses.values(): pluginClass.loadLocale() for command in list(internationalizedCommands.values()): internationalizeDocstring(command) - for function in internationalizedFunctions: - function.loadLocale() + utils.str._relocalizeFunctions(PluginInternationalization()) def normalize(string, removeNewline=False): import supybot.utils as utils @@ -148,7 +152,13 @@ line = line[0:-1] # Remove the ending \n line = line - if line.startswith(MSGID): + if step == WAITING_FOR_MSGID and line.startswith(FUZZY): + step = NEXT_IS_FUZZY + elif step == NEXT_IS_FUZZY and line.startswith(MSGID): + # Don't use fuzzy strings; they may have a mismatched number of %s or be + # outright wrong; use English instead. + step = WAITING_FOR_MSGID + elif line.startswith(MSGID): # Don't check if step is WAITING_FOR_MSGID untranslated = '' translated = '' @@ -193,7 +203,6 @@ return translations -i18nSupybot = None def PluginInternationalization(name='supybot'): # This is a proxy that prevents having several objects for the same plugin if name in i18nClasses: @@ -318,40 +327,6 @@ name in self._l10nFunctions: return self._l10nFunctions[name] - def internationalizeFunction(self, name): - """Decorates functions and internationalize their code. - - Only useful for Supybot core functions""" - if self.name != 'supybot': - return - class FunctionInternationalizer: - def __init__(self, parent, name): - self._parent = parent - self._name = name - def __call__(self, obj): - obj = InternationalizedFunction(self._parent, self._name, obj) - obj.loadLocale() - return obj - return FunctionInternationalizer(self, name) - -class InternationalizedFunction: - """Proxy for functions that need to be fully localized. - - The localization code is in locales/LOCALE.py""" - def __init__(self, internationalizer, name, function): - self._internationalizer = internationalizer - self._name = name - self._origin = function - internationalizedFunctions.append(self) - def loadLocale(self): - self.__call__ = self._internationalizer.localizeFunction(self._name) - if self.__call__ == None: - self.restore() - def restore(self): - self.__call__ = self._origin - - def __call__(self, *args, **kwargs): - return self._origin(*args, **kwargs) try: class InternationalizedString(str): @@ -367,6 +342,7 @@ know if a string is already localized""" pass + def internationalizeDocstring(obj): """Decorates functions and internationalize their docstring. @@ -383,3 +359,13 @@ # attribute '__doc__' of 'type' objects is not writable pass return obj + + +def _install(): + from . import utils + _ = PluginInternationalization() + utils.gen._ = _ + utils.str._ = _ + utils.str._relocalizeFunctions(_) + +_install() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Limnoria-master-2022-03-19/src/irclib.py new/Limnoria-master-2022-07-03/src/irclib.py --- old/Limnoria-master-2022-03-19/src/irclib.py 2022-03-17 22:29:10.000000000 +0100 +++ new/Limnoria-master-2022-07-03/src/irclib.py 2022-06-23 22:31:17.000000000 +0200 @@ -1809,9 +1809,13 @@ `msg` is the message that triggered this call.""" self.state.fsm.expect_state([ - # Normal CAP ACK / CAP NAK during cap negotiation + # Normal CAP ACK / CAP NAK during cap negotiation: IrcStateFsm.States.INIT_CAP_NEGOTIATION, - # CAP ACK / CAP NAK after a CAP NEW (probably) + # Sigyn sends CAP REQ when it sees RPL_SASLSUCCESS, so we get the + # CAP ACK while waiting for MOTD on some IRCds (eg. InspIRCd): + IrcStateFsm.States.INIT_WAITING_MOTD, + IrcStateFsm.States.INIT_MOTD, + # CAP ACK / CAP NAK after a CAP NEW (probably): IrcStateFsm.States.CONNECTED, ]) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Limnoria-master-2022-03-19/src/setup.py new/Limnoria-master-2022-07-03/src/setup.py --- old/Limnoria-master-2022-03-19/src/setup.py 2022-03-17 22:29:10.000000000 +0100 +++ new/Limnoria-master-2022-07-03/src/setup.py 2022-06-23 22:31:17.000000000 +0200 @@ -79,7 +79,14 @@ break module_name = kwargs['name'].replace('-', '_') - kwargs.setdefault('packages', [module_name]) + + if 'packages' not in kwargs: + kwargs["packages"] = [module_name] + [ + "%s.%s" % (module_name, package_name.replace('-', '_')) + for package_name + in setuptools.find_packages(where=".") + ] + kwargs.setdefault('package_dir', {module_name: '.'}) kwargs.setdefault('entry_points', { 'limnoria.plugins': '%s = %s' % (capitalized_name, module_name)}) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Limnoria-master-2022-03-19/src/utils/__init__.py new/Limnoria-master-2022-07-03/src/utils/__init__.py --- old/Limnoria-master-2022-03-19/src/utils/__init__.py 2022-03-17 22:29:10.000000000 +0100 +++ new/Limnoria-master-2022-07-03/src/utils/__init__.py 2022-06-23 22:31:17.000000000 +0200 @@ -50,6 +50,7 @@ builtins = (__builtins__ if isinstance(__builtins__, dict) else __builtins__.__dict__) + # We use this often enough that we're going to stick it in builtins. def force(x): if callable(x): @@ -58,8 +59,6 @@ return x builtins['force'] = force -internationalization = builtins.get('supybotInternationalization', None) - # These imports need to happen below the block above, so things get put into # __builtins__ appropriately. from .gen import * diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Limnoria-master-2022-03-19/src/utils/gen.py new/Limnoria-master-2022-07-03/src/utils/gen.py --- old/Limnoria-master-2022-03-19/src/utils/gen.py 2022-03-17 22:29:10.000000000 +0100 +++ new/Limnoria-master-2022-07-03/src/utils/gen.py 2022-06-23 22:31:17.000000000 +0200 @@ -45,7 +45,9 @@ from .str import format from .file import mktemp from . import minisix -from . import internationalization as _ + +# will be replaced by supybot.i18n.install() +_ = lambda x: x def warn_non_constant_time(f): @functools.wraps(f) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Limnoria-master-2022-03-19/src/utils/str.py new/Limnoria-master-2022-07-03/src/utils/str.py --- old/Limnoria-master-2022-03-19/src/utils/str.py 2022-03-17 22:29:10.000000000 +0100 +++ new/Limnoria-master-2022-07-03/src/utils/str.py 2022-06-23 22:31:17.000000000 +0200 @@ -38,20 +38,46 @@ import time import string import textwrap +import functools from . import minisix from .iter import any from .structures import TwoWayDictionary -from . import internationalization as _ -internationalizeFunction = _.internationalizeFunction - try: from charade.universaldetector import UniversalDetector charadeLoaded = True except ImportError: charadeLoaded = False +# will be replaced by supybot.i18n.install() +_ = lambda x: x + +# used by supybot.i18n.reloadLocales() to (re)load the localized function of +# these functions +_localizedFunctions = {} +_defaultFunctions = {} + + +def internationalizeFunction(f): + f_name = f.__name__ + _localizedFunctions[f_name] = f + _defaultFunctions[f_name] = f + + @functools.wraps(f) + def newf(*args, **kwargs): + f = _localizedFunctions[f_name] + assert f is not None, "_localizedFunctions[%s] is None" % f_name + return f(*args, **kwargs) + + return newf + + +def _relocalizeFunctions(localizer): + for f_name in list(_localizedFunctions): + f = localizer.localizeFunction(f_name) or _defaultFunctions[f_name] + _localizedFunctions[f_name] = f + if minisix.PY3: def decode_raw_line(line): #first, try to decode using utf-8 @@ -390,7 +416,7 @@ L[i] = L[i].upper() return ''.join(L) -@internationalizeFunction('pluralize') +@internationalizeFunction def pluralize(s): """Returns the plural of s. Put any exceptions to the general English rule of appending 's' in the plurals dictionary. @@ -413,7 +439,7 @@ else: return matchCase(s, s+'s') -@internationalizeFunction('depluralize') +@internationalizeFunction def depluralize(s): """Returns the singular of s.""" consonants = 'bcdfghjklmnpqrstvwxz' @@ -467,7 +493,7 @@ else: return format('%s %s %s', n, between, item) -@internationalizeFunction('ordinal') +@internationalizeFunction def ordinal(i): """Returns i + the ordinal indicator for the number. @@ -486,7 +512,7 @@ ord = 'rd' return '%s%s' % (i, ord) -@internationalizeFunction('be') +@internationalizeFunction def be(i): """Returns the form of the verb 'to be' based on the number i.""" if i == 1: @@ -494,7 +520,7 @@ else: return 'are' -@internationalizeFunction('has') +@internationalizeFunction def has(i): """Returns the form of the verb 'to have' based on the number i.""" if i == 1: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Limnoria-master-2022-03-19/test/test_callbacks.py new/Limnoria-master-2022-07-03/test/test_callbacks.py --- old/Limnoria-master-2022-03-19/test/test_callbacks.py 2022-03-17 22:29:10.000000000 +0100 +++ new/Limnoria-master-2022-07-03/test/test_callbacks.py 2022-06-23 22:31:17.000000000 +0200 @@ -634,6 +634,51 @@ finally: self.irc.state.capabilities_ack.remove('message-tags') + def testClientTagReplyChannel(self): + self.irc.addCallback(self.First(self.irc)) + + try: + conf.supybot.protocols.irc.experimentalExtensions.setValue(True) + self.irc.state.capabilities_ack.add('message-tags') + + # Reply in channel to channel message -> +draft/channel-context + # is absent + self.irc.feedMsg(ircmsgs.IrcMsg( + command='PRIVMSG', prefix=self.prefix, + args=('#foo', '%s: firstcmd' % self.nick), + server_tags={'msgid': 'foobar'})) + msg = self.irc.takeMsg() + self.assertEqual(msg, ircmsgs.IrcMsg( + command='PRIVMSG', args=('#foo', '%s: foo' % self.nick), + server_tags={'+draft/reply': 'foobar'})) + + # Reply in private to channel message -> +draft/channel-context + # is present + with conf.supybot.reply.inPrivate.context(True): + self.irc.feedMsg(ircmsgs.IrcMsg( + command='PRIVMSG', prefix=self.prefix, + args=('#foo', '%s: firstcmd' % self.nick), + server_tags={'msgid': 'foobar'})) + msg = self.irc.takeMsg() + self.assertEqual(msg, ircmsgs.IrcMsg( + command='NOTICE', args=(self.nick, 'foo'), + server_tags={'+draft/reply': 'foobar', + '+draft/channel-context': '#foo'})) + + # Reply in private to private message -> +draft/channel-context + # is absent + self.irc.feedMsg(ircmsgs.IrcMsg( + command='PRIVMSG', prefix=self.prefix, + args=(self.nick, 'firstcmd'), + server_tags={'msgid': 'foobar'})) + msg = self.irc.takeMsg() + self.assertEqual(msg, ircmsgs.IrcMsg( + command='NOTICE', args=(self.nick, 'foo'), + server_tags={'+draft/reply': 'foobar'})) + finally: + conf.supybot.protocols.irc.experimentalExtensions.setValue(False) + self.irc.state.capabilities_ack.remove('message-tags') + class TwoRepliesFirstAction(callbacks.Plugin): def testactionreply(self, irc, msg, args): irc.reply('foo', action=True)