mirror of
https://github.com/Mikaela/Limnoria.git
synced 2025-08-19 19:37:21 +02:00
Compare commits
No commits in common. "d435442b39f167509e64b21a9cd1cf3f71e33033" and "4ed318d06fc1af264e61a5e1cd4928f466802361" have entirely different histories.
d435442b39
...
4ed318d06f
@ -50,7 +50,6 @@ class AdminTestCase(PluginTestCase):
|
|||||||
self.irc.feedMsg(ircmsgs.join('#Baz', prefix=self.prefix))
|
self.irc.feedMsg(ircmsgs.join('#Baz', prefix=self.prefix))
|
||||||
getAfterJoinMessages()
|
getAfterJoinMessages()
|
||||||
self.assertRegexp('channels', '#bar, #Baz, and #foo')
|
self.assertRegexp('channels', '#bar, #Baz, and #foo')
|
||||||
self.assertNotRegexp('config networks.test.channels', '.*#foo.*')
|
|
||||||
|
|
||||||
def testIgnoreAddRemove(self):
|
def testIgnoreAddRemove(self):
|
||||||
self.assertNotError('admin ignore add foo!bar@baz')
|
self.assertNotError('admin ignore add foo!bar@baz')
|
||||||
@ -88,16 +87,13 @@ class AdminTestCase(PluginTestCase):
|
|||||||
ircdb.users.delUser(u.id)
|
ircdb.users.delUser(u.id)
|
||||||
|
|
||||||
def testJoin(self):
|
def testJoin(self):
|
||||||
try:
|
m = self.getMsg('join #foo')
|
||||||
m = self.getMsg('join #foo')
|
self.assertEqual(m.command, 'JOIN')
|
||||||
self.assertEqual(m.command, 'JOIN')
|
self.assertEqual(m.args[0], '#foo')
|
||||||
self.assertEqual(m.args[0], '#foo')
|
m = self.getMsg('join #foo key')
|
||||||
m = self.getMsg('join #foo key')
|
self.assertEqual(m.command, 'JOIN')
|
||||||
self.assertEqual(m.command, 'JOIN')
|
self.assertEqual(m.args[0], '#foo')
|
||||||
self.assertEqual(m.args[0], '#foo')
|
self.assertEqual(m.args[1], 'key')
|
||||||
self.assertEqual(m.args[1], 'key')
|
|
||||||
finally:
|
|
||||||
conf.supybot.networks.test.channels.setValue('')
|
|
||||||
|
|
||||||
def testNick(self):
|
def testNick(self):
|
||||||
try:
|
try:
|
||||||
@ -111,13 +107,10 @@ class AdminTestCase(PluginTestCase):
|
|||||||
self.assertError('admin capability add %s owner' % self.nick)
|
self.assertError('admin capability add %s owner' % self.nick)
|
||||||
|
|
||||||
def testJoinOnOwnerInvite(self):
|
def testJoinOnOwnerInvite(self):
|
||||||
try:
|
self.irc.feedMsg(ircmsgs.invite(conf.supybot.nick(), '#foo', prefix=self.prefix))
|
||||||
self.irc.feedMsg(ircmsgs.invite(conf.supybot.nick(), '#foo', prefix=self.prefix))
|
m = self.getMsg(' ')
|
||||||
m = self.getMsg(' ')
|
self.assertEqual(m.command, 'JOIN')
|
||||||
self.assertEqual(m.command, 'JOIN')
|
self.assertEqual(m.args[0], '#foo')
|
||||||
self.assertEqual(m.args[0], '#foo')
|
|
||||||
finally:
|
|
||||||
conf.supybot.networks.test.channels.setValue('')
|
|
||||||
|
|
||||||
def testNoJoinOnUnprivilegedInvite(self):
|
def testNoJoinOnUnprivilegedInvite(self):
|
||||||
try:
|
try:
|
||||||
@ -128,7 +121,6 @@ class AdminTestCase(PluginTestCase):
|
|||||||
'Error: "somecommand" is not a valid command.')
|
'Error: "somecommand" is not a valid command.')
|
||||||
finally:
|
finally:
|
||||||
world.testing = True
|
world.testing = True
|
||||||
self.assertNotRegexp('config networks.test.channels', '.*#foo.*')
|
|
||||||
|
|
||||||
def testAcmd(self):
|
def testAcmd(self):
|
||||||
self.irc.feedMsg(ircmsgs.join('#foo', prefix=self.prefix))
|
self.irc.feedMsg(ircmsgs.join('#foo', prefix=self.prefix))
|
||||||
|
@ -991,14 +991,9 @@ class Channel(callbacks.Plugin):
|
|||||||
network = conf.supybot.networks.get(irc.network)
|
network = conf.supybot.networks.get(irc.network)
|
||||||
network.channels().remove(channel)
|
network.channels().remove(channel)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
if channel not in irc.state.channels:
|
pass
|
||||||
# Not configured AND not in the channel
|
if channel not in irc.state.channels:
|
||||||
irc.error(_('I\'m not in %s.') % channel, Raise=True)
|
irc.error(_('I\'m not in %s.') % channel, Raise=True)
|
||||||
else:
|
|
||||||
if channel not in irc.state.channels:
|
|
||||||
# Configured, but not in the channel
|
|
||||||
irc.reply(_('%s removed from configured join list.') % channel)
|
|
||||||
return
|
|
||||||
reason = (reason or self.registryValue("partMsg", channel, irc.network))
|
reason = (reason or self.registryValue("partMsg", channel, irc.network))
|
||||||
reason = ircutils.standardSubstitute(irc, msg, reason)
|
reason = ircutils.standardSubstitute(irc, msg, reason)
|
||||||
irc.queueMsg(ircmsgs.part(channel, reason))
|
irc.queueMsg(ircmsgs.part(channel, reason))
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
###
|
###
|
||||||
# Copyright (c) 2002-2005, Jeremiah Fincher
|
# Copyright (c) 2002-2005, Jeremiah Fincher
|
||||||
# Copyright (c) 2010-2021, Valentin Lorentz
|
# Copyright (c) 2010-2021, Valentin Lorentz
|
||||||
|
@ -177,15 +177,9 @@ class Fediverse(callbacks.PluginRegexp):
|
|||||||
|
|
||||||
def _has_webfinger_support(self, hostname):
|
def _has_webfinger_support(self, hostname):
|
||||||
if hostname not in self._webfinger_support_cache:
|
if hostname not in self._webfinger_support_cache:
|
||||||
try:
|
self._webfinger_support_cache[hostname] = ap.has_webfinger_support(
|
||||||
self._webfinger_support_cache[hostname] = ap.has_webfinger_support(
|
hostname
|
||||||
hostname
|
)
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
self.log.error(
|
|
||||||
"Checking Webfinger support for %s raised %s", hostname, e
|
|
||||||
)
|
|
||||||
return False
|
|
||||||
return self._webfinger_support_cache[hostname]
|
return self._webfinger_support_cache[hostname]
|
||||||
|
|
||||||
def _get_actor(self, irc, username):
|
def _get_actor(self, irc, username):
|
||||||
|
@ -187,7 +187,7 @@ class GeographyLocaltimeTestCase(PluginTestCase):
|
|||||||
|
|
||||||
class GeographyWikidataTestCase(SupyTestCase):
|
class GeographyWikidataTestCase(SupyTestCase):
|
||||||
@skipIf(not network, "Network test")
|
@skipIf(not network, "Network test")
|
||||||
def testRelationOsmidToTimezone(self):
|
def testOsmidToTimezone(self):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
wikidata.uri_from_osmid(450381),
|
wikidata.uri_from_osmid(450381),
|
||||||
"http://www.wikidata.org/entity/Q22690",
|
"http://www.wikidata.org/entity/Q22690",
|
||||||
@ -196,12 +196,6 @@ class GeographyWikidataTestCase(SupyTestCase):
|
|||||||
wikidata.uri_from_osmid(192468),
|
wikidata.uri_from_osmid(192468),
|
||||||
"http://www.wikidata.org/entity/Q47045",
|
"http://www.wikidata.org/entity/Q47045",
|
||||||
)
|
)
|
||||||
@skipIf(not network, "Network test")
|
|
||||||
def testNodeOsmidToTimezone(self):
|
|
||||||
self.assertEqual(
|
|
||||||
wikidata.uri_from_osmid(436012592),
|
|
||||||
"http://www.wikidata.org/entity/Q933",
|
|
||||||
)
|
|
||||||
|
|
||||||
@skipIf(not network, "Network test")
|
@skipIf(not network, "Network test")
|
||||||
def testDirect(self):
|
def testDirect(self):
|
||||||
|
@ -115,14 +115,7 @@ LIMIT 1
|
|||||||
OSMID_QUERY = string.Template(
|
OSMID_QUERY = string.Template(
|
||||||
"""
|
"""
|
||||||
SELECT ?item WHERE {
|
SELECT ?item WHERE {
|
||||||
{
|
?item wdt:P402 "$osmid".
|
||||||
?item wdt:P402 "$osmid". # OSM relation ID
|
|
||||||
}
|
|
||||||
UNION
|
|
||||||
{
|
|
||||||
?item wdt:P11693 "$osmid". # OSM node ID
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
"""
|
"""
|
||||||
|
@ -158,7 +158,7 @@ class Internet(callbacks.Plugin):
|
|||||||
if not status:
|
if not status:
|
||||||
status = 'unknown'
|
status = 'unknown'
|
||||||
try:
|
try:
|
||||||
t = telnetlib.Telnet('whois.iana.org', 43)
|
t = telnetlib.Telnet('whois.pir.org', 43)
|
||||||
except socket.error as e:
|
except socket.error as e:
|
||||||
irc.error(str(e))
|
irc.error(str(e))
|
||||||
return
|
return
|
||||||
|
@ -21,11 +21,6 @@ and checking latency to the server.
|
|||||||
Commands
|
Commands
|
||||||
--------
|
--------
|
||||||
|
|
||||||
.. _command-network-authenticate:
|
|
||||||
|
|
||||||
authenticate takes no arguments
|
|
||||||
Manually initiate SASL authentication.
|
|
||||||
|
|
||||||
.. _command-network-capabilities:
|
.. _command-network-capabilities:
|
||||||
|
|
||||||
capabilities [<network>]
|
capabilities [<network>]
|
||||||
|
@ -8,8 +8,9 @@ Purpose
|
|||||||
|
|
||||||
Provides basic functionality for handling RSS/RDF feeds, and allows announcing
|
Provides basic functionality for handling RSS/RDF feeds, and allows announcing
|
||||||
them periodically to channels.
|
them periodically to channels.
|
||||||
In order to use this plugin you must have `python3-feedparser
|
In order to use this plugin you must have the following modules
|
||||||
<https://pypi.org/project/feedparser/>`_ installed.
|
installed:
|
||||||
|
* feedparser: http://feedparser.org/
|
||||||
|
|
||||||
Usage
|
Usage
|
||||||
-----
|
-----
|
||||||
@ -139,7 +140,7 @@ supybot.plugins.RSS.feeds
|
|||||||
supybot.plugins.RSS.format
|
supybot.plugins.RSS.format
|
||||||
This config variable defaults to "$date: $title <$link>", is network-specific, and is channel-specific.
|
This config variable defaults to "$date: $title <$link>", is network-specific, and is channel-specific.
|
||||||
|
|
||||||
The format the bot will use for displaying headlines of a RSS feed that is triggered manually. In addition to fields defined by feedparser ($published (the entry date), $title, $link, $description, $id, etc.), the following variables can be used: $feed_name (the configured name) $feed_title/$feed_subtitle/$feed_author/$feed_language/$feed_link, $date (parsed date, as defined in supybot.reply.format.time)
|
The format the bot will use for displaying headlines of a RSS feed that is triggered manually. In addition to fields defined by feedparser ($published (the entry date), $title, $link, $description, $id, etc.), the following variables can be used: $feed_name, $date (parsed date, as defined in supybot.reply.format.time)
|
||||||
|
|
||||||
.. _conf-supybot.plugins.RSS.headlineSeparator:
|
.. _conf-supybot.plugins.RSS.headlineSeparator:
|
||||||
|
|
||||||
|
@ -31,8 +31,9 @@
|
|||||||
"""
|
"""
|
||||||
Provides basic functionality for handling RSS/RDF feeds, and allows announcing
|
Provides basic functionality for handling RSS/RDF feeds, and allows announcing
|
||||||
them periodically to channels.
|
them periodically to channels.
|
||||||
In order to use this plugin you must have `python3-feedparser
|
In order to use this plugin you must have the following modules
|
||||||
<https://pypi.org/project/feedparser/>`_ installed.
|
installed:
|
||||||
|
* feedparser: http://feedparser.org/
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import supybot
|
import supybot
|
||||||
|
@ -364,11 +364,6 @@ class RSS(callbacks.Plugin):
|
|||||||
feed.url, e)
|
feed.url, e)
|
||||||
feed.last_exception = e
|
feed.last_exception = e
|
||||||
return
|
return
|
||||||
except http.client.HTTPException as e:
|
|
||||||
self.log.warning("HTTP error while fetching <%s>: %s",
|
|
||||||
feed.url, e)
|
|
||||||
feed.last_exception = e
|
|
||||||
return
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.log.error("Failed to fetch <%s>: %s", feed.url, e)
|
self.log.error("Failed to fetch <%s>: %s", feed.url, e)
|
||||||
raise # reraise so @log.firewall prints the traceback
|
raise # reraise so @log.firewall prints the traceback
|
||||||
@ -502,48 +497,6 @@ class RSS(callbacks.Plugin):
|
|||||||
isinstance(v, str)}
|
isinstance(v, str)}
|
||||||
kwargs["feed_name"] = feed.name
|
kwargs["feed_name"] = feed.name
|
||||||
kwargs.update(entry)
|
kwargs.update(entry)
|
||||||
for (key, value) in list(kwargs.items()):
|
|
||||||
# First look for plain text
|
|
||||||
if isinstance(value, list):
|
|
||||||
for item in value:
|
|
||||||
if isinstance(item, dict) and 'value' in item and \
|
|
||||||
item.get('type') == 'text/plain':
|
|
||||||
value = item['value']
|
|
||||||
break
|
|
||||||
# Then look for HTML text or URL
|
|
||||||
if isinstance(value, list):
|
|
||||||
for item in value:
|
|
||||||
if isinstance(item, dict) and item.get('type') in \
|
|
||||||
('text/html', 'application/xhtml+xml'):
|
|
||||||
if 'value' in item:
|
|
||||||
value = utils.web.htmlToText(item['value'])
|
|
||||||
elif 'href' in item:
|
|
||||||
value = item['href']
|
|
||||||
# Then fall back to any URL
|
|
||||||
if isinstance(value, list):
|
|
||||||
for item in value:
|
|
||||||
if isinstance(item, dict) and 'href' in item:
|
|
||||||
value = item['href']
|
|
||||||
break
|
|
||||||
# Finally, as a last resort, use the value as-is
|
|
||||||
if isinstance(value, list):
|
|
||||||
for item in value:
|
|
||||||
if isinstance(item, dict) and 'value' in item:
|
|
||||||
value = item['value']
|
|
||||||
kwargs[key] = value
|
|
||||||
|
|
||||||
for key in ('summary', 'title'):
|
|
||||||
detail = kwargs.get('%s_detail' % key)
|
|
||||||
if isinstance(detail, dict) and detail.get('type') in \
|
|
||||||
('text/html', 'application/xhtml+xml'):
|
|
||||||
kwargs[key] = utils.web.htmlToText(detail['value'])
|
|
||||||
|
|
||||||
if 'description' not in kwargs and kwargs[key]:
|
|
||||||
kwargs['description'] = kwargs[key]
|
|
||||||
|
|
||||||
if 'description' not in kwargs and kwargs.get('content'):
|
|
||||||
kwargs['description'] = kwargs['content']
|
|
||||||
|
|
||||||
s = string.Template(template).safe_substitute(entry, **kwargs, date=date)
|
s = string.Template(template).safe_substitute(entry, **kwargs, date=date)
|
||||||
return self._normalize_entry(s)
|
return self._normalize_entry(s)
|
||||||
|
|
||||||
|
@ -59,6 +59,7 @@ not_well_formed = """<?xml version="1.0" encoding="utf-8"?>
|
|||||||
</rss>
|
</rss>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class MockResponse:
|
class MockResponse:
|
||||||
headers = {}
|
headers = {}
|
||||||
url = ''
|
url = ''
|
||||||
@ -358,130 +359,6 @@ class RSSTestCase(ChannelPluginTestCase):
|
|||||||
self.assertRegexp('rss http://xkcd.com/rss.xml',
|
self.assertRegexp('rss http://xkcd.com/rss.xml',
|
||||||
'On the other hand, the refractor\'s')
|
'On the other hand, the refractor\'s')
|
||||||
|
|
||||||
@mock_urllib
|
|
||||||
def testAtomContentHtmlOnly(self, mock):
|
|
||||||
timeFastForward(1.1)
|
|
||||||
mock._data = """
|
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" xml:lang="en-US">
|
|
||||||
<title>Recent Commits to anope:2.0</title>
|
|
||||||
<updated>2023-10-04T16:14:39Z</updated>
|
|
||||||
<entry>
|
|
||||||
<title>title with <pre>HTML</pre></title>
|
|
||||||
<updated>2023-10-04T16:14:39Z</updated>
|
|
||||||
<content type="html">
|
|
||||||
content with <pre>HTML</pre>
|
|
||||||
</content>
|
|
||||||
</entry>
|
|
||||||
</feed>"""
|
|
||||||
with conf.supybot.plugins.RSS.format.context('$content'):
|
|
||||||
self.assertRegexp('rss https://example.org',
|
|
||||||
'content with HTML')
|
|
||||||
with conf.supybot.plugins.RSS.format.context('$description'):
|
|
||||||
self.assertRegexp('rss https://example.org',
|
|
||||||
'content with HTML')
|
|
||||||
|
|
||||||
@mock_urllib
|
|
||||||
def testAtomContentXhtmlOnly(self, mock):
|
|
||||||
timeFastForward(1.1)
|
|
||||||
mock._data = """
|
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" xml:lang="en-US">
|
|
||||||
<title>Recent Commits to anope:2.0</title>
|
|
||||||
<updated>2023-10-04T16:14:39Z</updated>
|
|
||||||
<entry>
|
|
||||||
<title>title with <pre>HTML</pre></title>
|
|
||||||
<updated>2023-10-04T16:14:39Z</updated>
|
|
||||||
<content type="xhtml">
|
|
||||||
<div xmlns="http://www.w3.org/1999/xhtml">
|
|
||||||
content with <pre>XHTML</pre>
|
|
||||||
</div>
|
|
||||||
</content>
|
|
||||||
</entry>
|
|
||||||
</feed>"""
|
|
||||||
with conf.supybot.plugins.RSS.format.context('$content'):
|
|
||||||
self.assertRegexp('rss https://example.org',
|
|
||||||
'content with XHTML')
|
|
||||||
with conf.supybot.plugins.RSS.format.context('$description'):
|
|
||||||
self.assertRegexp('rss https://example.org',
|
|
||||||
'content with XHTML')
|
|
||||||
|
|
||||||
@mock_urllib
|
|
||||||
def testAtomContentHtmlAndPlaintext(self, mock):
|
|
||||||
timeFastForward(1.1)
|
|
||||||
mock._data = """
|
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" xml:lang="en-US">
|
|
||||||
<title>Recent Commits to anope:2.0</title>
|
|
||||||
<updated>2023-10-04T16:14:39Z</updated>
|
|
||||||
<entry>
|
|
||||||
<title>title with <pre>HTML</pre></title>
|
|
||||||
<updated>2023-10-04T16:14:39Z</updated>
|
|
||||||
<!-- Atom spec says multiple contents is invalid, feedparser says it's not.
|
|
||||||
I like having the option, so let's make sure we support it. -->
|
|
||||||
<content type="html">
|
|
||||||
content with <pre>HTML</pre>
|
|
||||||
</content>
|
|
||||||
<content type="text">
|
|
||||||
content with plaintext
|
|
||||||
</content>
|
|
||||||
</entry>
|
|
||||||
</feed>"""
|
|
||||||
with conf.supybot.plugins.RSS.format.context('$content'):
|
|
||||||
self.assertRegexp('rss https://example.org',
|
|
||||||
'content with plaintext')
|
|
||||||
with conf.supybot.plugins.RSS.format.context('$description'):
|
|
||||||
self.assertRegexp('rss https://example.org',
|
|
||||||
'content with plaintext')
|
|
||||||
|
|
||||||
@mock_urllib
|
|
||||||
def testAtomContentPlaintextAndHtml(self, mock):
|
|
||||||
timeFastForward(1.1)
|
|
||||||
mock._data = """
|
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" xml:lang="en-US">
|
|
||||||
<title>Recent Commits to anope:2.0</title>
|
|
||||||
<updated>2023-10-04T16:14:39Z</updated>
|
|
||||||
<entry>
|
|
||||||
<title>title with <pre>HTML</pre></title>
|
|
||||||
<updated>2023-10-04T16:14:39Z</updated>
|
|
||||||
<!-- Atom spec says multiple contents is invalid, feedparser says it's not.
|
|
||||||
I like having the option, so let's make sure we support it. -->
|
|
||||||
<content type="text">
|
|
||||||
content with plaintext
|
|
||||||
</content>
|
|
||||||
<content type="html">
|
|
||||||
content with <pre>HTML</pre>
|
|
||||||
</content>
|
|
||||||
</entry>
|
|
||||||
</feed>"""
|
|
||||||
with conf.supybot.plugins.RSS.format.context('$content'):
|
|
||||||
self.assertRegexp('rss https://example.org',
|
|
||||||
'content with plaintext')
|
|
||||||
with conf.supybot.plugins.RSS.format.context('$description'):
|
|
||||||
self.assertRegexp('rss https://example.org',
|
|
||||||
'content with plaintext')
|
|
||||||
|
|
||||||
@mock_urllib
|
|
||||||
def testRssDescriptionHtml(self, mock):
|
|
||||||
timeFastForward(1.1)
|
|
||||||
mock._data = """
|
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:foaf="http://xmlns.com/foaf/0.1/" xmlns:og="http://ogp.me/ns#" xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#" xmlns:schema="http://schema.org/" xmlns:sioc="http://rdfs.org/sioc/ns#" xmlns:sioct="http://rdfs.org/sioc/types#" xmlns:skos="http://www.w3.org/2004/02/skos/core#" xmlns:xsd="http://www.w3.org/2001/XMLSchema#" version="2.0">
|
|
||||||
<channel>
|
|
||||||
<title>feed title</title>
|
|
||||||
<description/>
|
|
||||||
<language>en</language>
|
|
||||||
<item>
|
|
||||||
<title>title with <pre>HTML</pre></title>
|
|
||||||
<description>description with <pre>HTML</pre></description>
|
|
||||||
</item>
|
|
||||||
</channel>
|
|
||||||
</feed>"""
|
|
||||||
with conf.supybot.plugins.RSS.format.context('$description'):
|
|
||||||
self.assertRegexp('rss https://example.org',
|
|
||||||
'description with HTML')
|
|
||||||
|
|
||||||
@mock_urllib
|
@mock_urllib
|
||||||
def testFeedAttribute(self, mock):
|
def testFeedAttribute(self, mock):
|
||||||
timeFastForward(1.1)
|
timeFastForward(1.1)
|
||||||
|
@ -67,22 +67,6 @@ supybot.plugins.SedRegex.enable
|
|||||||
|
|
||||||
Should Perl/sed-style regex replacing work in this channel?
|
Should Perl/sed-style regex replacing work in this channel?
|
||||||
|
|
||||||
.. _conf-supybot.plugins.SedRegex.format:
|
|
||||||
|
|
||||||
|
|
||||||
supybot.plugins.SedRegex.format
|
|
||||||
This config variable defaults to "$nick meant to say: $replacement", is network-specific, and is channel-specific.
|
|
||||||
|
|
||||||
Sets the format string for a message edited by the original author. Required fields: $nick (nick of the author), $replacement (edited message)
|
|
||||||
|
|
||||||
.. _conf-supybot.plugins.SedRegex.format.other:
|
|
||||||
|
|
||||||
|
|
||||||
supybot.plugins.SedRegex.format.other
|
|
||||||
This config variable defaults to "$otherNick thinks $nick meant to say: $replacement", is network-specific, and is channel-specific.
|
|
||||||
|
|
||||||
Sets the format string for a message edited by another author. Required fields: $nick (nick of the original author), $otherNick (nick of the editor), $replacement (edited message)
|
|
||||||
|
|
||||||
.. _conf-supybot.plugins.SedRegex.ignoreRegex:
|
.. _conf-supybot.plugins.SedRegex.ignoreRegex:
|
||||||
|
|
||||||
|
|
||||||
|
@ -124,11 +124,9 @@ class Services(callbacks.Plugin):
|
|||||||
return
|
return
|
||||||
nickserv = self.registryValue('NickServ', network=irc.network)
|
nickserv = self.registryValue('NickServ', network=irc.network)
|
||||||
password = self._getNickServPassword(nick, irc.network)
|
password = self._getNickServPassword(nick, irc.network)
|
||||||
if not nickserv:
|
if not nickserv or not password:
|
||||||
self.log.warning('Tried to identify without a NickServ set.')
|
s = 'Tried to identify without a NickServ or password set.'
|
||||||
return
|
self.log.warning(s)
|
||||||
if not password:
|
|
||||||
self.log.warning('Tried to identify without a password set.')
|
|
||||||
return
|
return
|
||||||
assert ircutils.strEqual(irc.nick, nick), \
|
assert ircutils.strEqual(irc.nick, nick), \
|
||||||
'Identifying with not normal nick.'
|
'Identifying with not normal nick.'
|
||||||
@ -152,15 +150,16 @@ class Services(callbacks.Plugin):
|
|||||||
ghostDelay = self.registryValue('ghostDelay', network=irc.network)
|
ghostDelay = self.registryValue('ghostDelay', network=irc.network)
|
||||||
if not ghostDelay:
|
if not ghostDelay:
|
||||||
return
|
return
|
||||||
if not nickserv:
|
if not nickserv or not password:
|
||||||
self.log.warning('Tried to ghost without a NickServ set.')
|
s = 'Tried to ghost without a NickServ or password set.'
|
||||||
return
|
self.log.warning(s)
|
||||||
if not password:
|
|
||||||
self.log.warning('Tried to ghost without a password set.')
|
|
||||||
return
|
return
|
||||||
if state.sentGhost and time.time() < (state.sentGhost + ghostDelay):
|
if state.sentGhost and time.time() < (state.sentGhost + ghostDelay):
|
||||||
self.log.warning('Refusing to send GHOST more than once every '
|
self.log.warning('Refusing to send GHOST more than once every '
|
||||||
'%s seconds.' % ghostDelay)
|
'%s seconds.' % ghostDelay)
|
||||||
|
elif not password:
|
||||||
|
self.log.warning('Not ghosting: no password set.')
|
||||||
|
return
|
||||||
else:
|
else:
|
||||||
self.log.info('Sending ghost (current nick: %s; ghosting: %s)',
|
self.log.info('Sending ghost (current nick: %s; ghosting: %s)',
|
||||||
irc.nick, nick)
|
irc.nick, nick)
|
||||||
|
@ -144,7 +144,7 @@ supybot.plugins.Unix.ping
|
|||||||
|
|
||||||
|
|
||||||
supybot.plugins.Unix.ping.command
|
supybot.plugins.Unix.ping.command
|
||||||
This config variable defaults to "/usr/bin/ping", is not network-specific, and is not channel-specific.
|
This config variable defaults to "/bin/ping", is not network-specific, and is not channel-specific.
|
||||||
|
|
||||||
Determines what command will be called for the ping command.
|
Determines what command will be called for the ping command.
|
||||||
|
|
||||||
@ -166,7 +166,7 @@ supybot.plugins.Unix.ping6
|
|||||||
|
|
||||||
|
|
||||||
supybot.plugins.Unix.ping6.command
|
supybot.plugins.Unix.ping6.command
|
||||||
This config variable defaults to "/usr/bin/ping6", is not network-specific, and is not channel-specific.
|
This config variable defaults to "/bin/ping6", is not network-specific, and is not channel-specific.
|
||||||
|
|
||||||
Determines what command will be called for the ping6 command.
|
Determines what command will be called for the ping6 command.
|
||||||
|
|
||||||
@ -210,7 +210,7 @@ supybot.plugins.Unix.sysuname
|
|||||||
|
|
||||||
|
|
||||||
supybot.plugins.Unix.sysuname.command
|
supybot.plugins.Unix.sysuname.command
|
||||||
This config variable defaults to "/usr/bin/uname", is not network-specific, and is not channel-specific.
|
This config variable defaults to "/bin/uname", is not network-specific, and is not channel-specific.
|
||||||
|
|
||||||
Determines what command will be called for the uname command.
|
Determines what command will be called for the uname command.
|
||||||
|
|
||||||
|
@ -154,7 +154,7 @@ class Web(callbacks.PluginRegexp):
|
|||||||
if parsed_url.netloc == 'youtube.com' \
|
if parsed_url.netloc == 'youtube.com' \
|
||||||
or parsed_url.netloc.endswith(('.youtube.com')):
|
or parsed_url.netloc.endswith(('.youtube.com')):
|
||||||
# there is a lot of Javascript before the <title>
|
# there is a lot of Javascript before the <title>
|
||||||
size = max(819200, size)
|
size = max(409600, size)
|
||||||
if parsed_url.netloc in ('reddit.com', 'www.reddit.com', 'new.reddit.com'):
|
if parsed_url.netloc in ('reddit.com', 'www.reddit.com', 'new.reddit.com'):
|
||||||
# Since 2022-03, New Reddit has 'Reddit - Dive into anything' as
|
# Since 2022-03, New Reddit has 'Reddit - Dive into anything' as
|
||||||
# <title> on every page.
|
# <title> on every page.
|
||||||
@ -173,9 +173,8 @@ class Web(callbacks.PluginRegexp):
|
|||||||
if raiseErrors:
|
if raiseErrors:
|
||||||
irc.error(_('Connection to %s timed out') % url, Raise=True)
|
irc.error(_('Connection to %s timed out') % url, Raise=True)
|
||||||
else:
|
else:
|
||||||
self.log.info('Web plugins TitleSnarfer: URL <%s> timed out',
|
selg.log.info('Web plugins TitleSnarfer: URL <%s> timed out',
|
||||||
url)
|
url)
|
||||||
return
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if raiseErrors:
|
if raiseErrors:
|
||||||
irc.error(_('That URL raised <' + str(e)) + '>',
|
irc.error(_('That URL raised <' + str(e)) + '>',
|
||||||
@ -187,14 +186,9 @@ class Web(callbacks.PluginRegexp):
|
|||||||
|
|
||||||
encoding = None
|
encoding = None
|
||||||
if 'Content-Type' in fd.headers:
|
if 'Content-Type' in fd.headers:
|
||||||
# using p.partition('=') instead of 'p.split('=', 1)' because,
|
mime_params = [p.split('=', 1)
|
||||||
# unlike RFC 7231, RFC 9110 allows an empty parameter list
|
|
||||||
# after ';':
|
|
||||||
# * https://www.rfc-editor.org/rfc/rfc9110.html#name-media-type
|
|
||||||
# * https://www.rfc-editor.org/rfc/rfc9110.html#parameter
|
|
||||||
mime_params = [p.partition('=')
|
|
||||||
for p in fd.headers['Content-Type'].split(';')[1:]]
|
for p in fd.headers['Content-Type'].split(';')[1:]]
|
||||||
mime_params = {k.strip(): v.strip() for (k, sep, v) in mime_params}
|
mime_params = {k.strip(): v.strip() for (k, v) in mime_params}
|
||||||
if mime_params.get('charset'):
|
if mime_params.get('charset'):
|
||||||
encoding = mime_params['charset']
|
encoding = mime_params['charset']
|
||||||
|
|
||||||
|
@ -85,12 +85,6 @@ class WebTestCase(ChannelPluginTestCase):
|
|||||||
'title https://www.reddit.com/r/irc/',
|
'title https://www.reddit.com/r/irc/',
|
||||||
'Internet Relay Chat')
|
'Internet Relay Chat')
|
||||||
|
|
||||||
def testTitleMarcinfo(self):
|
|
||||||
# Checks that we don't crash on 'Content-Type: text/html;'
|
|
||||||
self.assertResponse(
|
|
||||||
'title https://marc.info/?l=openbsd-tech&m=169841790407370&w=2',
|
|
||||||
"'Removing syscall(2) from libc and kernel' - MARC")
|
|
||||||
|
|
||||||
def testTitleSnarfer(self):
|
def testTitleSnarfer(self):
|
||||||
try:
|
try:
|
||||||
conf.supybot.plugins.Web.titleSnarfer.setValue(True)
|
conf.supybot.plugins.Web.titleSnarfer.setValue(True)
|
||||||
|
@ -941,7 +941,7 @@ class Directory(registry.String):
|
|||||||
if os.path.isabs(filename):
|
if os.path.isabs(filename):
|
||||||
filename = os.path.abspath(filename)
|
filename = os.path.abspath(filename)
|
||||||
selfAbs = os.path.abspath(myself)
|
selfAbs = os.path.abspath(myself)
|
||||||
commonPrefix = os.path.commonpath([selfAbs, filename])
|
commonPrefix = os.path.commonprefix([selfAbs, filename])
|
||||||
filename = filename[len(commonPrefix):]
|
filename = filename[len(commonPrefix):]
|
||||||
elif not os.path.isabs(myself):
|
elif not os.path.isabs(myself):
|
||||||
if filename.startswith(myself):
|
if filename.startswith(myself):
|
||||||
@ -954,7 +954,7 @@ class DataFilename(registry.String):
|
|||||||
def __call__(self):
|
def __call__(self):
|
||||||
v = super(DataFilename, self).__call__()
|
v = super(DataFilename, self).__call__()
|
||||||
dataDir = supybot.directories.data()
|
dataDir = supybot.directories.data()
|
||||||
if not v.startswith("/") and not v.startswith(dataDir):
|
if not v.startswith(dataDir):
|
||||||
v = os.path.basename(v)
|
v = os.path.basename(v)
|
||||||
v = os.path.join(dataDir, v)
|
v = os.path.join(dataDir, v)
|
||||||
self.setValue(v)
|
self.setValue(v)
|
||||||
|
@ -337,7 +337,7 @@ class Static(SupyHTTPServerCallback):
|
|||||||
super(Static, self).__init__()
|
super(Static, self).__init__()
|
||||||
self._mimetype = mimetype
|
self._mimetype = mimetype
|
||||||
def doGetOrHead(self, handler, path, write_content):
|
def doGetOrHead(self, handler, path, write_content):
|
||||||
response = get_template(path[1:]) # strip leading /
|
response = get_template(path)
|
||||||
if minisix.PY3:
|
if minisix.PY3:
|
||||||
response = response.encode()
|
response = response.encode()
|
||||||
handler.send_response(200)
|
handler.send_response(200)
|
||||||
|
@ -468,12 +468,7 @@ class IrcChannel(object):
|
|||||||
return True
|
return True
|
||||||
if world.testing:
|
if world.testing:
|
||||||
return False
|
return False
|
||||||
if not ircutils.isUserHostmask(hostmask):
|
assert ircutils.isUserHostmask(hostmask), 'got %s' % hostmask
|
||||||
# Treat messages from a server (e.g. snomasks) as not ignored, as
|
|
||||||
# the ignores system doesn't understand them
|
|
||||||
if '.' not in hostmask:
|
|
||||||
raise ValueError("Expected full prefix, got %r" % hostmask)
|
|
||||||
return False
|
|
||||||
if self.checkBan(hostmask):
|
if self.checkBan(hostmask):
|
||||||
return True
|
return True
|
||||||
if self.ignores.match(hostmask):
|
if self.ignores.match(hostmask):
|
||||||
|
@ -104,7 +104,7 @@ def _main():
|
|||||||
|
|
||||||
def main():
|
def main():
|
||||||
try:
|
try:
|
||||||
_main()
|
main()
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -36,7 +36,6 @@ import sys
|
|||||||
import time
|
import time
|
||||||
import shutil
|
import shutil
|
||||||
import fnmatch
|
import fnmatch
|
||||||
from tempfile import TemporaryDirectory
|
|
||||||
started = time.time()
|
started = time.time()
|
||||||
|
|
||||||
import supybot
|
import supybot
|
||||||
@ -44,24 +43,21 @@ import logging
|
|||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
# We need to do this before we import conf.
|
# We need to do this before we import conf.
|
||||||
main_temp_dir = TemporaryDirectory()
|
if not os.path.exists('test-conf'):
|
||||||
|
os.mkdir('test-conf')
|
||||||
|
|
||||||
os.makedirs(os.path.join(main_temp_dir.name, 'conf'))
|
registryFilename = os.path.join('test-conf', 'test.conf')
|
||||||
os.makedirs(os.path.join(main_temp_dir.name, 'data'))
|
fd = open(registryFilename, 'w')
|
||||||
os.makedirs(os.path.join(main_temp_dir.name, 'logs'))
|
fd.write("""
|
||||||
|
|
||||||
registryFilename = os.path.join(main_temp_dir.name, 'conf', 'test.conf')
|
|
||||||
with open(registryFilename, 'w') as fd:
|
|
||||||
fd.write("""
|
|
||||||
supybot.directories.backup: /dev/null
|
supybot.directories.backup: /dev/null
|
||||||
supybot.directories.conf: {temp_conf}
|
supybot.directories.conf: %(base_dir)s/test-conf
|
||||||
supybot.directories.data: {temp_data}
|
supybot.directories.data: %(base_dir)s/test-data
|
||||||
supybot.directories.log: {temp_logs}
|
supybot.directories.log: %(base_dir)s/test-logs
|
||||||
supybot.reply.whenNotCommand: True
|
supybot.reply.whenNotCommand: True
|
||||||
supybot.log.stdout: False
|
supybot.log.stdout: False
|
||||||
supybot.log.stdout.level: ERROR
|
supybot.log.stdout.level: ERROR
|
||||||
supybot.log.level: DEBUG
|
supybot.log.level: DEBUG
|
||||||
supybot.log.format: %(levelname)s %(message)s
|
supybot.log.format: %%(levelname)s %%(message)s
|
||||||
supybot.log.plugins.individualLogfiles: False
|
supybot.log.plugins.individualLogfiles: False
|
||||||
supybot.protocols.irc.throttleTime: 0
|
supybot.protocols.irc.throttleTime: 0
|
||||||
supybot.reply.whenAddressedBy.chars: @
|
supybot.reply.whenAddressedBy.chars: @
|
||||||
@ -71,11 +67,8 @@ supybot.networks.testnet2.server: should.not.need.this
|
|||||||
supybot.networks.testnet3.server: should.not.need.this
|
supybot.networks.testnet3.server: should.not.need.this
|
||||||
supybot.nick: test
|
supybot.nick: test
|
||||||
supybot.databases.users.allowUnregistration: True
|
supybot.databases.users.allowUnregistration: True
|
||||||
""".format(
|
""" % {'base_dir': os.getcwd()})
|
||||||
temp_conf=os.path.join(main_temp_dir.name, 'conf'),
|
fd.close()
|
||||||
temp_data=os.path.join(main_temp_dir.name, 'data'),
|
|
||||||
temp_logs=os.path.join(main_temp_dir.name, 'logs')
|
|
||||||
))
|
|
||||||
|
|
||||||
import supybot.registry as registry
|
import supybot.registry as registry
|
||||||
registry.open_registry(registryFilename)
|
registry.open_registry(registryFilename)
|
||||||
@ -258,9 +251,6 @@ def main():
|
|||||||
if result.wasSuccessful():
|
if result.wasSuccessful():
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
else:
|
else:
|
||||||
# Deactivate autocleaning for the temporary directiories to allow inspection.
|
|
||||||
main_temp_dir._finalizer.detach()
|
|
||||||
print(f"Temporary directory path: {main_temp_dir.name}")
|
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
@ -350,23 +350,6 @@ class IrcChannelTestCase(IrcdbTestCase):
|
|||||||
c.removeBan(banmask)
|
c.removeBan(banmask)
|
||||||
self.assertFalse(c.checkIgnored(prefix))
|
self.assertFalse(c.checkIgnored(prefix))
|
||||||
|
|
||||||
# Only full n!u@h is accepted here
|
|
||||||
self.assertRaises(ValueError, c.checkIgnored, 'foo')
|
|
||||||
|
|
||||||
def testIgnoredServerNames(self):
|
|
||||||
c = ircdb.IrcChannel()
|
|
||||||
# Server names are not handled by the ignores system, so this is false
|
|
||||||
self.assertFalse(c.checkIgnored('irc.example.com'))
|
|
||||||
# But we should treat full prefixes that match nick!user@host normally,
|
|
||||||
# even if they include "." like a server name
|
|
||||||
prefix = 'irc.example.com!bar@baz'
|
|
||||||
banmask = ircutils.banmask(prefix)
|
|
||||||
self.assertFalse(c.checkIgnored(prefix))
|
|
||||||
c.addIgnore(banmask)
|
|
||||||
self.assertTrue(c.checkIgnored(prefix))
|
|
||||||
c.removeIgnore(banmask)
|
|
||||||
self.assertFalse(c.checkIgnored(prefix))
|
|
||||||
|
|
||||||
class IrcNetworkTestCase(IrcdbTestCase):
|
class IrcNetworkTestCase(IrcdbTestCase):
|
||||||
def testDefaults(self):
|
def testDefaults(self):
|
||||||
n = ircdb.IrcNetwork()
|
n = ircdb.IrcNetwork()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user