Hello community,
here is the log from the commit of package python-limnoria for openSUSE:Factory
checked in at 2020-10-15 13:50:36
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-limnoria (Old)
and /work/SRC/openSUSE:Factory/.python-limnoria.new.3486 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-limnoria"
Thu Oct 15 13:50:36 2020 rev:18 rq:841776 version:2020.10.13
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-limnoria/python-limnoria.changes
2020-09-06 00:03:00.871272231 +0200
+++
/work/SRC/openSUSE:Factory/.python-limnoria.new.3486/python-limnoria.changes
2020-10-15 13:50:58.221313675 +0200
@@ -1,0 +2,8 @@
+Wed Oct 14 11:06:32 UTC 2020 - Atri Bhattacharya <[email protected]>
+
+- Update to version 2020-10-13:
+ * RSS: Fix announce removal to work with net+chan-specific
+ config.
+ * It only removed the value from the chan-specific value.
+
+-------------------------------------------------------------------
Old:
----
limnoria-2020.09.03.tar.gz
New:
----
limnoria-2020.10.13.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-limnoria.spec ++++++
--- /var/tmp/diff_new_pack.9GgyXH/_old 2020-10-15 13:50:59.013314005 +0200
+++ /var/tmp/diff_new_pack.9GgyXH/_new 2020-10-15 13:50:59.017314007 +0200
@@ -18,9 +18,9 @@
%define skip_python2 1
%define appname limnoria
-%define srcver 2020-09-03
+%define srcver 2020-10-13
Name: python-limnoria
-Version: 2020.09.03
+Version: 2020.10.13
Release: 0
Summary: A modified version of Supybot (an IRC bot and framework)
License: BSD-3-Clause
++++++ limnoria-2020.09.03.tar.gz -> limnoria-2020.10.13.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Limnoria-master-2020-09-03/.travis.yml
new/Limnoria-master-2020-10-13/.travis.yml
--- old/Limnoria-master-2020-09-03/.travis.yml 2020-08-30 14:52:45.000000000
+0200
+++ new/Limnoria-master-2020-10-13/.travis.yml 2020-10-10 11:51:56.000000000
+0200
@@ -15,10 +15,10 @@
matrix:
include:
- python: "3.4"
- env: WITH_OPT_DEPS=true
+ env: WITH_OPT_DEPS=false
dist: trusty
- python: "3.5"
- env: WITH_OPT_DEPS=true
+ env: WITH_OPT_DEPS=false
dist: trusty
- python: "3.6"
env: WITH_OPT_DEPS=true
@@ -44,7 +44,7 @@
env: WITH_OPT_DEPS=true
dist: xenial
- allow_failures:
+# allow_failures:
- python: "nightly"
env: WITH_OPT_DEPS=true
dist: xenial
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/Limnoria-master-2020-09-03/plugins/Autocomplete/README.md
new/Limnoria-master-2020-10-13/plugins/Autocomplete/README.md
--- old/Limnoria-master-2020-09-03/plugins/Autocomplete/README.md
2020-08-30 14:52:45.000000000 +0200
+++ new/Limnoria-master-2020-10-13/plugins/Autocomplete/README.md
2020-10-10 11:51:56.000000000 +0200
@@ -6,4 +6,4 @@
False unless you know what you are doing).
If you are interested in this feature, please contribute to
-[the discussion](https://github.com/ircv3/ircv3-specifications/pull/415>)
+[the discussion](https://github.com/ircv3/ircv3-specifications/pull/415)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/Limnoria-master-2020-09-03/plugins/BadWords/config.py
new/Limnoria-master-2020-10-13/plugins/BadWords/config.py
--- old/Limnoria-master-2020-09-03/plugins/BadWords/config.py 2020-08-30
14:52:45.000000000 +0200
+++ new/Limnoria-master-2020-10-13/plugins/BadWords/config.py 2020-10-10
11:51:56.000000000 +0200
@@ -44,15 +44,27 @@
'spaces)'))
conf.supybot.plugins.BadWords.words.set(words)
-class LastModifiedSetOfStrings(registry.SpaceSeparatedSetOfStrings):
+class
LastModifiedSpaceSeparatedSetOfStrings(registry.SpaceSeparatedSetOfStrings):
lastModified = 0
def setValue(self, v):
self.lastModified = time.time()
registry.SpaceSeparatedListOfStrings.setValue(self, v)
+class
LastModifiedCommaSeparatedSetOfStrings(registry.CommaSeparatedSetOfStrings):
+ lastModified = 0
+ def set(self, v):
+ if not v.strip():
+ self.setValue(set())
+ else:
+ super().set(v)
+
+ def setValue(self, v):
+ self.lastModified = time.time()
+ registry.CommaSeparatedListOfStrings.setValue(self, v)
+
BadWords = conf.registerPlugin('BadWords')
conf.registerGlobalValue(BadWords, 'words',
- LastModifiedSetOfStrings([], _("""Determines what words are
+ LastModifiedSpaceSeparatedSetOfStrings([], _("""Determines what words are
considered to be 'bad' so the bot won't say them.""")))
conf.registerChannelValue(BadWords,'requireWordBoundaries',
registry.Boolean(False, _("""Determines whether the bot will require bad
@@ -62,6 +74,9 @@
false. After changing this setting, the BadWords regexp needs to be
regenerated by adding/removing a word to the list, or reloading the
plugin.""")))
+conf.registerGlobalValue(BadWords, 'phrases',
+ LastModifiedCommaSeparatedSetOfStrings([], _("""Comma-separated groups
+ of words that are considered to be 'bad'.""")))
class String256(registry.String):
def __call__(self):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/Limnoria-master-2020-09-03/plugins/BadWords/plugin.py
new/Limnoria-master-2020-10-13/plugins/BadWords/plugin.py
--- old/Limnoria-master-2020-09-03/plugins/BadWords/plugin.py 2020-08-30
14:52:45.000000000 +0200
+++ new/Limnoria-master-2020-10-13/plugins/BadWords/plugin.py 2020-10-10
11:51:56.000000000 +0200
@@ -53,6 +53,7 @@
self.filtering = True
self.lastModified = 0
self.words = conf.supybot.plugins.BadWords.words
+ self.phrases = conf.supybot.plugins.BadWords.phrases
def callCommand(self, name, irc, msg, *args, **kwargs):
if ircdb.checkCapability(msg.prefix, 'admin'):
@@ -71,7 +72,7 @@
self.filtering = True
# We need to check for bad words here rather than in doPrivmsg because
# messages don't get to doPrivmsg if the user is ignored.
- if msg.command == 'PRIVMSG' and self.words():
+ if msg.command == 'PRIVMSG' and (self.words() or self.phrases()):
channel = msg.channel
self.updateRegexp(channel, irc.network)
s = ircutils.stripFormatting(msg.args[1])
@@ -96,12 +97,14 @@
return msg
def updateRegexp(self, channel, network):
- if self.lastModified < self.words.lastModified:
- self.makeRegexp(self.words(), channel, network)
+ if self.lastModified < self.words.lastModified \
+ or self.lastModified < self.phrases.lastModified:
+ self.makeRegexp(self.words() | self.phrases(), channel, network)
self.lastModified = time.time()
def outFilter(self, irc, msg):
- if self.filtering and msg.command == 'PRIVMSG' and self.words():
+ if self.filtering and msg.command == 'PRIVMSG' \
+ and (self.words() or self.phrases()):
channel = msg.channel
self.updateRegexp(channel, irc.network)
s = msg.args[1]
@@ -124,7 +127,7 @@
Returns the list of words being censored.
"""
- L = list(self.words())
+ L = list(self.words() | self.phrases())
if L:
self.filtering = False
utils.sortBy(str.lower, L)
@@ -134,27 +137,40 @@
list = wrap(list, ['admin'])
@internationalizeDocstring
- def add(self, irc, msg, args, words):
+ def add(self, irc, msg, args, new_words):
"""<word> [<word> ...]
Adds all <word>s to the list of words being censored.
"""
- set = self.words()
- set.update(words)
- self.words.setValue(set)
+ words = self.words()
+ phrases = self.phrases()
+ for word in new_words:
+ if ' ' in word:
+ phrases.add(word)
+ else:
+ words.add(word)
+
+ self.words.setValue(words)
+ self.phrases.setValue(phrases)
+
irc.replySuccess()
add = wrap(add, ['admin', many('something')])
@internationalizeDocstring
- def remove(self, irc, msg, args, words):
+ def remove(self, irc, msg, args, old_words):
"""<word> [<word> ...]
Removes <word>s from the list of words being censored.
"""
- set = self.words()
- for word in words:
- set.discard(word)
- self.words.setValue(set)
+ words = self.words()
+ phrases = self.phrases()
+ for word in old_words:
+ words.discard(word)
+ phrases.discard(word)
+ self.words.setValue(words)
+ self.phrases.setValue(phrases)
+
+
irc.replySuccess()
remove = wrap(remove, ['admin', many('something')])
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Limnoria-master-2020-09-03/plugins/BadWords/test.py
new/Limnoria-master-2020-10-13/plugins/BadWords/test.py
--- old/Limnoria-master-2020-09-03/plugins/BadWords/test.py 2020-08-30
14:52:45.000000000 +0200
+++ new/Limnoria-master-2020-10-13/plugins/BadWords/test.py 2020-10-10
11:51:56.000000000 +0200
@@ -76,7 +76,8 @@
self.assertNotError('badwords list')
self.assertNotError('badwords add shit')
self.assertNotError('badwords add ass')
- self.assertResponse('badwords list', 'ass and shit')
+ self.assertNotError('badwords add "fuck you"')
+ self.assertResponse('badwords list', 'ass, fuck you, and shit')
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Limnoria-master-2020-09-03/plugins/Config/plugin.py
new/Limnoria-master-2020-10-13/plugins/Config/plugin.py
--- old/Limnoria-master-2020-09-03/plugins/Config/plugin.py 2020-08-30
14:52:45.000000000 +0200
+++ new/Limnoria-master-2020-10-13/plugins/Config/plugin.py 2020-10-10
11:51:56.000000000 +0200
@@ -37,7 +37,6 @@
import supybot.world as world
import supybot.ircdb as ircdb
from supybot.commands import *
-from supybot.utils.iter import all
import supybot.ircutils as ircutils
import supybot.registry as registry
import supybot.callbacks as callbacks
@@ -172,7 +171,9 @@
vname = ':' + vname
if getattr(v, '_channelValue', False):
vname = '#' + vname
- if v._added and not all(irc.isChannel, v._added):
+ if v._added and not all(
+ irc.isChannel(child) or child.startswith(':')
+ for child in v._added):
vname = '@' + vname
L.append(vname)
utils.sortBy(str.lower, L)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Limnoria-master-2020-09-03/plugins/Config/test.py
new/Limnoria-master-2020-10-13/plugins/Config/test.py
--- old/Limnoria-master-2020-09-03/plugins/Config/test.py 2020-08-30
14:52:45.000000000 +0200
+++ new/Limnoria-master-2020-10-13/plugins/Config/test.py 2020-10-10
11:51:56.000000000 +0200
@@ -39,7 +39,7 @@
class ConfigTestCase(ChannelPluginTestCase):
# We add utilities so there's something in supybot.plugins.
- plugins = ('Config', 'User', 'Utilities')
+ plugins = ('Config', 'User', 'Utilities', 'Web')
prefix1 = '[email protected]'
prefix2 = '[email protected]'
@@ -437,6 +437,77 @@
self.assertFalse(
conf.supybot.reply.whenAddressedBy.strings.get(':test')._wasSet)
+ def testResetRegexpChannel(self):
+ """Specifically tests resetting a Regexp value, as they have an extra
+ internal state that needs to be reset; see the comment in plugin.py"""
+ self.assertResponse(
+ 'config plugins.Web.nonSnarfingRegexp',
+ 'Global: ; #test @ test: '
+ )
+ self.assertResponse(
+ 'config plugins.Web.nonSnarfingRegexp m/foo/',
+ 'The operation succeeded.'
+ )
+ self.assertResponse(
+ 'config channel plugins.Web.nonSnarfingRegexp m/bar/',
+ 'The operation succeeded.'
+ )
+ self.assertResponse(
+ 'config plugins.Web.nonSnarfingRegexp',
+ 'Global: m/foo/; #test @ test: m/bar/'
+ )
+ self.assertResponse(
+ 'config reset channel plugins.Web.nonSnarfingRegexp',
+ 'The operation succeeded.'
+ )
+ self.assertResponse('config plugins.Web.nonSnarfingRegexp',
+ 'Global: m/foo/; #test @ test: m/foo/'
+ )
+ self.assertResponse(
+ 'config plugins.Web.nonSnarfingRegexp ""',
+ 'The operation succeeded.'
+ )
+ self.assertResponse(
+ 'config plugins.Web.nonSnarfingRegexp',
+ 'Global: ; #test @ test: '
+ )
+
+ def testResetRegexpNetwork(self):
+ """Specifically tests resetting a Regexp value, as they have an extra
+ internal state that needs to be reset; see the comment in plugin.py"""
+ self.assertResponse(
+ 'config plugins.Web.nonSnarfingRegexp',
+ 'Global: ; #test @ test: '
+ )
+ self.assertResponse(
+ 'config plugins.Web.nonSnarfingRegexp m/foo/',
+ 'The operation succeeded.'
+ )
+ self.assertResponse(
+ 'config network plugins.Web.nonSnarfingRegexp m/bar/',
+ 'The operation succeeded.'
+ )
+ self.assertResponse(
+ 'config plugins.Web.nonSnarfingRegexp',
+ 'Global: m/foo/; #test @ test: m/bar/'
+ )
+ self.assertResponse(
+ 'config reset network plugins.Web.nonSnarfingRegexp',
+ 'The operation succeeded.'
+ )
+ self.assertResponse('config plugins.Web.nonSnarfingRegexp',
+ 'Global: m/foo/; #test @ test: m/foo/'
+ )
+ self.assertResponse(
+ 'config plugins.Web.nonSnarfingRegexp ""',
+ 'The operation succeeded.'
+ )
+ self.assertResponse(
+ 'config plugins.Web.nonSnarfingRegexp',
+ 'Global: ; #test @ test: '
+ )
+
+
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Limnoria-master-2020-09-03/plugins/Math/test.py
new/Limnoria-master-2020-10-13/plugins/Math/test.py
--- old/Limnoria-master-2020-09-03/plugins/Math/test.py 2020-08-30
14:52:45.000000000 +0200
+++ new/Limnoria-master-2020-10-13/plugins/Math/test.py 2020-10-10
11:51:56.000000000 +0200
@@ -137,7 +137,8 @@
def testCalcMemoryError(self):
self.assertRegexp('calc ' + '('*10000,
- '(too much recursion' # cpython
+ '(too much recursion' # cpython < 3.10
+ '|too many nested parentheses' # cpython >= 3.10
'|parenthesis is never closed)' # pypy
)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Limnoria-master-2020-09-03/plugins/Misc/plugin.py
new/Limnoria-master-2020-10-13/plugins/Misc/plugin.py
--- old/Limnoria-master-2020-09-03/plugins/Misc/plugin.py 2020-08-30
14:52:45.000000000 +0200
+++ new/Limnoria-master-2020-10-13/plugins/Misc/plugin.py 2020-10-10
11:51:56.000000000 +0200
@@ -168,8 +168,7 @@
# echo [] will get us an empty token set, but there's no need
# to log this in that case anyway, it being a nested command.
self.log.info('Not replying to %s in %s, not a command.',
- tokens[0], channel
- if channel != irc.nick else _('private'))
+ tokens[0], channel or _('private'))
if irc.nested:
bracketConfig = conf.supybot.commands.nested.brackets
brackets = bracketConfig.getSpecific(irc.network, channel)()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Limnoria-master-2020-09-03/plugins/RSS/plugin.py
new/Limnoria-master-2020-10-13/plugins/RSS/plugin.py
--- old/Limnoria-master-2020-09-03/plugins/RSS/plugin.py 2020-08-30
14:52:45.000000000 +0200
+++ new/Limnoria-master-2020-10-13/plugins/RSS/plugin.py 2020-10-10
11:51:56.000000000 +0200
@@ -527,10 +527,17 @@
message isn't sent in the channel itself.
"""
announce = conf.supybot.plugins.RSS.announce
- S = announce.get(channel)()
- for feed in feeds:
- S.discard(feed)
- announce.get(channel).setValue(S)
+
+ def remove_from_var(var):
+ S = var()
+ for feed in feeds:
+ S.discard(feed)
+ var.setValue(S)
+
+ remove_from_var(announce.get(channel))
+ remove_from_var(announce.getSpecific(
+ channel=channel, network=irc.network))
+
irc.replySuccess()
remove = wrap(remove, [('checkChannelCapability', 'op'),
many(first('url', 'feedName'))])
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Limnoria-master-2020-09-03/plugins/RSS/test.py
new/Limnoria-master-2020-10-13/plugins/RSS/test.py
--- old/Limnoria-master-2020-09-03/plugins/RSS/test.py 2020-08-30
14:52:45.000000000 +0200
+++ new/Limnoria-master-2020-10-13/plugins/RSS/test.py 2020-10-10
11:51:56.000000000 +0200
@@ -28,8 +28,12 @@
# POSSIBILITY OF SUCH DAMAGE.
###
+import functools
+from unittest.mock import patch
import sys
+
import feedparser
+
from supybot.test import *
import supybot.conf as conf
import supybot.utils.minisix as minisix
@@ -53,12 +57,26 @@
</rss>
"""
-def constant(content):
- if minisix.PY3:
- content = content.encode()
- def f(*args, **kwargs):
- return minisix.io.BytesIO(content)
- return f
+
+class MockResponse:
+ headers = {}
+ url = ''
+ def read(self):
+ return self._data.encode()
+
+ def close(self):
+ pass
+
+def mock_urllib(f):
+ mock = MockResponse()
+
+ @functools.wraps(f)
+ def newf(self):
+ with patch("urllib.request.OpenerDirector.open", return_value=mock):
+ f(self, mock)
+
+ return newf
+
url = 'http://www.advogato.org/rss/articles.xml'
class RSSTestCase(ChannelPluginTestCase):
@@ -82,9 +100,9 @@
finally:
self.assertNotError('rss remove xkcd')
- def testInitialAnnounceNewest(self):
- old_open = feedparser._open_resource
- feedparser._open_resource = constant(xkcd_new)
+ @mock_urllib
+ def testInitialAnnounceNewest(self, mock):
+ mock._data = xkcd_new
timeFastForward(1.1)
try:
with conf.supybot.plugins.RSS.sortFeedItems.context('newestFirst'):
@@ -95,11 +113,10 @@
finally:
self._feedMsg('rss announce remove xkcd')
self._feedMsg('rss remove xkcd')
- feedparser._open_resource = old_open
- def testInitialAnnounceOldest(self):
- old_open = feedparser._open_resource
- feedparser._open_resource = constant(xkcd_new)
+ @mock_urllib
+ def testInitialAnnounceOldest(self, mock):
+ mock._data = xkcd_new
timeFastForward(1.1)
try:
with conf.supybot.plugins.RSS.initialAnnounceHeadlines.context(1):
@@ -110,11 +127,10 @@
finally:
self._feedMsg('rss announce remove xkcd')
self._feedMsg('rss remove xkcd')
- feedparser._open_resource = old_open
- def testNoInitialAnnounce(self):
- old_open = feedparser._open_resource
- feedparser._open_resource = constant(xkcd_old)
+ @mock_urllib
+ def testNoInitialAnnounce(self, mock):
+ mock._data = xkcd_old
timeFastForward(1.1)
try:
with conf.supybot.plugins.RSS.initialAnnounceHeadlines.context(0):
@@ -124,11 +140,10 @@
finally:
self._feedMsg('rss announce remove xkcd')
self._feedMsg('rss remove xkcd')
- feedparser._open_resource = old_open
- def testAnnounce(self):
- old_open = feedparser._open_resource
- feedparser._open_resource = constant(xkcd_old)
+ @mock_urllib
+ def testAnnounce(self, mock):
+ mock._data = xkcd_old
timeFastForward(1.1)
try:
self.assertError('rss announce add xkcd')
@@ -139,7 +154,7 @@
with conf.supybot.plugins.RSS.waitPeriod.context(1):
timeFastForward(1.1)
self.assertNoResponse(' ', timeout=0.1)
- feedparser._open_resource = constant(xkcd_new)
+ mock._data = xkcd_new
self.assertNoResponse(' ', timeout=0.1)
timeFastForward(1.1)
self.assertRegexp(' ', 'Chaos')
@@ -148,11 +163,10 @@
finally:
self._feedMsg('rss announce remove xkcd')
self._feedMsg('rss remove xkcd')
- feedparser._open_resource = old_open
- def testMaxAnnounces(self):
- old_open = feedparser._open_resource
- feedparser._open_resource = constant(xkcd_old)
+ @mock_urllib
+ def testMaxAnnounces(self, mock):
+ mock._data = xkcd_old
timeFastForward(1.1)
try:
self.assertError('rss announce add xkcd')
@@ -164,7 +178,7 @@
with
conf.supybot.plugins.RSS.maximumAnnounceHeadlines.context(1):
timeFastForward(1.1)
self.assertNoResponse(' ', timeout=0.1)
- feedparser._open_resource = constant(xkcd_new)
+ mock._data = xkcd_new
log.debug('set return value to: %r', xkcd_new)
self.assertNoResponse(' ', timeout=0.1)
timeFastForward(1.1)
@@ -173,11 +187,10 @@
finally:
self._feedMsg('rss announce remove xkcd')
self._feedMsg('rss remove xkcd')
- feedparser._open_resource = old_open
- def testAnnounceAnonymous(self):
- old_open = feedparser._open_resource
- feedparser._open_resource = constant(xkcd_old)
+ @mock_urllib
+ def testAnnounceAnonymous(self, mock):
+ mock._data = xkcd_old
timeFastForward(1.1)
try:
self.assertNotError('rss announce add http://xkcd.com/rss.xml')
@@ -185,18 +198,17 @@
with conf.supybot.plugins.RSS.waitPeriod.context(1):
timeFastForward(1.1)
self.assertNoResponse(' ', timeout=0.1)
- feedparser._open_resource = constant(xkcd_new)
+ mock._data = xkcd_new
self.assertNoResponse(' ', timeout=0.1)
timeFastForward(1.1)
self.assertRegexp(' ', 'Telescopes')
finally:
self._feedMsg('rss announce remove http://xkcd.com/rss.xml')
self._feedMsg('rss remove http://xkcd.com/rss.xml')
- feedparser._open_resource = old_open
- def testAnnounceReload(self):
- old_open = feedparser._open_resource
- feedparser._open_resource = constant(xkcd_old)
+ @mock_urllib
+ def testAnnounceReload(self, mock):
+ mock._data = xkcd_old
timeFastForward(1.1)
try:
with conf.supybot.plugins.RSS.waitPeriod.context(1):
@@ -210,29 +222,27 @@
finally:
self._feedMsg('rss announce remove xkcd')
self._feedMsg('rss remove xkcd')
- feedparser._open_resource = old_open
- def testReload(self):
- old_open = feedparser._open_resource
- feedparser._open_resource = constant(xkcd_old)
+ @mock_urllib
+ def testReload(self, mock):
+ mock._data = xkcd_old
timeFastForward(1.1)
try:
with conf.supybot.plugins.RSS.waitPeriod.context(1):
self.assertNotError('rss add xkcd http://xkcd.com/rss.xml')
self.assertNotError('rss announce add xkcd')
self.assertRegexp(' ', 'Snake Facts')
- feedparser._open_resource = constant(xkcd_new)
+ mock._data = xkcd_new
self.assertNotError('reload RSS')
self.assertRegexp(' ', 'Telescopes')
finally:
self._feedMsg('rss announce remove xkcd')
self._feedMsg('rss remove xkcd')
- feedparser._open_resource = old_open
- def testReloadNoDelay(self):
+ @mock_urllib
+ def testReloadNoDelay(self, mock):
# https://github.com/ProgVal/Limnoria/issues/922
- old_open = feedparser._open_resource
- feedparser._open_resource = constant(xkcd_old)
+ mock._data = xkcd_old
timeFastForward(1.1)
try:
with conf.supybot.plugins.RSS.waitPeriod.context(1):
@@ -243,11 +253,10 @@
finally:
self._feedMsg('rss announce remove xkcd')
self._feedMsg('rss remove xkcd')
- feedparser._open_resource = old_open
- def testReannounce(self):
- old_open = feedparser._open_resource
- feedparser._open_resource = constant(xkcd_old)
+ @mock_urllib
+ def testReannounce(self, mock):
+ mock._data = xkcd_old
timeFastForward(1.1)
try:
self.assertError('rss announce add xkcd')
@@ -260,7 +269,7 @@
timeFastForward(1.1)
self.assertNoResponse(' ', timeout=0.1)
self._feedMsg('rss announce remove xkcd')
- feedparser._open_resource = constant(xkcd_new)
+ mock._data = xkcd_new
timeFastForward(1.1)
self.assertNoResponse(' ', timeout=0.1)
self.assertNotError('rss announce add xkcd')
@@ -271,11 +280,10 @@
finally:
self._feedMsg('rss announce remove xkcd')
self._feedMsg('rss remove xkcd')
- feedparser._open_resource = old_open
- def testFeedSpecificFormat(self):
- old_open = feedparser._open_resource
- feedparser._open_resource = constant(xkcd_old)
+ @mock_urllib
+ def testFeedSpecificFormat(self, mock):
+ mock._data = xkcd_old
timeFastForward(1.1)
try:
self.assertNotError('rss add xkcd http://xkcd.com/rss.xml')
@@ -287,11 +295,10 @@
finally:
self._feedMsg('rss remove xkcd')
self._feedMsg('rss remove xkcdsec')
- feedparser._open_resource = old_open
- def testFeedSpecificWaitPeriod(self):
- old_open = feedparser._open_resource
- feedparser._open_resource = constant(xkcd_old)
+ @mock_urllib
+ def testFeedSpecificWaitPeriod(self, mock):
+ mock._data = xkcd_old
timeFastForward(1.1)
try:
self.assertNotError('rss add xkcd1 http://xkcd.com/rss.xml')
@@ -304,7 +311,7 @@
with
conf.supybot.plugins.RSS.feeds.xkcd1.waitPeriod.context(1):
timeFastForward(1.1)
self.assertNoResponse(' ', timeout=0.1)
- feedparser._open_resource = constant(xkcd_new)
+ mock._data = xkcd_new
self.assertNoResponse(' ', timeout=0.1)
timeFastForward(1.1)
self.assertRegexp(' ', 'xkcd1.*Chaos')
@@ -317,30 +324,23 @@
self._feedMsg('rss remove xkcd1')
self._feedMsg('rss announce remove xkcd2')
self._feedMsg('rss remove xkcd2')
- feedparser._open_resource = old_open
- def testDescription(self):
+ @mock_urllib
+ def testDescription(self, mock):
timeFastForward(1.1)
with conf.supybot.plugins.RSS.format.context('$description'):
- old_open = feedparser._open_resource
- feedparser._open_resource = constant(xkcd_new)
- try:
- self.assertRegexp('rss http://xkcd.com/rss.xml',
- 'On the other hand, the refractor\'s')
- finally:
- feedparser._open_resource = old_open
+ mock._data = xkcd_new
+ self.assertRegexp('rss http://xkcd.com/rss.xml',
+ 'On the other hand, the refractor\'s')
- def testBadlyFormedFeedWithNoItems(self):
+ @mock_urllib
+ def testBadlyFormedFeedWithNoItems(self, mock):
# This combination will cause the RSS command to show the last parser
# error.
- old_open = feedparser._open_resource
timeFastForward(1.1)
- feedparser._open_resource = constant(not_well_formed)
- try:
- self.assertRegexp('rss http://example.com/',
- 'Parser error')
- finally:
- feedparser._open_resource = old_open
+ mock._data = not_well_formed
+ self.assertRegexp('rss http://example.com/',
+ 'Parser error')
if network:
timeout = 5 # Note this applies also to the above tests
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/Limnoria-master-2020-09-03/plugins/Scheduler/plugin.py
new/Limnoria-master-2020-10-13/plugins/Scheduler/plugin.py
--- old/Limnoria-master-2020-09-03/plugins/Scheduler/plugin.py 2020-08-30
14:52:45.000000000 +0200
+++ new/Limnoria-master-2020-10-13/plugins/Scheduler/plugin.py 2020-10-10
11:51:56.000000000 +0200
@@ -90,7 +90,12 @@
# though we'll never know for sure, because other
# plugins can schedule stuff, too.
n = int(name)
- self._add(network, event['msg'], event['time'],
event['command'], n)
+ # Here we use event.get() method instead of event[]
+ # This is to maintain compatibility with older bots
+ # lacking 'is_reminder' in their event dicts
+ is_reminder = event.get('is_reminder', False)
+ self._add(network, event['msg'], event['time'],
event['command'],
+ is_reminder, n)
elif event['type'] == 'repeat': # repeating event
now = time.time()
first_run = event.get('first_run')
@@ -145,13 +150,27 @@
self.Proxy(irc, msg, tokens)
return f
- def _add(self, network, msg, t, command, name=None):
- f = self._makeCommandFunction(network, msg, command)
+ def _makeReminderFunction(self, network, msg, text):
+ """Makes a function suitable for scheduling text"""
+ def f():
+ # If the network isn't available, pick any other one
+ irc = world.getIrc(network) or world.ircs[0]
+ replyIrc = callbacks.ReplyIrcProxy(irc, msg)
+ replyIrc.reply(_('Reminder: %s') % text, msg=msg, prefixNick=True)
+ del self.events[str(f.eventId)]
+ return f
+
+ def _add(self, network, msg, t, command, is_reminder=False, name=None):
+ if is_reminder:
+ f = self._makeReminderFunction(network, msg, command)
+ else:
+ f = self._makeCommandFunction(network, msg, command)
id = schedule.addEvent(f, t, name)
f.eventId = id
self.events[str(id)] = {'command':command,
+ 'is_reminder':is_reminder,
'msg':msg,
- 'network': network,
+ 'network':network,
'time':t,
'type':'single'}
return id
@@ -172,6 +191,19 @@
add = wrap(add, ['positiveInt', 'text'])
@internationalizeDocstring
+ def remind(self, irc, msg, args, seconds, text):
+ """ <seconds> <text>
+
+ Sets a reminder with string <text> to run <seconds> seconds in the
+ future. For example, 'scheduler remind [seconds 30m] "Hello World"'
+ will return '<nick> Reminder: Hello World' 30 minutes after being set.
+ """
+ t = time.time() + seconds
+ id = self._add(irc.network, msg, t, text, is_reminder=True)
+ irc.replySuccess(format(_('Reminder Event #%i added.'), id))
+ remind = wrap(remind, ['positiveInt', 'text'])
+
+ @internationalizeDocstring
def remove(self, irc, msg, args, id):
"""<id>
@@ -200,7 +232,7 @@
assert id == name
self.events[name] = {'command':command,
'msg':msg,
- 'network': network,
+ 'network':network,
'time':seconds,
'type':'repeat',
'first_run': first_run,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Limnoria-master-2020-09-03/plugins/Scheduler/test.py
new/Limnoria-master-2020-10-13/plugins/Scheduler/test.py
--- old/Limnoria-master-2020-09-03/plugins/Scheduler/test.py 2020-08-30
14:52:45.000000000 +0200
+++ new/Limnoria-master-2020-10-13/plugins/Scheduler/test.py 2020-10-10
11:51:56.000000000 +0200
@@ -74,6 +74,21 @@
timeFastForward(5)
self.assertNoResponse(' ', timeout=1)
+ def testRemind(self):
+ self.assertNotError('scheduler remind 5 testRemind')
+ self.assertResponse(
+ 'scheduler list',
+ '3 (in 4 seconds): "testRemind"')
+ timeFastForward(3)
+ self.assertNoResponse(' ', timeout=1)
+ timeFastForward(3)
+ self.assertResponse(' ', 'Reminder: testRemind')
+ timeFastForward(5)
+ self.assertNoResponse(' ', timeout=1)
+ self.assertResponse(
+ 'scheduler list', 'There are currently no scheduled commands.')
+
+
def testRepeat(self):
self.assertRegexp('scheduler repeat repeater 5 echo testRepeat',
'testRepeat')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Limnoria-master-2020-09-03/src/ircdb.py
new/Limnoria-master-2020-10-13/src/ircdb.py
--- old/Limnoria-master-2020-09-03/src/ircdb.py 2020-08-30 14:52:45.000000000
+0200
+++ new/Limnoria-master-2020-10-13/src/ircdb.py 2020-10-10 11:51:56.000000000
+0200
@@ -1172,8 +1172,8 @@
try:
id = users.getUserId(hostmask)
user = users.getUser(id)
- if user._checkCapability('owner'):
- # Owners shouldn't ever be ignored.
+ if user._checkCapability('trusted'):
+ # Trusted users (including owners) shouldn't ever be ignored.
return False
elif user.ignore:
log.debug('Ignoring %s due to their IrcUser ignore flag.',
hostmask)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Limnoria-master-2020-09-03/src/irclib.py
new/Limnoria-master-2020-10-13/src/irclib.py
--- old/Limnoria-master-2020-09-03/src/irclib.py 2020-08-30
14:52:45.000000000 +0200
+++ new/Limnoria-master-2020-10-13/src/irclib.py 2020-10-10
11:51:56.000000000 +0200
@@ -301,6 +301,58 @@
# status of various modes (especially ops/halfops/voices) in channels, etc.
###
class ChannelState(utils.python.Object):
+ """Represents the known state of an IRC channel.
+
+ .. attribute:: topic
+
+ The topic of a channel (possibly the empty stringĂ
+
+ :type: str
+
+ .. attribute:: created
+
+ Timestamp of the channel creation, according to the server.
+
+ :type: int
+
+ .. attribute:: ops
+
+ Set of the nicks of all the operators of the channel.
+
+ :type: ircutils.IrcSet[str]
+
+ .. attribute:: halfops
+
+ Set of the nicks of all the half-operators of the channel.
+
+ :type: ircutils.IrcSet[str]
+
+ .. attribute:: voices
+
+ Set of the nicks of all the voiced users of the channel.
+
+ :type: ircutils.IrcSet[str]
+
+ .. attribute:: users
+
+ Set of the nicks of all the users in the channel.
+
+ :type: ircutils.IrcSet[str]
+
+ .. attribute:: bans
+
+ Set of the all the banmasks set in the channel.
+
+ :type: ircutils.IrcSet[str]
+
+ .. attribute:: modes
+
+ Dict of all the modes set in the channel, with they value, if any.
+ This excludes the following modes: ovhbeq
+
+ :type: Dict[str, Optional[str]]
+ """
+
__slots__ = ('users', 'ops', 'halfops', 'bans',
'voices', 'topic', 'modes', 'created')
def __init__(self):
@@ -314,16 +366,27 @@
self.modes = {}
def isOp(self, nick):
+ """Returns whether the given nick is an op."""
return nick in self.ops
+
def isOpPlus(self, nick):
+ """Returns whether the given nick is an op."""
return nick in self.ops
+
def isVoice(self, nick):
+ """Returns whether the given nick is voiced."""
return nick in self.voices
+
def isVoicePlus(self, nick):
+ """Returns whether the given nick is voiced, an halfop, or an op."""
return nick in self.voices or nick in self.halfops or nick in self.ops
+
def isHalfop(self, nick):
+ """Returns whether the given nick is an halfop."""
return nick in self.halfops
+
def isHalfopPlus(self, nick):
+ """Returns whether the given nick is an halfop, or an op."""
return nick in self.halfops or nick in self.ops
def addUser(self, user):
@@ -412,7 +475,11 @@
ret = ret and getattr(self, name) == getattr(other, name)
return ret
+
Batch = collections.namedtuple('Batch', 'type arguments messages')
+"""Represents a batch of messages, see
+<https://ircv3.net/specs/extensions/batch-3.2>"""
+
class IrcStateFsm(object):
'''Finite State Machine keeping track of what part of the connection
@@ -421,6 +488,8 @@
@enum.unique
class States(enum.Enum):
+ """Enumeration of all the states of an IRC connection."""
+
UNINITIALIZED = 10
'''Nothing received yet (except server notices)'''
@@ -515,6 +584,8 @@
self._transition(irc, msg, self.States.INIT_MOTD, [
self.States.INIT_CAP_NEGOTIATION,
self.States.INIT_WAITING_MOTD,
+ self.States.CONNECTED,
+ self.States.CONNECTED_SASL,
])
def on_end_motd(self, irc, msg):
@@ -522,7 +593,9 @@
self._transition(irc, msg, self.States.CONNECTED, [
self.States.INIT_CAP_NEGOTIATION,
self.States.INIT_WAITING_MOTD,
- self.States.INIT_MOTD
+ self.States.INIT_MOTD,
+ self.States.CONNECTED,
+ self.States.CONNECTED_SASL,
])
def on_shutdown(self, irc, msg):
@@ -530,8 +603,84 @@
class IrcState(IrcCommandDispatcher, log.Firewalled):
"""Maintains state of the Irc connection. Should also become smarter.
+
+ .. attribute:: fsm
+
+ A finite-state machine representing the current state of the IRC
+ connection: various steps while connecting, then remains in the
+ CONNECTED state (or CONNECTED_SASL when doing SASL in the middle of a
+ connection).
+
+ :type: IrcStateFsm
+
+ .. attribute:: capabilities_req
+
+ Set of all capabilities requested from the server.
+ See <https://ircv3.net/specs/core/capability-negotiation>
+
+ :type: Set[str]
+
+ .. attribute:: capabilities_acq
+
+ Set of all capabilities requested from and acknowledged by the
+ server. See <https://ircv3.net/specs/core/capability-negotiation>
+
+ :type: Set[str]
+
+ .. attribute:: capabilities_nak
+
+ Set of all capabilities requested from and refused by the server.
+ This should always be empty unless the bot, a plugin, or the server is
+ misbehaving. See <https://ircv3.net/specs/core/capability-negotiation>
+
+ :type: Set[str]
+
+ .. attribute:: capabilities_ls
+
+ Stores all the capabilities advertised by the server, as well as their
+ value, if any.
+
+ :type: Dict[str, Optional[str]]
+
+ .. attribute:: ircd
+
+ Identification string of the software running the server we are
+ connected to. See
+ <https://defs.ircdocs.horse/defs/numerics.html#rpl-myinfo-004>
+
+ :type: str
+
+ .. attribute:: supported
+
+ Stores the value of ISUPPORT sent when connecting.
+ See <https://defs.ircdocs.horse/defs/isupport.html> for the list of
+ keys.
+
+ :type: utils.InsensitivePreservingDict[str, Any]
+
+ .. attribute:: history
+
+ History of messages received from the network. Automatically discards
+ messages so it doesn't exceed
+ ``supybot.protocols.irc.maxHistoryLength``.
+
+ :type: RingBuffer[ircmsgs.IrcMsg]
+
+ .. attribute:: channels
+
+ Store channel states.
+
+ :type: ircutils.IrcDict[str, ChannelState]
+
+ .. attribute:: nickToHostmask
+
+ Stores the last hostmask of a seen nick.
+
+ :type: ircutils.IrcDict[str, str]
"""
__firewalled__ = {'addMsg': None}
+
+
def __init__(self, history=None, supported=None,
nicksToHostmasks=None, channels=None,
capabilities_req=None,
@@ -895,6 +1044,76 @@
"""The base class for an IRC connection.
Handles PING commands already.
+
+ .. attribute:: zombie
+
+ Whether or not this object represents a living IRC connection.
+
+ :type: bool
+
+ .. attribute:: network
+
+ The name of the network this object is connected to.
+
+ :type: str
+
+ .. attribute:: startedAt
+
+ When this connection was (re)started.
+
+ :type: float
+
+ .. attribute:: callbacks
+
+ List of all callbacks (ie. plugins) currently loaded
+
+ :type: List[IrcCallback]
+
+ .. attribute:: queue
+
+ Queue of messages waiting to be sent. Plugins should use the
+ ``queueMsg`` method instead of accessing this directly.
+
+ :type: IrcMsgQueue
+
+ .. attribute:: fastqueue
+
+ Same as ``queue``, but for messages with high priority. Plugins should
+ use the ``sendMsg`` method instead of accessing this directly (or
+ `queueMsg` if the message isn't high priority).
+
+ :type: smallqueue
+
+ .. attribute:: driver
+
+ Driver of the IRC connection (normally, a
+ :py:class:`supybot.drivers.Socket.SocketDriver` object).
+ Plugins normally do not need to access this.
+
+ .. attribute:: startedSync
+
+ When joining a channel, a ``'#channel': time.time()`` entry is added
+ to this dict, which is then removed when the join is completed.
+ Plugins should not change this value, it is automatically handled when
+ they send a JOIN.
+
+ :type: ircutils.IrcDict[str, float]
+
+ .. attribute:: monitoring
+
+ A dict with nicks as keys and the number of plugins monitoring this
+ nick as value.
+ Plugins should not access this directly, and should use the ``monitor``
+ and ``unmonitor`` methods instead.
+
+ :type: ircutils.IrcDict[str, int]
+
+ .. attribute:: state
+
+ An :py:class:`supybot.irclib.IrcState` object, which stores all the
+ known information about the connection with the IRC network.
+
+ :type: supybot.irclib.IrcState
"""
__firewalled__ = {'die': None,
'feedMsg': None,
@@ -930,6 +1149,9 @@
return ircutils.isChannel(s, **kw)
def isNick(self, s):
+ """Returns whether the given argument is a valid nick on this
+ network.
+ """
kw = {}
if 'nicklen' in self.state.supported:
kw['nicklen'] = self.state.supported['nicklen']
@@ -1291,6 +1513,12 @@
'userhost-in-names', 'invite-notify', 'server-time',
'chghost', 'batch', 'away-notify', 'message-tags',
'msgid', 'setname', 'labeled-response', 'echo-message'])
+ """IRCv3 capabilities requested when they are available.
+
+ echo-message is special-cased to be requested only with labeled-response.
+
+ To check if a capability was negotiated, use `irc.state.capabilities_ack`.
+ """
def _queueConnectMessages(self):
if self.zombie:
@@ -1358,7 +1586,10 @@
self._maybeStartSasl(msg)
else:
pass # Already in the middle of a SASL auth
- else:
+ elif self.state.fsm.state != IrcStateFsm.States.CONNECTED:
+ # If we are still in the initial cap negotiation (ie. if this
+ # is not in response to a 'CAP NEW'), send a CAP END so the
+ # server sends us the MOTD
self.endCapabilityNegociation(msg)
else:
log.debug('Waiting for ACK/NAK of capabilities: %r',
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Limnoria-master-2020-09-03/src/registry.py
new/Limnoria-master-2020-10-13/src/registry.py
--- old/Limnoria-master-2020-09-03/src/registry.py 2020-08-30
14:52:45.000000000 +0200
+++ new/Limnoria-master-2020-10-13/src/registry.py 2020-10-10
11:51:56.000000000 +0200
@@ -749,14 +749,15 @@
class Regexp(Value):
"""Value must be a valid regular expression."""
- __slots__ = ('sr', 'value', '__parent')
errormsg = _('Value must be a valid regular expression, not %r.')
- def __init__(self, *args, **kwargs):
- kwargs['setDefault'] = False
- self.sr = ''
- self.value = None
- self.__parent = super(Regexp, self)
- self.__parent.__init__(*args, **kwargs)
+
+ def __init__(self, default, *args, **kwargs):
+ # We're not supposed to do convertions here, BUT this is needed
+ # when the value is set programmatically because the value
+ # plugins set (a string) is not the same as the one they get
+ # (a compiled pattern object)
+ default = self._convertFromString(default)
+ super().__init__(default, *args, **kwargs)
def error(self, e):
s = 'Value must be a regexp of the form m/.../ or /.../. %s' % e
@@ -764,29 +765,48 @@
e.value = self
raise e
+ def _convertFromString(self, s):
+ if s:
+ # We need to preserve the original string, as it's shown in
+ # the user interface and the config file.
+ # It might be tempting to set the original string as an
+ # attribute, but doing so would result in inconsistent states
+ # for childs of this variable, should they be reset, or the
+ # value of there parent change.
+ return (s, utils.str.perlReToPythonRe(s))
+ else:
+ return None
+
def set(self, s):
try:
- if s:
- self.setValue(utils.str.perlReToPythonRe(s), sr=s)
- else:
- self.setValue(None)
+ v = self._convertFromString(s)
except ValueError as e:
self.error(e)
+ else:
+ super().set(v)
- def setValue(self, v, sr=None):
- if v is None:
- self.sr = ''
- self.__parent.setValue(None)
- elif sr is not None:
- self.sr = sr
- self.__parent.setValue(v)
+ def setValue(self, v):
+ """Don't call this function directly from plugins, it is subject
+ to change without notice."""
+ if v is not None and (not isinstance(v, tuple) or len(v) != 2):
+ raise InvalidRegistryValue(
+ 'Can\'t setValue a regexp, there would be an inconsistency '
+ 'between the regexp and the recorded string value. '
+ 'Use .set() instead.')
+
+ super().setValue(v)
+
+ def __call__(self):
+ if self.value is None:
+ return None
else:
- raise InvalidRegistryValue('Can\'t setValue a regexp, there would
be an inconsistency '\
- 'between the regexp and the recorded string value.')
+ return self.value[1]
def __str__(self):
- self() # Gotta update if we've been reloaded.
- return self.sr
+ if self.value is None:
+ return ''
+ else:
+ return self.value[0]
class SeparatedListOf(Value):
__slots__ = ()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Limnoria-master-2020-09-03/src/setup.py
new/Limnoria-master-2020-10-13/src/setup.py
--- old/Limnoria-master-2020-09-03/src/setup.py 2020-08-30 14:52:45.000000000
+0200
+++ new/Limnoria-master-2020-10-13/src/setup.py 2020-10-10 11:51:56.000000000
+0200
@@ -70,6 +70,7 @@
if os.path.isfile(readme_path):
with open(readme_path, 'r') as fd:
kwargs['long_description'] = fd.read()
+ kwargs['long_description_content_type'] = 'text/markdown'
module_name = kwargs['name'].replace('-', '_')
kwargs.setdefault('packages', [module_name])
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Limnoria-master-2020-09-03/test/test_irclib.py
new/Limnoria-master-2020-10-13/test/test_irclib.py
--- old/Limnoria-master-2020-09-03/test/test_irclib.py 2020-08-30
14:52:45.000000000 +0200
+++ new/Limnoria-master-2020-10-13/test/test_irclib.py 2020-10-10
11:51:56.000000000 +0200
@@ -45,6 +45,38 @@
rawmsgs = []
+class CapNegMixin:
+ """Utilities for handling the capability negotiation."""
+
+ def startCapNegociation(self, caps='sasl'):
+ m = self.irc.takeMsg()
+ self.assertTrue(m.command == 'CAP', 'Expected CAP, got %r.' % m)
+ self.assertTrue(m.args == ('LS', '302'), 'Expected CAP LS 302, got
%r.' % m)
+
+ m = self.irc.takeMsg()
+ self.assertTrue(m.command == 'NICK', 'Expected NICK, got %r.' % m)
+
+ m = self.irc.takeMsg()
+ self.assertTrue(m.command == 'USER', 'Expected USER, got %r.' % m)
+
+ self.irc.feedMsg(ircmsgs.IrcMsg(command='CAP',
+ args=('*', 'LS', caps)))
+
+ if caps:
+ m = self.irc.takeMsg()
+ self.assertTrue(m.command == 'CAP', 'Expected CAP, got %r.' % m)
+ self.assertEqual(m.args[0], 'REQ', m)
+ self.assertEqual(m.args[1], 'sasl')
+
+ self.irc.feedMsg(ircmsgs.IrcMsg(command='CAP',
+ args=('*', 'ACK', 'sasl')))
+
+ def endCapNegociation(self):
+ m = self.irc.takeMsg()
+ self.assertTrue(m.command == 'CAP', 'Expected CAP, got %r.' % m)
+ self.assertEqual(m.args, ('END',), m)
+
+
class IrcCommandDispatcherTestCase(SupyTestCase):
class DispatchedClass(irclib.IrcCommandDispatcher):
def doPrivmsg():
@@ -473,7 +505,8 @@
st = irclib.IrcState()
self.assert_(st.addMsg(self.irc, ircmsgs.IrcMsg('MODE foo +i')) or 1)
-class IrcCapsTestCase(SupyTestCase):
+
+class IrcCapsTestCase(SupyTestCase, CapNegMixin):
def testReqLineLength(self):
self.irc = irclib.Irc('test')
@@ -572,6 +605,74 @@
m = self.irc.takeMsg()
self.assertIsNone(m)
+ def testCapNew(self):
+ self.irc = irclib.Irc('test')
+
+ self.assertEqual(self.irc.sasl_current_mechanism, None)
+ self.assertEqual(self.irc.sasl_next_mechanisms, [])
+
+ self.startCapNegociation(caps='')
+
+ self.endCapNegociation()
+
+ while self.irc.takeMsg():
+ pass
+
+ self.irc.feedMsg(ircmsgs.IrcMsg(command='422')) # ERR_NOMOTD
+
+ m = self.irc.takeMsg()
+ self.assertIsNone(m)
+
+ self.irc.feedMsg(ircmsgs.IrcMsg(
+ command='CAP', args=['*', 'NEW', 'account-notify']))
+
+ m = self.irc.takeMsg()
+ self.assertEqual(m,
+ ircmsgs.IrcMsg(command='CAP', args=['REQ', 'account-notify']))
+
+ self.irc.feedMsg(ircmsgs.IrcMsg(
+ command='CAP', args=['*', 'ACK', 'account-notify']))
+
+ self.assertIn('account-notify', self.irc.state.capabilities_ack)
+
+ def testCapNewEchomessageLabeledResponse(self):
+ self.irc = irclib.Irc('test')
+
+ self.assertEqual(self.irc.sasl_current_mechanism, None)
+ self.assertEqual(self.irc.sasl_next_mechanisms, [])
+
+ self.startCapNegociation(caps='')
+
+ self.endCapNegociation()
+
+ while self.irc.takeMsg():
+ pass
+
+ self.irc.feedMsg(ircmsgs.IrcMsg(command='422')) # ERR_NOMOTD
+
+ m = self.irc.takeMsg()
+ self.assertIsNone(m)
+
+ self.irc.feedMsg(ircmsgs.IrcMsg(
+ command='CAP', args=['*', 'NEW', 'echo-message']))
+
+ m = self.irc.takeMsg()
+ self.assertIsNone(m)
+
+ self.irc.feedMsg(ircmsgs.IrcMsg(
+ command='CAP', args=['*', 'NEW', 'labeled-response']))
+
+ m = self.irc.takeMsg()
+ self.assertEqual(m,
+ ircmsgs.IrcMsg(
+ command='CAP', args=['REQ', 'echo-message labeled-response']))
+
+ self.irc.feedMsg(ircmsgs.IrcMsg(
+ command='CAP', args=['*', 'ACK', 'echo-message labeled-response']))
+
+ self.assertIn('echo-message', self.irc.state.capabilities_ack)
+ self.assertIn('labeled-response', self.irc.state.capabilities_ack)
+
class StsTestCase(SupyTestCase):
def setUp(self):
@@ -757,6 +858,17 @@
self.irc.feedMsg(msg2)
self.assertEqual(list(self.irc.state.history), [msg1, msg2])
+ def testMultipleMotd(self):
+ self.irc.reset()
+
+ self.irc.feedMsg(ircmsgs.IrcMsg(command='422'))
+
+ self.irc.feedMsg(ircmsgs.IrcMsg(command='422'))
+
+ self.irc.feedMsg(ircmsgs.IrcMsg(command='375', args=['nick']))
+ self.irc.feedMsg(ircmsgs.IrcMsg(command='372', args=['nick', 'some
message']))
+ self.irc.feedMsg(ircmsgs.IrcMsg(command='376', args=['nick']))
+
def testMsgChannel(self):
self.irc.reset()
@@ -856,38 +968,10 @@
self.irc.removeCallback(c.name())
self.assertEqual(c.batch, irclib.Batch('netjoin', (), [m1, m2, m3,
m4]))
-class SaslTestCase(SupyTestCase):
+class SaslTestCase(SupyTestCase, CapNegMixin):
def setUp(self):
pass
- def startCapNegociation(self, caps='sasl'):
- m = self.irc.takeMsg()
- self.assertTrue(m.command == 'CAP', 'Expected CAP, got %r.' % m)
- self.assertTrue(m.args == ('LS', '302'), 'Expected CAP LS 302, got
%r.' % m)
-
- m = self.irc.takeMsg()
- self.assertTrue(m.command == 'NICK', 'Expected NICK, got %r.' % m)
-
- m = self.irc.takeMsg()
- self.assertTrue(m.command == 'USER', 'Expected USER, got %r.' % m)
-
- self.irc.feedMsg(ircmsgs.IrcMsg(command='CAP',
- args=('*', 'LS', caps)))
-
- if caps:
- m = self.irc.takeMsg()
- self.assertTrue(m.command == 'CAP', 'Expected CAP, got %r.' % m)
- self.assertEqual(m.args[0], 'REQ', m)
- self.assertEqual(m.args[1], 'sasl')
-
- self.irc.feedMsg(ircmsgs.IrcMsg(command='CAP',
- args=('*', 'ACK', 'sasl')))
-
- def endCapNegociation(self):
- m = self.irc.takeMsg()
- self.assertTrue(m.command == 'CAP', 'Expected CAP, got %r.' % m)
- self.assertEqual(m.args, ('END',), m)
-
def testPlain(self):
try:
conf.supybot.networks.test.sasl.username.setValue('jilles')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Limnoria-master-2020-09-03/test/test_registry.py
new/Limnoria-master-2020-10-13/test/test_registry.py
--- old/Limnoria-master-2020-09-03/test/test_registry.py 2020-08-30
14:52:45.000000000 +0200
+++ new/Limnoria-master-2020-10-13/test/test_registry.py 2020-10-10
11:51:56.000000000 +0200
@@ -182,9 +182,24 @@
self.assertTrue(v().match('foo'))
v.set('')
self.assertEqual(v(), None)
+
+ def testRegexpSetValue(self):
+ v = registry.Regexp(None, 'help')
+ self.assertRaises(registry.InvalidRegistryValue,
+ v.setValue, r'foo')
self.assertRaises(registry.InvalidRegistryValue,
v.setValue, re.compile(r'foo'))
+ def testRegexpDefaultString(self):
+ v = registry.Regexp('m/foo/', 'help')
+ self.assertEqual(v(), re.compile('foo'))
+
+ v = registry.Regexp('', 'help')
+ self.assertEqual(v(), None)
+
+ v = registry.Regexp(None, 'help')
+ self.assertEqual(v(), None)
+
def testBackslashesKeys(self):
conf.supybot.reply.whenAddressedBy.strings.get(':foo').set('=/*')
filename = conf.supybot.directories.conf.dirize('backslashes1.conf')