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-11-01 13:42:23
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-limnoria (Old)
and /work/SRC/openSUSE:Factory/.python-limnoria.new.2275 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-limnoria"
Tue Nov 1 13:42:23 2022 rev:27 rq:1032514 version:2022.09.27
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-limnoria/python-limnoria.changes
2022-07-15 13:52:43.903568340 +0200
+++
/work/SRC/openSUSE:Factory/.python-limnoria.new.2275/python-limnoria.changes
2022-11-01 13:42:34.387897647 +0100
@@ -1,0 +2,6 @@
+Mon Oct 31 09:57:34 UTC 2022 - Atri Bhattacharya <[email protected]>
+
+- Update to version 2022-09-27:
+ * utils/web: Add <br/> to the list of block elements.
+
+-------------------------------------------------------------------
Old:
----
limnoria-2022.07.03.tar.gz
New:
----
limnoria-2022.09.27.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-limnoria.spec ++++++
--- /var/tmp/diff_new_pack.zvtK2G/_old 2022-11-01 13:42:34.875900244 +0100
+++ /var/tmp/diff_new_pack.zvtK2G/_new 2022-11-01 13:42:34.879900264 +0100
@@ -18,9 +18,9 @@
%define skip_python2 1
%define appname limnoria
-%define srcver 2022-07-03
+%define srcver 2022-09-27
Name: python-limnoria
-Version: 2022.07.03
+Version: 2022.09.27
Release: 0
Summary: A modified version of Supybot (an IRC bot and framework)
License: BSD-3-Clause
++++++ limnoria-2022.07.03.tar.gz -> limnoria-2022.09.27.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Limnoria-master-2022-07-03/plugins/Aka/README.rst
new/Limnoria-master-2022-09-27/plugins/Aka/README.rst
--- old/Limnoria-master-2022-07-03/plugins/Aka/README.rst 2022-06-23
22:31:17.000000000 +0200
+++ new/Limnoria-master-2022-09-27/plugins/Aka/README.rst 2022-09-20
07:51:46.000000000 +0200
@@ -46,7 +46,7 @@
Trout
^^^^^
-Add an aka, trout, which expects a word as an argument::
+Add an aka, ``trout``, which expects a word as an argument::
<jamessan> @aka add trout "reply action slaps $1 with a large trout"
<bot> jamessan: The operation succeeded.
@@ -56,23 +56,19 @@
This ``trout`` aka requires the plugin ``Reply`` to be loaded since it
provides the ``action`` command.
-LastFM
-^^^^^^
+Random percentage
+^^^^^^^^^^^^^^^^^
-Add an aka, ``lastfm``, which expects a last.fm username and replies with
-their most recently played item::
+Add an aka, ``randpercent``, which returns a random percentage value::
- @aka add lastfm "rss [format concat http://ws.audioscrobbler.com/1.0/user/
[format concat [web urlquote $1] /recenttracks.rss]]"
+ @aka add randpercent "squish [dice 1d100]%"
-This ``lastfm`` aka requires the following plugins to be loaded: ``RSS``,
-``Format`` and ``Web``.
+This requires the ``Filter`` and ``Games`` plugins to be loaded.
-``RSS`` provides ``rss``, ``Format`` provides ``concat`` and ``Web`` provides
-``urlquote``.
-
-Note that if the nested commands being aliased hadn't been quoted, then
-those commands would have been run immediately, and ``@lastfm`` would always
-reply with the same information, the result of those commands.
+Note that nested commands in an alias should be quoted, or they will only
+run once when you create the alias, and not each time the alias is
+called. (In this case, not quoting the nested command would mean that
+``@randpercent`` always responds with the same value!)
.. _commands-Aka:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Limnoria-master-2022-07-03/plugins/Aka/plugin.py
new/Limnoria-master-2022-09-27/plugins/Aka/plugin.py
--- old/Limnoria-master-2022-07-03/plugins/Aka/plugin.py 2022-06-23
22:31:17.000000000 +0200
+++ new/Limnoria-master-2022-09-27/plugins/Aka/plugin.py 2022-09-20
07:51:46.000000000 +0200
@@ -532,7 +532,7 @@
Trout
^^^^^
- Add an aka, trout, which expects a word as an argument::
+ Add an aka, ``trout``, which expects a word as an argument::
<jamessan> @aka add trout "reply action slaps $1 with a large trout"
<bot> jamessan: The operation succeeded.
@@ -542,23 +542,19 @@
This ``trout`` aka requires the plugin ``Reply`` to be loaded since it
provides the ``action`` command.
- LastFM
- ^^^^^^
+ Random percentage
+ ^^^^^^^^^^^^^^^^^
- Add an aka, ``lastfm``, which expects a last.fm username and replies with
- their most recently played item::
+ Add an aka, ``randpercent``, which returns a random percentage value::
- @aka add lastfm "rss [format concat
http://ws.audioscrobbler.com/1.0/user/ [format concat [web urlquote $1]
/recenttracks.rss]]"
+ @aka add randpercent "squish [dice 1d100]%"
- This ``lastfm`` aka requires the following plugins to be loaded: ``RSS``,
- ``Format`` and ``Web``.
+ This requires the ``Filter`` and ``Games`` plugins to be loaded.
- ``RSS`` provides ``rss``, ``Format`` provides ``concat`` and ``Web``
provides
- ``urlquote``.
-
- Note that if the nested commands being aliased hadn't been quoted, then
- those commands would have been run immediately, and ``@lastfm`` would
always
- reply with the same information, the result of those commands.
+ Note that nested commands in an alias should be quoted, or they will only
+ run once when you create the alias, and not each time the alias is
+ called. (In this case, not quoting the nested command would mean that
+ ``@randpercent`` always responds with the same value!)
"""
def __init__(self, irc):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Limnoria-master-2022-07-03/plugins/Alias/README.rst
new/Limnoria-master-2022-09-27/plugins/Alias/README.rst
--- old/Limnoria-master-2022-07-03/plugins/Alias/README.rst 2022-06-23
22:31:17.000000000 +0200
+++ new/Limnoria-master-2022-09-27/plugins/Alias/README.rst 2022-09-20
07:51:46.000000000 +0200
@@ -18,21 +18,23 @@
built-in Aka plugin instead (you can migrate your existing aliases using
the 'importaliasdatabase' command.
-To add an alias, `trout`, which expects a word as an argument::
+To add an alias, ``trout``, which expects a word as an argument::
<jamessan> @alias add trout "action slaps $1 with a large trout"
<bot> jamessan: The operation succeeded.
<jamessan> @trout me
* bot slaps me with a large trout
-To add an alias, `lastfm`, which expects a last.fm user and replies with
-their recently played items::
+Add an alias, ``randpercent``, which returns a random percentage value::
- @alias add lastfm "rss [format concat
http://ws.audioscrobbler.com/1.0/user/ [format concat [urlquote $1]
/recenttracks.rss]]"
+ @alias add randpercent "squish [dice 1d100]%"
-Note that if the nested commands being aliased hadn't been quoted, then
-those commands would have been run immediately, and `@lastfm` would always
-reply with the same information, the result of those commands.
+This requires the ``Filter`` and ``Games`` plugins to be loaded.
+
+Note that nested commands in an alias should be quoted, or they will only
+run once when you create the alias, and not each time the alias is
+called. (In this case, not quoting the nested command would mean that
+``@randpercent`` always responds with the same value!)
.. _commands-Alias:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Limnoria-master-2022-07-03/plugins/Alias/plugin.py
new/Limnoria-master-2022-09-27/plugins/Alias/plugin.py
--- old/Limnoria-master-2022-07-03/plugins/Alias/plugin.py 2022-06-23
22:31:17.000000000 +0200
+++ new/Limnoria-master-2022-09-27/plugins/Alias/plugin.py 2022-09-20
07:51:46.000000000 +0200
@@ -243,21 +243,23 @@
built-in Aka plugin instead (you can migrate your existing aliases using
the 'importaliasdatabase' command.
- To add an alias, `trout`, which expects a word as an argument::
+ To add an alias, ``trout``, which expects a word as an argument::
<jamessan> @alias add trout "action slaps $1 with a large trout"
<bot> jamessan: The operation succeeded.
<jamessan> @trout me
* bot slaps me with a large trout
- To add an alias, `lastfm`, which expects a last.fm user and replies with
- their recently played items::
+ Add an alias, ``randpercent``, which returns a random percentage value::
- @alias add lastfm "rss [format concat
http://ws.audioscrobbler.com/1.0/user/ [format concat [urlquote $1]
/recenttracks.rss]]"
+ @alias add randpercent "squish [dice 1d100]%"
- Note that if the nested commands being aliased hadn't been quoted, then
- those commands would have been run immediately, and `@lastfm` would always
- reply with the same information, the result of those commands.
+ This requires the ``Filter`` and ``Games`` plugins to be loaded.
+
+ Note that nested commands in an alias should be quoted, or they will only
+ run once when you create the alias, and not each time the alias is
+ called. (In this case, not quoting the nested command would mean that
+ ``@randpercent`` always responds with the same value!)
"""
def __init__(self, irc):
self.__parent = super(Alias, self)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/Limnoria-master-2022-07-03/plugins/Autocomplete/__init__.py
new/Limnoria-master-2022-09-27/plugins/Autocomplete/__init__.py
--- old/Limnoria-master-2022-07-03/plugins/Autocomplete/__init__.py
2022-06-23 22:31:17.000000000 +0200
+++ new/Limnoria-master-2022-09-27/plugins/Autocomplete/__init__.py
2022-09-20 07:51:46.000000000 +0200
@@ -61,6 +61,7 @@
from . import plugin
from importlib import reload
+
# In case we're being reloaded.
reload(config)
reload(plugin)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Limnoria-master-2022-07-03/plugins/Channel/plugin.py
new/Limnoria-master-2022-09-27/plugins/Channel/plugin.py
--- old/Limnoria-master-2022-07-03/plugins/Channel/plugin.py 2022-06-23
22:31:17.000000000 +0200
+++ new/Limnoria-master-2022-09-27/plugins/Channel/plugin.py 2022-09-20
07:51:46.000000000 +0200
@@ -381,8 +381,12 @@
msg.prefix, bannedNick)
raise callbacks.ArgumentError
elif bannedNick == irc.nick:
- self.log.warning('%q tried to make me kban myself.', msg.prefix)
- irc.error(_('I cowardly refuse to kickban myself.'))
+ if kick:
+ self.log.warning('%q tried to make me kban myself.',
msg.prefix)
+ irc.error(_('I cowardly refuse to kickban myself.'))
+ else:
+ self.log.warning('%q tried to make me ban myself.', msg.prefix)
+ irc.error(_('I cowardly refuse to ban myself.'))
return
if not reason:
reason = msg.nick
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Limnoria-master-2022-07-03/plugins/Config/test.py
new/Limnoria-master-2022-09-27/plugins/Config/test.py
--- old/Limnoria-master-2022-07-03/plugins/Config/test.py 2022-06-23
22:31:17.000000000 +0200
+++ new/Limnoria-master-2022-09-27/plugins/Config/test.py 2022-09-20
07:51:46.000000000 +0200
@@ -1,7 +1,7 @@
###
# Copyright (c) 2002-2005, Jeremiah Fincher
# Copyright (c) 2009, James McCoy
-# Copyright (c) 2010-2021, Valentin Lorentz
+# Copyright (c) 2010-2022, Valentin Lorentz
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
@@ -33,11 +33,20 @@
from supybot.test import *
import supybot.conf as conf
+import supybot.registry as registry
_letters = 'abcdefghijklmnopqrstuvwxyz'
def random_string():
return ''.join(random.choice(_letters) for _ in range(16))
+class Fruit(registry.OnlySomeStrings):
+ validStrings = ('Apple', 'Orange')
+
+group = conf.registerGroup(conf.supybot.plugins.Config, 'test')
+conf.registerGlobalValue(group, 'fruit',
+ Fruit('Orange', '''Must be a fruit'''))
+
+
class ConfigTestCase(ChannelPluginTestCase):
# We add utilities so there's something in supybot.plugins.
plugins = ('Config', 'User', 'Utilities', 'Web')
@@ -50,6 +59,16 @@
self.assertNotRegexp('config get supybot.reply', r'registry\.Group')
self.assertResponse('config supybot.protocols.irc.throttleTime', '0.0')
+ def testSetOnlysomestrings(self):
+ self.assertResponse('config supybot.plugins.Config.test.fruit Apple',
+ 'The operation succeeded.')
+ self.assertResponse('config supybot.plugins.Config.test.fruit orange',
+ 'The operation succeeded.')
+ self.assertResponse('config supybot.plugins.Config.test.fruit Tomatoe',
+ "Error: Valid values include 'Apple' and "
+ "'Orange', not 'Tomatoe'.")
+
+
def testList(self):
self.assertError('config list asldfkj')
self.assertError('config list supybot.asdfkjsldf')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Limnoria-master-2022-07-03/plugins/Ctcp/plugin.py
new/Limnoria-master-2022-09-27/plugins/Ctcp/plugin.py
--- old/Limnoria-master-2022-07-03/plugins/Ctcp/plugin.py 2022-06-23
22:31:17.000000000 +0200
+++ new/Limnoria-master-2022-09-27/plugins/Ctcp/plugin.py 2022-09-20
07:51:46.000000000 +0200
@@ -64,7 +64,7 @@
def callCommand(self, command, irc, msg, *args, **kwargs):
if conf.supybot.abuse.flood.ctcp():
now = time.time()
- for (ignore, expiration) in self.ignores.items():
+ for (ignore, expiration) in list(self.ignores.items()):
if expiration < now:
del self.ignores[ignore]
elif ircutils.hostmaskPatternEqual(ignore, msg.prefix):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/Limnoria-master-2022-07-03/plugins/Fediverse/__init__.py
new/Limnoria-master-2022-09-27/plugins/Fediverse/__init__.py
--- old/Limnoria-master-2022-07-03/plugins/Fediverse/__init__.py
2022-06-23 22:31:17.000000000 +0200
+++ new/Limnoria-master-2022-09-27/plugins/Fediverse/__init__.py
2022-09-20 07:51:46.000000000 +0200
@@ -53,6 +53,7 @@
from . import plugin
from importlib import reload
+
# In case we're being reloaded.
reload(config)
reload(plugin)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/Limnoria-master-2022-07-03/plugins/Fediverse/plugin.py
new/Limnoria-master-2022-09-27/plugins/Fediverse/plugin.py
--- old/Limnoria-master-2022-07-03/plugins/Fediverse/plugin.py 2022-06-23
22:31:17.000000000 +0200
+++ new/Limnoria-master-2022-09-27/plugins/Fediverse/plugin.py 2022-09-20
07:51:46.000000000 +0200
@@ -38,6 +38,7 @@
from supybot.i18n import PluginInternationalization
from . import activitypub as ap
+from .utils import parse_xsd_duration
importlib.reload(ap)
@@ -49,6 +50,10 @@
_username_regexp = re.compile("@(?P<localuser>[^@ ]+)@(?P<hostname>[^@ ]+)")
+def html_to_text(html):
+ return utils.web.htmlToText(html).split("\n", 1)[0].strip()
+
+
class FediverseHttp(httpserver.SupyHTTPServerCallback):
name = "minimal ActivityPub server"
defaultResponse = _(
@@ -222,18 +227,40 @@
name = actor.get("name", username)
return "\x02%s\x02 (@%s@%s)" % (name, username, hostname)
+ def _format_author(self, irc, author):
+ if isinstance(author, str):
+ # it's an URL
+ try:
+ author = self._get_actor(irc, author)
+ except ap.ActivityPubError as e:
+ return _("<error: %s>") % str(e)
+ else:
+ return self._format_actor_fullname(author)
+ elif isinstance(author, dict):
+ if author.get("type") == "Group":
+ # Typically, there is an actor named "Default <username>
channel"
+ # on PeerTube, which we do not want to show.
+ return None
+ if author.get("id"):
+ return self._format_author(irc, author["id"])
+ elif isinstance(author, list):
+ return format(
+ "%L",
+ filter(
+ bool, [self._format_author(irc, item) for item in author]
+ ),
+ )
+ else:
+ return "<unknown>"
+
def _format_status(self, irc, msg, status):
if status["type"] == "Create":
return self._format_status(irc, msg, status["object"])
elif status["type"] == "Note":
- author_url = status["attributedTo"]
- try:
- author = self._get_actor(irc, author_url)
- except ap.ActivityPubError as e:
- author_fullname = _("<error: %s>") % str(e)
- else:
- author_fullname = self._format_actor_fullname(author)
cw = status.get("summary")
+ author_fullname = self._format_author(
+ irc, status.get("attributedTo")
+ )
if cw:
if self.registryValue(
"format.statuses.showContentWithCW",
@@ -246,7 +273,7 @@
% (
author_fullname,
cw,
- utils.web.htmlToText(status["content"]),
+ html_to_text(status["content"]),
)
]
else:
@@ -258,7 +285,7 @@
_("%s: %s")
% (
author_fullname,
- utils.web.htmlToText(status["content"]),
+ html_to_text(status["content"]),
)
]
@@ -275,6 +302,17 @@
return self._format_status(irc, msg, status)
except ap.ActivityPubProtocolError as e:
return "<Could not fetch status: %s>" % e.args[0]
+ elif status["type"] == "Video":
+ author_fullname = self._format_author(
+ irc, status.get("attributedTo")
+ )
+ return format(
+ _("\x02%s\x02 (%T) by %s: %s"),
+ status["name"],
+ abs(parse_xsd_duration(status["duration"]).total_seconds()),
+ author_fullname,
+ html_to_text(status["content"]),
+ )
else:
assert False, "Unknown status type %s: %r" % (
status["type"],
@@ -292,14 +330,14 @@
_("%s: %s")
% (
self._format_actor_fullname(actor),
- utils.web.htmlToText(actor["summary"]),
+ html_to_text(actor["summary"]),
)
)
def _format_profile(self, irc, msg, actor):
return _("%s: %s") % (
self._format_actor_fullname(actor),
- utils.web.htmlToText(actor["summary"]),
+ html_to_text(actor["summary"]),
)
def usernameSnarfer(self, irc, msg, match):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Limnoria-master-2022-07-03/plugins/Fediverse/test.py
new/Limnoria-master-2022-09-27/plugins/Fediverse/test.py
--- old/Limnoria-master-2022-07-03/plugins/Fediverse/test.py 2022-06-23
22:31:17.000000000 +0200
+++ new/Limnoria-master-2022-09-27/plugins/Fediverse/test.py 2022-09-20
07:51:46.000000000 +0200
@@ -60,6 +60,10 @@
BOOSTED_DATA,
BOOSTED_ACTOR_URL,
BOOSTED_ACTOR_DATA,
+ PEERTUBE_VIDEO_URL,
+ PEERTUBE_VIDEO_DATA,
+ PEERTUBE_ACTOR_URL,
+ PEERTUBE_ACTOR_DATA,
)
@@ -430,6 +434,20 @@
+
"<https://example.net/system/media_attachments/image.png>",
)
+ def testVideo(self):
+ expected_requests = [
+ (PEERTUBE_VIDEO_URL, PEERTUBE_VIDEO_DATA),
+ (PEERTUBE_ACTOR_URL, PEERTUBE_ACTOR_DATA),
+ ]
+
+ with self.mockRequests(expected_requests):
+ self.assertResponse(
+ "status https://example.org/w/gABde9e210FGHre",
+ "\x02name of video\x02 (1 hour, 26 minutes, and 0 seconds) "
+ "by \x02chocobozzz\x02 (@[email protected]): "
+ "description of the video with a second line",
+ )
+
def testStatusUrlSnarferDisabled(self):
with self.mockWebfingerSupport("not called"), self.mockRequests([]):
self.assertSnarfNoResponse(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/Limnoria-master-2022-07-03/plugins/Fediverse/test_data.py
new/Limnoria-master-2022-09-27/plugins/Fediverse/test_data.py
--- old/Limnoria-master-2022-07-03/plugins/Fediverse/test_data.py
2022-06-23 22:31:17.000000000 +0200
+++ new/Limnoria-master-2022-09-27/plugins/Fediverse/test_data.py
2022-09-20 07:51:46.000000000 +0200
@@ -384,3 +384,124 @@
"endpoints": {"sharedInbox": "https://example.net/inbox"},
}
BOOSTED_ACTOR_DATA = json.dumps(BOOSTED_ACTOR_VALUE).encode()
+
+PEERTUBE_ACTOR_VALUE = {
+ "@context": [
+ "https://www.w3.org/ns/activitystreams",
+ "https://w3id.org/security/v1",
+ {"RsaSignature2017": "https://w3id.org/security#RsaSignature2017"},
+ {
+ "pt": "https://joinpeertube.org/ns#",
+ "sc": "http://schema.org/",
+ "playlists": {"@id": "pt:playlists", "@type": "@id"},
+ },
+ ],
+ "type": "Person",
+ "id": "https://peertube.cpy.re/accounts/chocobozzz",
+ "following": "https://peertube.cpy.re/accounts/chocobozzz/following",
+ "followers": "https://peertube.cpy.re/accounts/chocobozzz/followers",
+ "playlists": "https://peertube.cpy.re/accounts/chocobozzz/playlists",
+ "inbox": "https://peertube.cpy.re/accounts/chocobozzz/inbox",
+ "outbox": "https://peertube.cpy.re/accounts/chocobozzz/outbox",
+ "preferredUsername": "chocobozzz",
+ "url": "https://peertube.cpy.re/accounts/chocobozzz",
+ "name": "chocobozzz",
+ "published": "2017-11-28T08:48:24.271Z",
+ "summary": None,
+}
+PEERTUBE_ACTOR_DATA = json.dumps(PEERTUBE_ACTOR_VALUE).encode()
+PEERTUBE_ACTOR_URL = "https://peertube.cpy.re/accounts/chocobozzz"
+
+
+PEERTUBE_VIDEO_VALUE = {
+ "@context": [
+ "https://www.w3.org/ns/activitystreams",
+ "https://w3id.org/security/v1",
+ {"RsaSignature2017": "https://w3id.org/security#RsaSignature2017"},
+ {
+ "pt": "https://joinpeertube.org/ns#",
+ "sc": "http://schema.org/",
+ "Hashtag": "as:Hashtag",
+ "uuid": "sc:identifier",
+ "category": "sc:category",
+ "licence": "sc:license",
+ "subtitleLanguage": "sc:subtitleLanguage",
+ "sensitive": "as:sensitive",
+ "language": "sc:inLanguage",
+ "icons": "as:icon",
+ "isLiveBroadcast": "sc:isLiveBroadcast",
+ "liveSaveReplay": {
+ "@type": "sc:Boolean",
+ "@id": "pt:liveSaveReplay",
+ },
+ "permanentLive": {
+ "@type": "sc:Boolean",
+ "@id": "pt:permanentLive",
+ },
+ "latencyMode": {"@type": "sc:Number", "@id": "pt:latencyMode"},
+ "Infohash": "pt:Infohash",
+ "originallyPublishedAt": "sc:datePublished",
+ "views": {"@type": "sc:Number", "@id": "pt:views"},
+ "state": {"@type": "sc:Number", "@id": "pt:state"},
+ "size": {"@type": "sc:Number", "@id": "pt:size"},
+ "fps": {"@type": "sc:Number", "@id": "pt:fps"},
+ "commentsEnabled": {
+ "@type": "sc:Boolean",
+ "@id": "pt:commentsEnabled",
+ },
+ "downloadEnabled": {
+ "@type": "sc:Boolean",
+ "@id": "pt:downloadEnabled",
+ },
+ "waitTranscoding": {
+ "@type": "sc:Boolean",
+ "@id": "pt:waitTranscoding",
+ },
+ "support": {"@type": "sc:Text", "@id": "pt:support"},
+ "likes": {"@id": "as:likes", "@type": "@id"},
+ "dislikes": {"@id": "as:dislikes", "@type": "@id"},
+ "shares": {"@id": "as:shares", "@type": "@id"},
+ "comments": {"@id": "as:comments", "@type": "@id"},
+ },
+ ],
+ "to": ["https://www.w3.org/ns/activitystreams#Public"],
+ "type": "Video",
+ "name": "name of video",
+ "duration": "PT5160S",
+ "tag": [{"type": "Hashtag", "name": "vostfr"}],
+ "category": {"identifier": "2", "name": "Films"},
+ "licence": {"identifier": "4", "name": "Attribution - Non Commercial"},
+ "language": {"identifier": "en", "name": "English"},
+ "views": 13718,
+ "sensitive": False,
+ "waitTranscoding": False,
+ "state": 1,
+ "commentsEnabled": True,
+ "downloadEnabled": True,
+ "published": "2017-10-23T07:54:38.155Z",
+ "originallyPublishedAt": None,
+ "updated": "2022-07-13T07:03:12.373Z",
+ "mediaType": "text/markdown",
+ "content": "description of <strong>the</strong> video\r\nwith a second
line",
+ "support": None,
+ "subtitleLanguage": [],
+ "icon": [
+ # redacted
+ ],
+ "url": [
+ # redacted
+ ],
+ "attributedTo": [
+ {"type": "Person", "id": PEERTUBE_ACTOR_URL},
+ {
+ "type": "Group",
+ "id": ACTOR_URL,
+ },
+ ],
+ "isLiveBroadcast": False,
+ "liveSaveReplay": None,
+ "permanentLive": None,
+ "latencyMode": None,
+}
+PEERTUBE_VIDEO_DATA = json.dumps(PEERTUBE_VIDEO_VALUE).encode()
+PEERTUBE_VIDEO_URL = "https://example.org/w/gABde9e210FGHre"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/Limnoria-master-2022-07-03/plugins/Fediverse/utils.py
new/Limnoria-master-2022-09-27/plugins/Fediverse/utils.py
--- old/Limnoria-master-2022-07-03/plugins/Fediverse/utils.py 1970-01-01
01:00:00.000000000 +0100
+++ new/Limnoria-master-2022-09-27/plugins/Fediverse/utils.py 2022-09-20
07:51:46.000000000 +0200
@@ -0,0 +1,63 @@
+###
+# Copyright (c) 2022, Valentin Lorentz
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions, and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions, and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# * Neither the name of the author of this software nor the name of
+# contributors to this software may be used to endorse or promote products
+# derived from this software without specific prior written consent.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+###
+
+import re
+import datetime
+
+# Credits for the regexp and function:
https://stackoverflow.com/a/2765366/539465
+_XSD_DURATION_RE = re.compile(
+ "(?P<sign>-?)P"
+ "(?:(?P<years>\d+)Y)?"
+ "(?:(?P<months>\d+)M)?"
+ "(?:(?P<days>\d+)D)?"
+ "(?:T(?:(?P<hours>\d+)H)?(?:(?P<minutes>\d+)M)?(?:(?P<seconds>\d+)S)?)?"
+)
+
+
+def parse_xsd_duration(s):
+ """Parses this format to a timedelta:
+ https://www.w3.org/TR/xmlschema11-2/#duration"""
+ # Fetch the match groups with default value of 0 (not None)
+ duration = _XSD_DURATION_RE.match(s).groupdict(0)
+
+ # Create the timedelta object from extracted groups
+ delta = datetime.timedelta(
+ days=int(duration["days"])
+ + (int(duration["months"]) * 30)
+ + (int(duration["years"]) * 365),
+ hours=int(duration["hours"]),
+ minutes=int(duration["minutes"]),
+ seconds=int(duration["seconds"]),
+ )
+
+ if duration["sign"] == "-":
+ delta *= -1
+
+ return delta
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/Limnoria-master-2022-07-03/plugins/Geography/plugin.py
new/Limnoria-master-2022-09-27/plugins/Geography/plugin.py
--- old/Limnoria-master-2022-07-03/plugins/Geography/plugin.py 2022-06-23
22:31:17.000000000 +0200
+++ new/Limnoria-master-2022-09-27/plugins/Geography/plugin.py 2022-09-20
07:51:46.000000000 +0200
@@ -150,7 +150,8 @@
continue
offset_seconds = int(
- datetime.datetime.now(tz=timezone).utcoffset().total_seconds())
+ 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
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Limnoria-master-2022-07-03/plugins/Geography/test.py
new/Limnoria-master-2022-09-27/plugins/Geography/test.py
--- old/Limnoria-master-2022-07-03/plugins/Geography/test.py 2022-06-23
22:31:17.000000000 +0200
+++ new/Limnoria-master-2022-09-27/plugins/Geography/test.py 2022-09-20
07:51:46.000000000 +0200
@@ -83,7 +83,7 @@
with patch.object(wikidata, "timezone_from_uri", return_value=tz):
self.assertRegexp(
"timezone Newfoundland",
- r"Canada/Newfoundland \(currently UTC-[23]:30\)"
+ r"Canada/Newfoundland \(currently UTC-[23]:30\)",
)
tz = pytz.timezone("Asia/Kolkata")
@@ -111,7 +111,7 @@
with patch.object(wikidata, "timezone_from_uri", return_value=tz):
self.assertRegexp(
"timezone Newfoundland",
- r"Canada/Newfoundland \(currently UTC-[23]:30\)"
+ r"Canada/Newfoundland \(currently UTC-[23]:30\)",
)
tz = zoneinfo.ZoneInfo("Asia/Kolkata")
@@ -144,9 +144,7 @@
self.assertRegexp(
"timezone Delhi", r"Asia/Kolkata \(currently UTC\+5:30\)"
)
- self.assertRegexp(
- "timezone Newfoundland", r"UTC-[23]: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-07-03/plugins/Geography/wikidata.py
new/Limnoria-master-2022-09-27/plugins/Geography/wikidata.py
--- old/Limnoria-master-2022-07-03/plugins/Geography/wikidata.py
2022-06-23 22:31:17.000000000 +0200
+++ new/Limnoria-master-2022-09-27/plugins/Geography/wikidata.py
2022-09-20 07:51:46.000000000 +0200
@@ -134,10 +134,12 @@
"""Returns a :class:datetime.tzinfo object, given a Wikidata Q-ID.
eg. ``"Q60"`` for New York City."""
for tztype in [
- "http://www.wikidata.org/entity/Q17272692", # IANA timezones first
- "http://www.wikidata.org/entity/Q12143", # any timezone as a fallback
+ "http://www.wikidata.org/entity/Q17272692", # IANA timezones first
+ "http://www.wikidata.org/entity/Q12143", # any timezone as a fallback
]:
- data = _query_sparql(TIMEZONE_QUERY.substitute(subject=location_uri,
tztype=tztype))
+ data = _query_sparql(
+ TIMEZONE_QUERY.substitute(subject=location_uri, tztype=tztype)
+ )
results = data["results"]["bindings"]
for result in results:
if "tzid" in result:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Limnoria-master-2022-07-03/plugins/Math/plugin.py
new/Limnoria-master-2022-09-27/plugins/Math/plugin.py
--- old/Limnoria-master-2022-07-03/plugins/Math/plugin.py 2022-06-23
22:31:17.000000000 +0200
+++ new/Limnoria-master-2022-09-27/plugins/Math/plugin.py 2022-09-20
07:51:46.000000000 +0200
@@ -166,8 +166,8 @@
"""
try:
self.log.info('evaluating %q from %s', text, msg.prefix)
- x = safe_eval(text, allow_ints=True)
- irc.reply(str(x))
+ result = safe_eval(text, allow_ints=True)
+ float(result) # fail early if it is too large to be displayed
except OverflowError:
maxFloat = math.ldexp(0.9999999999999999, 1024)
irc.error(_('The answer exceeded %s or so.') % maxFloat)
@@ -177,6 +177,17 @@
irc.error(_('%s is not a defined function.') % str(e).split()[1])
except Exception as e:
irc.error(utils.exnToString(e))
+ else:
+ try:
+ result_str = str(result)
+ except ValueError as e:
+ # Probably too large to be converted to string; go through
+ # floats instead.
+ #
https://docs.python.org/3/library/stdtypes.html#int-max-str-digits
+ # (Depending on configuration, this may be dead code because it
+ # is caught by the float() check above.
+ result_str = str(float(result))
+ irc.reply(result_str)
icalc = wrap(icalc, [('checkCapability', 'trusted'), 'text'])
_rpnEnv = {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Limnoria-master-2022-07-03/plugins/Math/test.py
new/Limnoria-master-2022-09-27/plugins/Math/test.py
--- old/Limnoria-master-2022-07-03/plugins/Math/test.py 2022-06-23
22:31:17.000000000 +0200
+++ new/Limnoria-master-2022-09-27/plugins/Math/test.py 2022-09-20
07:51:46.000000000 +0200
@@ -112,7 +112,10 @@
self.assertNotError('calc (1600 * 1200) - 2*(1024*1280)')
self.assertNotError('calc 3-2*4')
self.assertNotError('calc (1600 * 1200)-2*(1024*1280)')
- self.assertError('calc factorial(20000)')
+ self.assertResponse('calc factorial(20000)',
+ 'Error: factorial argument too large')
+ self.assertResponse('calc factorial(20000) / factorial(19999)',
+ 'Error: factorial argument too large')
def testCalcNoNameError(self):
self.assertRegexp('calc foobar(x)', 'foobar is not a defined function')
@@ -147,7 +150,10 @@
self.assertResponse('icalc 1^1', '0')
self.assertResponse('icalc 10**24', '1' + '0'*24)
self.assertRegexp('icalc 49/6', '8.16')
- self.assertNotError('icalc factorial(20000)')
+ self.assertRegexp('icalc factorial(20000)',
+ 'Error: The answer exceeded')
+ self.assertResponse('icalc factorial(20000) / factorial(19999)',
+ '20000.0')
def testRpn(self):
self.assertResponse('rpn 5 2 +', '7')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/Limnoria-master-2022-07-03/plugins/MessageParser/plugin.py
new/Limnoria-master-2022-09-27/plugins/MessageParser/plugin.py
--- old/Limnoria-master-2022-07-03/plugins/MessageParser/plugin.py
2022-06-23 22:31:17.000000000 +0200
+++ new/Limnoria-master-2022-09-27/plugins/MessageParser/plugin.py
2022-09-20 07:51:46.000000000 +0200
@@ -163,6 +163,20 @@
channel = msg.channel
if not channel:
return
+
+ if 'batch' in msg.server_tags:
+ parent_batches = irc.state.getParentBatches(msg)
+ parent_batch_types = [batch.type for batch in parent_batches]
+ if 'chathistory' in parent_batch_types:
+ # Either sent automatically by the server upon join,
+ # or triggered by a plugin (why?!)
+ # Either way, replying to messages from the history would
+ # look weird, because they may have been sent a while ago,
+ # and we may have already answered them.
+ # (this is the same behavior as in Owner.doPrivmsg and
+ # PluginRegexp.doPrivmsg)
+ return
+
if self.registryValue('enable', channel, irc.network):
actions = []
results = []
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/Limnoria-master-2022-07-03/plugins/MessageParser/test.py
new/Limnoria-master-2022-09-27/plugins/MessageParser/test.py
--- old/Limnoria-master-2022-07-03/plugins/MessageParser/test.py
2022-06-23 22:31:17.000000000 +0200
+++ new/Limnoria-master-2022-09-27/plugins/MessageParser/test.py
2022-09-20 07:51:46.000000000 +0200
@@ -116,10 +116,31 @@
def testTrigger(self):
self.assertNotError('messageparser add "stuff" "echo i saw some
stuff"')
- self.feedMsg('this message has some stuff in it')
+ self.irc.feedMsg(ircmsgs.IrcMsg(
+ prefix=self.prefix,
+ command='PRIVMSG',
+ args=(self.channel, 'this message has some stuff in it')))
m = self.getMsg(' ')
self.assertTrue(str(m).startswith('PRIVMSG #test :i saw some stuff'))
+ def testIgnoreChathistory(self):
+ self.assertNotError('messageparser add "stuff" "echo i saw some
stuff"')
+
+ self.irc.feedMsg(ircmsgs.IrcMsg(
+ command='BATCH',
+ args=('+123', 'chathistory', self.channel)))
+ self.irc.feedMsg(ircmsgs.IrcMsg(
+ server_tags={'batch': '123'},
+ prefix=self.prefix,
+ command='PRIVMSG',
+ args=(self.channel, 'this message has some stuff in it')))
+ self.irc.feedMsg(ircmsgs.IrcMsg(
+ command='BATCH',
+ args=('-123',)))
+
+ m = self.getMsg(' ')
+ self.assertFalse(m)
+
def testMaxTriggers(self):
self.assertNotError('messageparser add "stuff" "echo i saw some
stuff"')
self.assertNotError('messageparser add "sbd" "echo i saw somebody"')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/Limnoria-master-2022-07-03/plugins/MoobotFactoids/plugin.py
new/Limnoria-master-2022-09-27/plugins/MoobotFactoids/plugin.py
--- old/Limnoria-master-2022-07-03/plugins/MoobotFactoids/plugin.py
2022-06-23 22:31:17.000000000 +0200
+++ new/Limnoria-master-2022-09-27/plugins/MoobotFactoids/plugin.py
2022-09-20 07:51:46.000000000 +0200
@@ -293,12 +293,23 @@
``@something is something`` And when you call ``@something`` the bot says
``something is something``.
- If you want factoid to be in different format say (for example):
+ If you want the factoid to be in different format say (for example):
``@Hi is <reply> Hello`` And when you call ``@hi`` the bot says ``Hello.``
If you want the bot to use /mes with Factoids, that is possible too.
``@test is <action> tests.`` and everytime when someone calls for
``test`` the bot answers ``* bot tests.``
+
+ If you want the factoid to have random answers say (for example):
+ ``@fruit is <reply> (orange|apple|banana)``. So when ``@fruit`` is called
+ the bot will reply with one of the listed fruits (random): ``orange``.
+
+ If you want to replace the value of the factoid, for example:
+ ``@no Hi is <reply> Hey`` when you call ``@hi`` the bot says ``Hey``.
+
+ If you want to append to the current value of a factoid say:
+ ``@Hi is also Hello``, so that when you call ``@hi`` the
+ bot says ``Hey, or Hello.``
"""
callBefore = ['Dunno']
def __init__(self, irc):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Limnoria-master-2022-07-03/plugins/Owner/plugin.py
new/Limnoria-master-2022-09-27/plugins/Owner/plugin.py
--- old/Limnoria-master-2022-07-03/plugins/Owner/plugin.py 2022-06-23
22:31:17.000000000 +0200
+++ new/Limnoria-master-2022-09-27/plugins/Owner/plugin.py 2022-09-20
07:51:46.000000000 +0200
@@ -309,8 +309,9 @@
# Either sent automatically by the server upon join,
# or triggered by a plugin (why?!)
# Either way, replying to commands from the history would
- # look weird, because it may have been sent a while ago,
- # and we may have already answered to it.
+ # look weird, because they may have been sent a while ago,
+ # and we may have already answered to them.
+ # (this is the same behavior as in PluginRegexp.doPrivmsg)
return
self._doPrivmsgs(irc, msg)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Limnoria-master-2022-07-03/plugins/Poll/__init__.py
new/Limnoria-master-2022-09-27/plugins/Poll/__init__.py
--- old/Limnoria-master-2022-07-03/plugins/Poll/__init__.py 2022-06-23
22:31:17.000000000 +0200
+++ new/Limnoria-master-2022-09-27/plugins/Poll/__init__.py 2022-09-20
07:51:46.000000000 +0200
@@ -53,6 +53,7 @@
from . import plugin
from importlib import reload
+
# In case we're being reloaded.
reload(config)
reload(plugin)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Limnoria-master-2022-07-03/plugins/Poll/plugin.py
new/Limnoria-master-2022-09-27/plugins/Poll/plugin.py
--- old/Limnoria-master-2022-07-03/plugins/Poll/plugin.py 2022-06-23
22:31:17.000000000 +0200
+++ new/Limnoria-master-2022-09-27/plugins/Poll/plugin.py 2022-09-20
07:51:46.000000000 +0200
@@ -131,7 +131,7 @@
poll_id = max(self._polls[(irc.network, channel)], default=0) + 1
- answers = [(answer.split()[0], answer) for answer in answers]
+ answers = [(answer.split()[0].casefold(), answer) for answer in
answers]
answer_id_counts = collections.Counter(
id_ for (id_, _) in answers
@@ -149,7 +149,10 @@
)
self._polls[(irc.network, channel)][poll_id] = Poll(
- question=question, answers=dict(answers), votes={}, open=True
+ question=question,
+ answers=dict(answers),
+ votes=ircutils.IrcDict(),
+ open=True,
)
irc.replySuccess(_("Poll # %d created.") % poll_id)
@@ -191,6 +194,8 @@
if msg.nick in poll.votes:
irc.error(_("You already voted on this poll."), Raise=True)
+ answer_id = answer_id.casefold()
+
if answer_id not in poll.answers:
irc.error(
format(
@@ -218,11 +223,32 @@
counts.update({answer_id: 0 for answer_id in poll.answers})
results = [
- format(_("%n for %s"), (v, "vote"), k)
+ format(_("%n for %s"), (v, _("vote")), poll.answers[k].split()[0])
for (k, v) in counts.most_common()
]
irc.replies(results)
+ @wrap(["channel"])
+ def list(self, irc, msg, args, channel):
+ """[<channel>]
+
+ Lists open polls in the <channel>."""
+ results = [
+ format(
+ _("%i: %s (%n)"),
+ poll_id,
+ poll.question,
+ (len(poll.votes), _("vote")),
+ )
+ for (poll_id, poll) in self._polls[(irc.network, channel)].items()
+ if poll.open
+ ]
+
+ if results:
+ irc.replies(results)
+ else:
+ irc.reply(_("There are no open polls."))
+
Class = Poll_
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Limnoria-master-2022-07-03/plugins/Poll/test.py
new/Limnoria-master-2022-09-27/plugins/Poll/test.py
--- old/Limnoria-master-2022-07-03/plugins/Poll/test.py 2022-06-23
22:31:17.000000000 +0200
+++ new/Limnoria-master-2022-09-27/plugins/Poll/test.py 2022-09-20
07:51:46.000000000 +0200
@@ -49,6 +49,17 @@
"2 votes for No, 1 vote for Yes, and 0 votes for Maybe",
)
+ def testNoResults(self):
+ self.assertResponse(
+ 'poll add "Is this a test?" "Yes" "No" "Maybe"',
+ "The operation succeeded. Poll # 1 created.",
+ )
+
+ self.assertResponse(
+ "results 1",
+ "0 votes for Yes, 0 votes for No, and 0 votes for Maybe",
+ )
+
def testDoubleVoting(self):
self.assertResponse(
'poll add "Is this a test?" "Yes" "No" "Maybe"',
@@ -62,6 +73,11 @@
"voter1: Error: You already voted on this poll.",
frm="voter1!foo@bar",
)
+ self.assertResponse(
+ "vote 1 Yes",
+ "VOTER1: Error: You already voted on this poll.",
+ frm="VOTER1!foo@bar",
+ )
self.assertRegexp(
"results 1",
@@ -115,10 +131,58 @@
def testDuplicateId(self):
self.assertResponse(
'poll add "Is this a test?" "Yes" "Yes" "Maybe"',
- "Error: Duplicate answer identifier(s): Yes",
+ "Error: Duplicate answer identifier(s): yes",
)
self.assertResponse(
'poll add "Is this a test?" "Yes totally" "Yes and no" "Maybe"',
- "Error: Duplicate answer identifier(s): Yes",
+ "Error: Duplicate answer identifier(s): yes",
+ )
+
+ def testCaseInsensitive(self):
+ self.assertResponse(
+ 'poll add "Is this a test?" "Ye??" "No" "Maybe"',
+ "The operation succeeded. Poll # 1 created.",
+ )
+
+ self.assertNotError("vote 1 Ye??", frm="voter1!foo@bar")
+ self.assertNotError("vote 1 yESS", frm="voter2!foo@bar")
+ self.assertNotError("vote 1 no", frm="voter3!foo@bar")
+
+ self.assertResponse(
+ "results 1",
+ "2 votes for Ye??, 1 vote for No, and 0 votes for Maybe",
+ )
+
+ def testList(self):
+ self.assertResponse("poll list", "There are no open polls.")
+
+ self.assertResponse(
+ 'poll add "Is this a test?" "Yes" "No" "Maybe"',
+ "The operation succeeded. Poll # 1 created.",
+ )
+ self.assertResponse("poll list", "1: Is this a test? (0 votes)")
+
+ self.assertNotError("vote 1 Yes", frm="voter1!foo@bar")
+ self.assertResponse("poll list", "1: Is this a test? (1 vote)")
+
+ self.assertNotError("vote 1 No", frm="voter2!foo@bar")
+ self.assertResponse("poll list", "1: Is this a test? (2 votes)")
+
+ self.assertResponse(
+ 'poll add "Is this another test?" "Yes" "No" "Maybe"',
+ "The operation succeeded. Poll # 2 created.",
+ )
+ self.assertResponse(
+ "poll list",
+ "1: Is this a test? (2 votes) and 2: Is this another test? (0
votes)",
)
+
+ self.assertNotError("poll close 1")
+ self.assertResponse(
+ "poll list",
+ "2: Is this another test? (0 votes)",
+ )
+
+ self.assertNotError("poll close 2")
+ self.assertResponse("poll list", "There are no open polls.")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Limnoria-master-2022-07-03/plugins/RSS/plugin.py
new/Limnoria-master-2022-09-27/plugins/RSS/plugin.py
--- old/Limnoria-master-2022-07-03/plugins/RSS/plugin.py 2022-06-23
22:31:17.000000000 +0200
+++ new/Limnoria-master-2022-09-27/plugins/RSS/plugin.py 2022-09-20
07:51:46.000000000 +0200
@@ -356,8 +356,17 @@
handlers.append(ProxyHandler(
{'https': utils.force(utils.web.proxy())}))
with feed.lock:
- d = feedparser.parse(feed.url, etag=feed.etag,
- modified=feed.modified, handlers=handlers)
+ try:
+ d = feedparser.parse(feed.url, etag=feed.etag,
+ modified=feed.modified, handlers=handlers)
+ except socket.error as e:
+ self.log.warning("Network error while fetching <%s>: %s",
+ feed.url, e)
+ feed.last_exception = e
+ return
+ except Exception as e:
+ self.log.error("Failed to fetch <%s>: %s", feed.url, e)
+ raise # reraise so @log.firewall prints the traceback
if 'status' not in d or d.status != 304: # Not modified
if 'etag' in d:
feed.etag = d.etag
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Limnoria-master-2022-07-03/plugins/RSS/test.py
new/Limnoria-master-2022-09-27/plugins/RSS/test.py
--- old/Limnoria-master-2022-07-03/plugins/RSS/test.py 2022-06-23
22:31:17.000000000 +0200
+++ new/Limnoria-master-2022-09-27/plugins/RSS/test.py 2022-09-20
07:51:46.000000000 +0200
@@ -31,6 +31,7 @@
import functools
from unittest.mock import patch
+import socket
import sys
import feedparser
@@ -362,7 +363,22 @@
timeFastForward(1.1)
mock._data = not_well_formed
self.assertRegexp('rss http://example.com/',
- 'Parser error')
+ 'Parser error: .*mismatch')
+
+ def testSocketError(self):
+ class MockResponse:
+ headers = {}
+ url = ''
+ def read(self):
+ raise socket.error("oh no")
+
+ def close(self):
+ pass
+ mock = MockResponse()
+ with patch("urllib.request.OpenerDirector.open", return_value=mock):
+ timeFastForward(1.1)
+ self.assertRegexp('rss http://example.com/',
+ 'Parser error: .*oh no')
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-2022-07-03/plugins/Time/plugin.py
new/Limnoria-master-2022-09-27/plugins/Time/plugin.py
--- old/Limnoria-master-2022-07-03/plugins/Time/plugin.py 2022-06-23
22:31:17.000000000 +0200
+++ new/Limnoria-master-2022-09-27/plugins/Time/plugin.py 2022-09-20
07:51:46.000000000 +0200
@@ -72,10 +72,16 @@
except ImportError:
tzlocal = None
+
+# Note: Python 3.6 does not support empty pattern matches, see:
+# https://docs.python.org/3/library/re.html#re.split
+_SECONDS_SPLIT_RE = re.compile('(?<=[a-z]) ?')
+
+
class Time(callbacks.Plugin):
"""This plugin allows you to use different time-related functions."""
@internationalizeDocstring
- def seconds(self, irc, msg, args):
+ def seconds(self, irc, msg, args, text):
"""[<years>y] [<weeks>w] [<days>d] [<hours>h] [<minutes>m] [<seconds>s]
Returns the number of seconds in the number of <years>, <weeks>,
@@ -84,11 +90,13 @@
Useful for scheduling events at a given number of seconds in the
future.
"""
- if not args:
- raise callbacks.ArgumentError
seconds = 0
- for arg in args:
- if not arg or arg[-1] not in 'ywdhms':
+ if not text:
+ raise callbacks.ArgumentError
+ for arg in _SECONDS_SPLIT_RE.split(text):
+ if not arg:
+ continue
+ if arg[-1] not in 'ywdhms':
raise callbacks.ArgumentError
(s, kind) = arg[:-1], arg[-1]
try:
@@ -108,6 +116,7 @@
elif kind == 's':
seconds += i
irc.reply(str(seconds))
+ seconds = wrap(seconds, ['text'])
@internationalizeDocstring
def at(self, irc, msg, args, s=None):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Limnoria-master-2022-07-03/plugins/Time/test.py
new/Limnoria-master-2022-09-27/plugins/Time/test.py
--- old/Limnoria-master-2022-07-03/plugins/Time/test.py 2022-06-23
22:31:17.000000000 +0200
+++ new/Limnoria-master-2022-09-27/plugins/Time/test.py 2022-09-20
07:51:46.000000000 +0200
@@ -88,6 +88,17 @@
self.assertResponse('seconds 1y 1s', '31536001')
self.assertResponse('seconds 1w 1s', '604801')
+ @skipIf(sys.version_info < (3, 7, 0),
+ "Python 3.6 does not support empty pattern matches, see: "
+ "https://docs.python.org/3/library/re.html#re.split")
+ def testSecondsNoSpace(self):
+ self.assertResponse('seconds 1m1s', '61')
+ self.assertResponse('seconds 1h1s', '3601')
+ self.assertResponse('seconds 1d1s', '86401')
+ self.assertResponse('seconds 2d2h2m2s', '180122')
+ self.assertResponse('seconds 1y1s', '31536001')
+ self.assertResponse('seconds 1w1s', '604801')
+
def testNoErrors(self):
self.assertNotError('ctime')
self.assertNotError('time %Y')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Limnoria-master-2022-07-03/requirements.txt
new/Limnoria-master-2022-09-27/requirements.txt
--- old/Limnoria-master-2022-07-03/requirements.txt 2022-06-23
22:31:17.000000000 +0200
+++ new/Limnoria-master-2022-09-27/requirements.txt 2022-09-20
07:51:46.000000000 +0200
@@ -1,10 +1,17 @@
+# mandatory:
+
setuptools
-chardet
-pytz;python_version<'3.9'
-python-dateutil
-python-gnupg
-feedparser
-PySocks
-mock
-cryptography
-pyxmpp2-scram
+
+# optional core dependencies:
+
+chardet # to detect encoding of incoming IRC lines, if
they are not in UTF-8
+python-gnupg # for authenticated based on GPG tokens
+PySocks # for SOCKS proxy (typically used to connect to
IRC via Tor)
+pyxmpp2-scram # for the scram-sha-256 SASL mechanism
+
+# optional plugin dependencies:
+
+cryptography # required to load the Fediverse plugin (used to
implement HTTP signatures to support Mastodon instances with
AUTHORIZED_FETCH=true)
+feedparser # required to load the RSS plugin
+pytz;python_version<'3.9' # enables timezone manipulation in the Time and
Geography plugins. On Python >=3.9, the standard library is used instead
+python-dateutil # enable fancy time string parsing in the Time
plugin
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/Limnoria-master-2022-07-03/scripts/supybot-plugin-doc
new/Limnoria-master-2022-09-27/scripts/supybot-plugin-doc
--- old/Limnoria-master-2022-07-03/scripts/supybot-plugin-doc 2022-06-23
22:31:17.000000000 +0200
+++ new/Limnoria-master-2022-09-27/scripts/supybot-plugin-doc 2022-09-20
07:51:46.000000000 +0200
@@ -297,7 +297,7 @@
'with the plugin\'s name and "$format" with the value '
'if --format.')
parser.add_option('-f', '--format', dest='format', choices=['rst', 'stx'],
- default='stx', help='Specifies which output format to '
+ default='rst', help='Specifies which output format to '
'use.')
parser.add_option('--plugins-dir',
action='append', dest='pluginsDirs', default=[],
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Limnoria-master-2022-07-03/src/callbacks.py
new/Limnoria-master-2022-09-27/src/callbacks.py
--- old/Limnoria-master-2022-07-03/src/callbacks.py 2022-06-23
22:31:17.000000000 +0200
+++ new/Limnoria-master-2022-09-27/src/callbacks.py 2022-09-20
07:51:46.000000000 +0200
@@ -1804,6 +1804,19 @@
def doPrivmsg(self, irc, msg):
if msg.isError:
return
+
+ if 'batch' in msg.server_tags:
+ parent_batches = irc.state.getParentBatches(msg)
+ parent_batch_types = [batch.type for batch in parent_batches]
+ if 'chathistory' in parent_batch_types:
+ # Either sent automatically by the server upon join,
+ # or triggered by a plugin (why?!)
+ # Either way, replying to messages from the history would
+ # look weird, because they may have been sent a while ago,
+ # and we may have already answered them.
+ # (this is the same behavior as in Owner.doPrivmsg)
+ return
+
proxy = self.Proxy(irc, msg)
if not msg.addressed:
for (r, name) in self.unaddressedRes:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Limnoria-master-2022-07-03/src/commands.py
new/Limnoria-master-2022-09-27/src/commands.py
--- old/Limnoria-master-2022-07-03/src/commands.py 2022-06-23
22:31:17.000000000 +0200
+++ new/Limnoria-master-2022-09-27/src/commands.py 2022-09-20
07:51:46.000000000 +0200
@@ -50,6 +50,13 @@
from .i18n import PluginInternationalization, internationalizeDocstring
_ = PluginInternationalization()
+LOG_CONVERTERS = world.testing
+"""Defines whether converters and contexts should log the argument stack
+while parsing it.
+Disabled by default (unless running via supybot-test) as it is very noisy
+and rarely needs to be debugged.
+"""
+
###
# Non-arg wrappers -- these just change the behavior of a command without
# changing the arguments given to it.
@@ -509,7 +516,8 @@
elif msg.channel:
channel = msg.channel
else:
- state.log.debug('Raising ArgumentError because there is no channel.')
+ if LOG_CONVERTERS:
+ state.log.debug('Raising ArgumentError because there is no
channel.')
raise callbacks.ArgumentError
state.channel = channel
state.args.append(channel)
@@ -520,7 +528,8 @@
elif msg.channel:
channels = [msg.channel]
else:
- state.log.debug('Raising ArgumentError because there is no channel.')
+ if LOG_CONVERTERS:
+ state.log.debug('Raising ArgumentError because there is no
channel.')
raise callbacks.ArgumentError
state.args.append(channels)
@@ -898,9 +907,11 @@
self.converter = spec
def __call__(self, irc, msg, args, state):
- log.debug('args before %r: %r', self, args)
+ if LOG_CONVERTERS:
+ log.debug('args before %r: %r', self, args)
self.converter(irc, msg, args, state, *self.args)
- log.debug('args after %r: %r', self, args)
+ if LOG_CONVERTERS:
+ log.debug('args after %r: %r', self, args)
def __repr__(self):
return '<%s for %s>' % (self.__class__.__name__, self.spec)
@@ -929,7 +940,8 @@
try:
self.__parent.__call__(irc, msg, args, state)
except IndexError:
- log.debug('Got IndexError, returning default.')
+ if LOG_CONVERTERS:
+ log.debug('Got IndexError, returning default.')
setDefault(state, self.default)
# optional means: Look for this, but if it's not the type I'm expecting or
@@ -939,7 +951,8 @@
try:
super(optional, self).__call__(irc, msg, args, state)
except (callbacks.ArgumentError, callbacks.Error) as e:
- log.debug('Got %s, returning default.', utils.exnToString(e))
+ if LOG_CONVERTERS:
+ log.debug('Got %s, returning default.', utils.exnToString(e))
state.errored = False
setDefault(state, self.default)
@@ -960,7 +973,8 @@
if not self.continueOnError:
raise
else:
- log.debug('Got %s, returning default.', utils.exnToString(e))
+ if LOG_CONVERTERS:
+ log.debug('Got %s, returning default.',
utils.exnToString(e))
pass
state.args.append(st.args)
@@ -1041,11 +1055,13 @@
self.getopts[name] = contextify(spec)
self.getoptL.append(name + '=')
self.getopts[name] = contextify(spec)
- log.debug('getopts: %r', self.getopts)
- log.debug('getoptL: %r', self.getoptL)
+ if LOG_CONVERTERS:
+ log.debug('getopts: %r', self.getopts)
+ log.debug('getoptL: %r', self.getoptL)
def __call__(self, irc, msg, args, state):
- log.debug('args before %r: %r', self, args)
+ if LOG_CONVERTERS:
+ log.debug('args before %r: %r', self, args)
(optlist, rest) = getopt.getopt(args, self.getoptLs, self.getoptL)
getopts = []
for (opt, arg) in optlist:
@@ -1053,7 +1069,8 @@
opt = opt[2:] # Strip --
else:
opt = opt[1:]
- log.debug('opt: %r, arg: %r', opt, arg)
+ if LOG_CONVERTERS:
+ log.debug('opt: %r, arg: %r', opt, arg)
context = self.getopts[opt]
if context is not None:
st = state.essence()
@@ -1064,7 +1081,8 @@
getopts.append((opt, True))
state.args.append(getopts)
args[:] = rest
- log.debug('args after %r: %r', self, args)
+ if LOG_CONVERTERS:
+ log.debug('args after %r: %r', self, args)
###
# This is our state object, passed to converters along with irc, msg, and args.
@@ -1123,7 +1141,8 @@
except IndexError:
raise callbacks.ArgumentError
if args and not state.allowExtra:
- log.debug('args and not self.allowExtra: %r', args)
+ if LOG_CONVERTERS:
+ log.debug('args and not self.allowExtra: %r', args)
raise callbacks.ArgumentError
return state
@@ -1134,9 +1153,11 @@
spec = Spec(specList, **kw)
def newf(self, irc, msg, args, **kwargs):
state = spec(irc, msg, args, stateAttrs={'cb': self, 'log': self.log})
- self.log.debug('State before call: %s', state)
+ if LOG_CONVERTERS:
+ self.log.debug('State before call: %s', state)
if state.errored:
- self.log.debug('Refusing to call %s due to state.errored.', f)
+ if LOG_CONVERTERS:
+ self.log.debug('Refusing to call %s due to state.errored.', f)
else:
try:
f(self, irc, msg, args, *state.args, **state.kwargs)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Limnoria-master-2022-07-03/src/irclib.py
new/Limnoria-master-2022-09-27/src/irclib.py
--- old/Limnoria-master-2022-07-03/src/irclib.py 2022-06-23
22:31:17.000000000 +0200
+++ new/Limnoria-master-2022-09-27/src/irclib.py 2022-09-20
07:51:46.000000000 +0200
@@ -1731,20 +1731,41 @@
self.sasl_current_mechanism = None
for mechanism in network_config.sasl.mechanisms():
- if mechanism == 'ecdsa-nist256p-challenge' and \
- crypto and self.sasl_username and \
- self.sasl_ecdsa_key:
- self.sasl_next_mechanisms.append(mechanism)
- elif mechanism == 'external' and (
- network_config.certfile() or
- conf.supybot.protocols.irc.certfile()):
- self.sasl_next_mechanisms.append(mechanism)
- elif mechanism.startswith('scram-') and scram and \
- self.sasl_username and self.sasl_password:
- self.sasl_next_mechanisms.append(mechanism)
- elif mechanism == 'plain' and \
- self.sasl_username and self.sasl_password:
- self.sasl_next_mechanisms.append(mechanism)
+ if mechanism == 'ecdsa-nist256p-challenge':
+ if not crypto:
+ log.debug('Skipping SASL %s, crypto module '
+ 'is not available',
+ mechanism)
+ elif not self.sasl_username or not self.sasl_ecdsa_key:
+ log.debug('Skipping SASL %s, missing username and/or key',
+ mechanism)
+ else:
+ self.sasl_next_mechanisms.append(mechanism)
+ elif mechanism == 'external':
+ if not network_config.certfile() and \
+ not conf.supybot.protocols.irc.certfile():
+ log.debug('Skipping SASL %s, missing cert file',
+ mechanism)
+ else:
+ self.sasl_next_mechanisms.append(mechanism)
+ elif mechanism.startswith('scram-'):
+ if not scram:
+ log.debug('Skipping SASL %s, scram module '
+ 'is not available',
+ mechanism)
+ elif not self.sasl_username or not self.sasl_password:
+ log.debug('Skipping SASL %s, missing username and/or '
+ 'password',
+ mechanism)
+ else:
+ self.sasl_next_mechanisms.append(mechanism)
+ elif mechanism == 'plain':
+ if not self.sasl_username or not self.sasl_password:
+ log.debug('Skipping SASL %s, missing username and/or '
+ 'password',
+ mechanism)
+ else:
+ self.sasl_next_mechanisms.append(mechanism)
if self.sasl_next_mechanisms:
self.REQUEST_CAPABILITIES.add('sasl')
@@ -1862,6 +1883,7 @@
IrcStateFsm.States.INIT_SASL,
IrcStateFsm.States.CONNECTED_SASL,
])
+ log.debug('Next SASL mechanisms: %s', self.sasl_next_mechanisms)
if self.sasl_next_mechanisms:
self.sasl_current_mechanism = self.sasl_next_mechanisms.pop(0)
self.sendMsg(ircmsgs.IrcMsg(command='AUTHENTICATE',
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Limnoria-master-2022-07-03/src/registry.py
new/Limnoria-master-2022-09-27/src/registry.py
--- old/Limnoria-master-2022-07-03/src/registry.py 2022-06-23
22:31:17.000000000 +0200
+++ new/Limnoria-master-2022-09-27/src/registry.py 2022-09-20
07:51:46.000000000 +0200
@@ -710,7 +710,7 @@
def setValue(self, s):
v = self.normalize(s)
- if s in self.validStrings:
+ if v in self.validStrings:
self.__parent.setValue(v)
else:
self.error(v)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Limnoria-master-2022-07-03/src/utils/web.py
new/Limnoria-master-2022-09-27/src/utils/web.py
--- old/Limnoria-master-2022-07-03/src/utils/web.py 2022-06-23
22:31:17.000000000 +0200
+++ new/Limnoria-master-2022-09-27/src/utils/web.py 2022-09-20
07:51:46.000000000 +0200
@@ -226,6 +226,8 @@
# From beautifulsoup (version 4.10.0, bs4/builder/__init__.py, line 391)
_block_elements = set(["address", "article", "aside", "blockquote", "canvas",
"dd", "div", "dl", "dt", "fieldset", "figcaption", "figure", "footer", "form",
"h1", "h2", "h3", "h4", "h5", "h6", "header", "hr", "li", "main", "nav",
"noscript", "ol", "output", "p", "pre", "section", "table", "tfoot", "ul",
"video"])
+_block_elements.update({"br"})
+
class HtmlToText(HTMLParser, object):
"""Taken from some eff-bot code on c.l.p."""
entitydefs = entitydefs.copy()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/Limnoria-master-2022-07-03/test/test_callbacks.py
new/Limnoria-master-2022-09-27/test/test_callbacks.py
--- old/Limnoria-master-2022-07-03/test/test_callbacks.py 2022-06-23
22:31:17.000000000 +0200
+++ new/Limnoria-master-2022-09-27/test/test_callbacks.py 2022-09-20
07:51:46.000000000 +0200
@@ -975,16 +975,50 @@
'-' + batch_name,)))
-class PluginRegexpTestCase(PluginTestCase):
+class PluginRegexpTestCase(ChannelPluginTestCase):
plugins = ()
class PCAR(callbacks.PluginRegexp):
+ regexps = ("test", "test2")
+
def test(self, irc, msg, args):
"<foo>"
raise callbacks.ArgumentError
- def testNoEscapingArgumentError(self):
+
+ def test2(self, irc, msg, args):
+ "<bar>"
+ irc.reply("hello")
+
+ def setUp(self):
+ super().setUp()
self.irc.addCallback(self.PCAR(self.irc))
+
+ def testNoEscapingArgumentError(self):
self.assertResponse('test', 'test <foo>')
+ def testReply(self):
+ self.irc.feedMsg(ircmsgs.IrcMsg(
+ prefix=self.prefix,
+ command='PRIVMSG',
+ args=(self.channel, 'foo <bar> baz')))
+ self.assertResponse(' ', 'hello')
+
+ def testIgnoreChathistory(self):
+ self.irc.feedMsg(ircmsgs.IrcMsg(
+ command='BATCH',
+ args=('+123', 'chathistory', self.channel)))
+
+ self.irc.feedMsg(ircmsgs.IrcMsg(
+ server_tags={'batch': '123'},
+ prefix=self.prefix,
+ command='PRIVMSG',
+ args=(self.channel, 'foo <bar> baz')))
+
+ self.irc.feedMsg(ircmsgs.IrcMsg(
+ command='BATCH',
+ args=('-123',)))
+
+ self.assertNoResponse(' ')
+
class RichReplyMethodsTestCase(PluginTestCase):
plugins = ('Config',)
class NoCapability(callbacks.Plugin):