mirror of https://github.com/Mikaela/Limnoria.git
Fediverse: Add URL snarfer.
This commit is contained in:
parent
5908b86635
commit
d1854cfc9b
|
@ -232,10 +232,10 @@ def actor_url(localuser, hostname):
|
||||||
def get_actor(localuser, hostname):
|
def get_actor(localuser, hostname):
|
||||||
url = actor_url(localuser, hostname)
|
url = actor_url(localuser, hostname)
|
||||||
|
|
||||||
return get_actor_from_url(url)
|
return get_resource_from_url(url)
|
||||||
|
|
||||||
|
|
||||||
def get_actor_from_url(url):
|
def get_resource_from_url(url):
|
||||||
content = signed_request(url, headers={"Accept": ACTIVITY_MIMETYPE})
|
content = signed_request(url, headers={"Accept": ACTIVITY_MIMETYPE})
|
||||||
|
|
||||||
assert content is not None
|
assert content is not None
|
||||||
|
|
|
@ -51,9 +51,10 @@ def configure(advanced):
|
||||||
|
|
||||||
|
|
||||||
Fediverse = conf.registerPlugin("Fediverse")
|
Fediverse = conf.registerPlugin("Fediverse")
|
||||||
|
conf.registerGroup(Fediverse, "snarfers")
|
||||||
conf.registerChannelValue(
|
conf.registerChannelValue(
|
||||||
Fediverse,
|
Fediverse.snarfers,
|
||||||
"usernameSnarfer",
|
"username",
|
||||||
registry.Boolean(
|
registry.Boolean(
|
||||||
False,
|
False,
|
||||||
_(
|
_(
|
||||||
|
@ -62,6 +63,28 @@ conf.registerChannelValue(
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
conf.registerChannelValue(
|
||||||
|
Fediverse.snarfers,
|
||||||
|
"profile",
|
||||||
|
registry.Boolean(
|
||||||
|
False,
|
||||||
|
_(
|
||||||
|
"""Determines whether the bot will output the profile of
|
||||||
|
URLs to Fediverse accounts it sees in channel messages."""
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
conf.registerChannelValue(
|
||||||
|
Fediverse.snarfers,
|
||||||
|
"status",
|
||||||
|
registry.Boolean(
|
||||||
|
False,
|
||||||
|
_(
|
||||||
|
"""Determines whether the bot will output the content of
|
||||||
|
statuses whose URLs it sees in channel messages."""
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
conf.registerGroup(Fediverse, "format")
|
conf.registerGroup(Fediverse, "format")
|
||||||
conf.registerGroup(Fediverse.format, "statuses")
|
conf.registerGroup(Fediverse.format, "statuses")
|
||||||
|
|
|
@ -34,7 +34,7 @@ import importlib
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
|
|
||||||
from supybot import utils, callbacks, httpserver
|
from supybot import utils, callbacks, httpserver
|
||||||
from supybot.commands import wrap
|
from supybot.commands import urlSnarfer, wrap
|
||||||
from supybot.i18n import PluginInternationalization
|
from supybot.i18n import PluginInternationalization
|
||||||
|
|
||||||
from . import activitypub as ap
|
from . import activitypub as ap
|
||||||
|
@ -102,7 +102,7 @@ class FediverseHttp(httpserver.SupyHTTPServerCallback):
|
||||||
],
|
],
|
||||||
"id": actor_url,
|
"id": actor_url,
|
||||||
"preferredUsername": hostname,
|
"preferredUsername": hostname,
|
||||||
"type": "Person",
|
"type": "Service",
|
||||||
"publicKey": {
|
"publicKey": {
|
||||||
"id": actor_url + "#main-key",
|
"id": actor_url + "#main-key",
|
||||||
"owner": actor_url,
|
"owner": actor_url,
|
||||||
|
@ -117,7 +117,7 @@ class Fediverse(callbacks.PluginRegexp):
|
||||||
"""Fetches information from ActivityPub servers."""
|
"""Fetches information from ActivityPub servers."""
|
||||||
|
|
||||||
threaded = True
|
threaded = True
|
||||||
regexps = ["usernameSnarfer"]
|
regexps = ["usernameSnarfer", "urlSnarfer_"]
|
||||||
|
|
||||||
def __init__(self, irc):
|
def __init__(self, irc):
|
||||||
super().__init__(irc)
|
super().__init__(irc)
|
||||||
|
@ -152,7 +152,7 @@ class Fediverse(callbacks.PluginRegexp):
|
||||||
match = utils.web.urlRe.match(username)
|
match = utils.web.urlRe.match(username)
|
||||||
if match:
|
if match:
|
||||||
# TODO: error handling
|
# TODO: error handling
|
||||||
actor = ap.get_actor_from_url(match.group(0))
|
actor = ap.get_resource_from_url(match.group(0))
|
||||||
username = self._format_actor_username(actor)
|
username = self._format_actor_username(actor)
|
||||||
else:
|
else:
|
||||||
irc.errorInvalid("fediverse username", username)
|
irc.errorInvalid("fediverse username", username)
|
||||||
|
@ -232,26 +232,64 @@ class Fediverse(callbacks.PluginRegexp):
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _format_profile(self, irc, msg, actor):
|
||||||
|
return _("\x02%s\x02 (%s): %s") % (
|
||||||
|
actor["name"],
|
||||||
|
self._format_actor_username(actor),
|
||||||
|
utils.web.htmlToText(actor["summary"]),
|
||||||
|
)
|
||||||
|
|
||||||
def usernameSnarfer(self, irc, msg, match):
|
def usernameSnarfer(self, irc, msg, match):
|
||||||
if callbacks.addressed(irc, msg):
|
if callbacks.addressed(irc, msg):
|
||||||
return
|
return
|
||||||
|
if not self.registryValue(
|
||||||
|
"snarfers.username", msg.channel, irc.network
|
||||||
|
):
|
||||||
|
return
|
||||||
try:
|
try:
|
||||||
actor = self._get_actor(irc, match.group(0))
|
actor = self._get_actor(irc, match.group(0))
|
||||||
except ap.ActivityPubError:
|
except ap.ActivityPubError:
|
||||||
# Be silent on errors
|
# Be silent on errors
|
||||||
return
|
return
|
||||||
|
|
||||||
irc.reply(
|
irc.reply(self._format_profile(irc, msg, actor))
|
||||||
_("\x02%s\x02 (%s): %s")
|
|
||||||
% (
|
|
||||||
actor["name"],
|
|
||||||
self._format_actor_username(actor),
|
|
||||||
utils.web.htmlToText(actor["summary"]),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
usernameSnarfer.__doc__ = _username_regexp.pattern
|
usernameSnarfer.__doc__ = _username_regexp.pattern
|
||||||
|
|
||||||
|
@urlSnarfer
|
||||||
|
def urlSnarfer_(self, irc, msg, match):
|
||||||
|
channel = msg.channel
|
||||||
|
network = irc.network
|
||||||
|
url = match.group(0)
|
||||||
|
if not channel:
|
||||||
|
return
|
||||||
|
if callbacks.addressed(irc, msg):
|
||||||
|
return
|
||||||
|
snarf_profile = self.registryValue(
|
||||||
|
"snarfers.profile", channel, network
|
||||||
|
)
|
||||||
|
snarf_status = self.registryValue("snarfers.status", channel, network)
|
||||||
|
if not snarf_profile and not snarf_status:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
resource = ap.get_resource_from_url(url)
|
||||||
|
except ap.ActivityPubError:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
if snarf_profile and resource["type"] in ("Person", "Service"):
|
||||||
|
irc.reply(self._format_profile(irc, msg, resource))
|
||||||
|
elif snarf_status and resource["type"] in (
|
||||||
|
"Create",
|
||||||
|
"Note",
|
||||||
|
"Announce",
|
||||||
|
):
|
||||||
|
irc.reply(self._format_status(irc, msg, resource))
|
||||||
|
except ap.ActivityPubError:
|
||||||
|
return
|
||||||
|
|
||||||
|
urlSnarfer_.__doc__ = utils.web._httpUrlRe
|
||||||
|
|
||||||
@wrap(["somethingWithoutSpaces"])
|
@wrap(["somethingWithoutSpaces"])
|
||||||
def featured(self, irc, msg, args, username):
|
def featured(self, irc, msg, args, username):
|
||||||
"""<@user@instance>
|
"""<@user@instance>
|
||||||
|
|
|
@ -166,6 +166,60 @@ OUTBOX_VALUE = {
|
||||||
}
|
}
|
||||||
OUTBOX_DATA = json.dumps(OUTBOX_VALUE).encode()
|
OUTBOX_DATA = json.dumps(OUTBOX_VALUE).encode()
|
||||||
|
|
||||||
|
STATUS_URL = "https://example.org/users/someuser/statuses/1234"
|
||||||
|
STATUS_VALUE = {
|
||||||
|
"id": "https://example.org/users/someuser/statuses/1234/activity",
|
||||||
|
"type": "Create",
|
||||||
|
"actor": "https://example.org/users/someuser",
|
||||||
|
"published": "2020-05-08T01:23:45Z",
|
||||||
|
"to": ["https://example.org/users/someuser/followers"],
|
||||||
|
"cc": [
|
||||||
|
"https://www.w3.org/ns/activitystreams#Public",
|
||||||
|
"https://example.com/users/FirstAuthor",
|
||||||
|
],
|
||||||
|
"object": {
|
||||||
|
"id": "https://example.org/users/someuser/statuses/1234",
|
||||||
|
"type": "Note",
|
||||||
|
"summary": None,
|
||||||
|
"inReplyTo": "https://example.com/users/FirstAuthor/statuses/42",
|
||||||
|
"published": "2020-05-08T01:23:45Z",
|
||||||
|
"url": "https://example.org/@FirstAuthor/42",
|
||||||
|
"attributedTo": "https://example.org/users/someuser",
|
||||||
|
"to": ["https://example.org/users/someuser/followers"],
|
||||||
|
"cc": [
|
||||||
|
"https://www.w3.org/ns/activitystreams#Public",
|
||||||
|
"https://example.com/users/FirstAuthor",
|
||||||
|
],
|
||||||
|
"sensitive": False,
|
||||||
|
"atomUri": "https://example.org/users/someuser/statuses/1234",
|
||||||
|
"inReplyToAtomUri": "https://example.com/users/FirstAuthor/statuses/42",
|
||||||
|
"conversation": "tag:example.com,2020-05-08:objectId=aaaa:objectType=Conversation",
|
||||||
|
"content": '<p><span class="h-card"><a href="https://example.com/@FirstAuthor" class="u-url mention">@<span>FirstAuthor</span></a></span> I am replying to you</p>',
|
||||||
|
"contentMap": {
|
||||||
|
"en": '<p><span class="h-card"><a href="https://example.com/@FirstAuthor" class="u-url mention">@<span>FirstAuthor</span></a></span> I am replying to you</p>'
|
||||||
|
},
|
||||||
|
"attachment": [],
|
||||||
|
"tag": [
|
||||||
|
{
|
||||||
|
"type": "Mention",
|
||||||
|
"href": "https://example.com/users/FirstAuthor",
|
||||||
|
"name": "@FirstAuthor@example.com",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"replies": {
|
||||||
|
"id": "https://example.org/users/someuser/statuses/1234/replies",
|
||||||
|
"type": "Collection",
|
||||||
|
"first": {
|
||||||
|
"type": "CollectionPage",
|
||||||
|
"next": "https://example.org/users/someuser/statuses/1234/replies?only_other_accounts=true&page=true",
|
||||||
|
"partOf": "https://example.org/users/someuser/statuses/1234/replies",
|
||||||
|
"items": [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
STATUS_DATA = json.dumps(STATUS_VALUE).encode()
|
||||||
|
|
||||||
OUTBOX_FIRSTPAGE_URL = "https://example.org/users/someuser/outbox?page=true"
|
OUTBOX_FIRSTPAGE_URL = "https://example.org/users/someuser/outbox?page=true"
|
||||||
OUTBOX_FIRSTPAGE_VALUE = {
|
OUTBOX_FIRSTPAGE_VALUE = {
|
||||||
"@context": [
|
"@context": [
|
||||||
|
@ -188,57 +242,7 @@ OUTBOX_FIRSTPAGE_VALUE = {
|
||||||
"prev": "https://example.org/users/someuser/outbox?min_id=104135036335976677&page=true",
|
"prev": "https://example.org/users/someuser/outbox?min_id=104135036335976677&page=true",
|
||||||
"partOf": "https://example.org/users/someuser/outbox",
|
"partOf": "https://example.org/users/someuser/outbox",
|
||||||
"orderedItems": [
|
"orderedItems": [
|
||||||
{
|
STATUS_VALUE,
|
||||||
"id": "https://example.org/users/someuser/statuses/1234/activity",
|
|
||||||
"type": "Create",
|
|
||||||
"actor": "https://example.org/users/someuser",
|
|
||||||
"published": "2020-05-08T01:23:45Z",
|
|
||||||
"to": ["https://example.org/users/someuser/followers"],
|
|
||||||
"cc": [
|
|
||||||
"https://www.w3.org/ns/activitystreams#Public",
|
|
||||||
"https://example.com/users/FirstAuthor",
|
|
||||||
],
|
|
||||||
"object": {
|
|
||||||
"id": "https://example.org/users/someuser/statuses/1234",
|
|
||||||
"type": "Note",
|
|
||||||
"summary": None,
|
|
||||||
"inReplyTo": "https://example.com/users/FirstAuthor/statuses/42",
|
|
||||||
"published": "2020-05-08T01:23:45Z",
|
|
||||||
"url": "https://example.org/@FirstAuthor/42",
|
|
||||||
"attributedTo": "https://example.org/users/someuser",
|
|
||||||
"to": ["https://example.org/users/someuser/followers"],
|
|
||||||
"cc": [
|
|
||||||
"https://www.w3.org/ns/activitystreams#Public",
|
|
||||||
"https://example.com/users/FirstAuthor",
|
|
||||||
],
|
|
||||||
"sensitive": False,
|
|
||||||
"atomUri": "https://example.org/users/someuser/statuses/1234",
|
|
||||||
"inReplyToAtomUri": "https://example.com/users/FirstAuthor/statuses/42",
|
|
||||||
"conversation": "tag:example.com,2020-05-08:objectId=aaaa:objectType=Conversation",
|
|
||||||
"content": '<p><span class="h-card"><a href="https://example.com/@FirstAuthor" class="u-url mention">@<span>FirstAuthor</span></a></span> I am replying to you</p>',
|
|
||||||
"contentMap": {
|
|
||||||
"en": '<p><span class="h-card"><a href="https://example.com/@FirstAuthor" class="u-url mention">@<span>FirstAuthor</span></a></span> I am replying to you</p>'
|
|
||||||
},
|
|
||||||
"attachment": [],
|
|
||||||
"tag": [
|
|
||||||
{
|
|
||||||
"type": "Mention",
|
|
||||||
"href": "https://example.com/users/FirstAuthor",
|
|
||||||
"name": "@FirstAuthor@example.com",
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"replies": {
|
|
||||||
"id": "https://example.org/users/someuser/statuses/1234/replies",
|
|
||||||
"type": "Collection",
|
|
||||||
"first": {
|
|
||||||
"type": "CollectionPage",
|
|
||||||
"next": "https://example.org/users/someuser/statuses/1234/replies?only_other_accounts=true&page=true",
|
|
||||||
"partOf": "https://example.org/users/someuser/statuses/1234/replies",
|
|
||||||
"items": [],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "https://example.org/users/someuser/statuses/1235/activity",
|
"id": "https://example.org/users/someuser/statuses/1235/activity",
|
||||||
"type": "Create",
|
"type": "Create",
|
||||||
|
@ -517,7 +521,12 @@ class FediverseTestCase(ChannelPluginTestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
def testProfileSnarfer(self):
|
def testProfileSnarfer(self):
|
||||||
with conf.supybot.plugins.Fediverse.usernameSnarfer.context(True):
|
with self.mockRequests([]):
|
||||||
|
self.assertSnarfNoResponse(
|
||||||
|
"aaa @nonexistinguser@example.org bbb", timeout=1
|
||||||
|
)
|
||||||
|
|
||||||
|
with conf.supybot.plugins.Fediverse.snarfers.username.context(True):
|
||||||
expected_requests = [
|
expected_requests = [
|
||||||
(HOSTMETA_URL, HOSTMETA_DATA),
|
(HOSTMETA_URL, HOSTMETA_DATA),
|
||||||
(WEBFINGER_URL, WEBFINGER_DATA),
|
(WEBFINGER_URL, WEBFINGER_DATA),
|
||||||
|
@ -540,6 +549,28 @@ class FediverseTestCase(ChannelPluginTestCase):
|
||||||
"aaa @nonexistinguser@example.org bbb", timeout=1
|
"aaa @nonexistinguser@example.org bbb", timeout=1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def testProfileUrlSnarfer(self):
|
||||||
|
with self.mockRequests([]):
|
||||||
|
self.assertSnarfNoResponse(
|
||||||
|
"aaa https://example.org/users/someuser bbb", timeout=1
|
||||||
|
)
|
||||||
|
|
||||||
|
with conf.supybot.plugins.Fediverse.snarfers.profile.context(True):
|
||||||
|
expected_requests = [(ACTOR_URL, utils.web.Error("blah"))]
|
||||||
|
|
||||||
|
with self.mockRequests(expected_requests):
|
||||||
|
self.assertSnarfNoResponse(
|
||||||
|
"aaa https://example.org/users/someuser bbb", timeout=1
|
||||||
|
)
|
||||||
|
|
||||||
|
expected_requests = [(ACTOR_URL, ACTOR_DATA)]
|
||||||
|
|
||||||
|
with self.mockRequests(expected_requests):
|
||||||
|
self.assertSnarfResponse(
|
||||||
|
"aaa https://example.org/users/someuser bbb",
|
||||||
|
"\x02someuser\x02 (@someuser@example.org): My Biography",
|
||||||
|
)
|
||||||
|
|
||||||
def testProfileUnknown(self):
|
def testProfileUnknown(self):
|
||||||
expected_requests = [
|
expected_requests = [
|
||||||
(HOSTMETA_URL, HOSTMETA_DATA),
|
(HOSTMETA_URL, HOSTMETA_DATA),
|
||||||
|
@ -596,5 +627,62 @@ class FediverseTestCase(ChannelPluginTestCase):
|
||||||
+ "Status Content",
|
+ "Status Content",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def testStatusUrlSnarfer(self):
|
||||||
|
with self.mockRequests([]):
|
||||||
|
self.assertSnarfNoResponse(
|
||||||
|
"aaa https://example.org/users/someuser/statuses/1234 bbb",
|
||||||
|
timeout=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
with conf.supybot.plugins.Fediverse.snarfers.status.context(True):
|
||||||
|
expected_requests = [
|
||||||
|
(STATUS_URL, STATUS_DATA),
|
||||||
|
(ACTOR_URL, utils.web.Error("blah")),
|
||||||
|
]
|
||||||
|
|
||||||
|
with self.mockRequests(expected_requests):
|
||||||
|
self.assertSnarfNoResponse(
|
||||||
|
"aaa https://example.org/users/someuser/statuses/1234 bbb",
|
||||||
|
timeout=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
expected_requests = [
|
||||||
|
(STATUS_URL, STATUS_DATA),
|
||||||
|
(ACTOR_URL, ACTOR_DATA),
|
||||||
|
]
|
||||||
|
|
||||||
|
with self.mockRequests(expected_requests):
|
||||||
|
self.assertSnarfResponse(
|
||||||
|
"aaa https://example.org/users/someuser/statuses/1234 bbb",
|
||||||
|
"\x02someuser (@someuser@example.org)\x02: "
|
||||||
|
+ "@ FirstAuthor I am replying to you",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def testSnarferType(self):
|
||||||
|
# Sends a request, notices it's a status, gives up
|
||||||
|
with conf.supybot.plugins.Fediverse.snarfers.profile.context(True):
|
||||||
|
expected_requests = [
|
||||||
|
(STATUS_URL, STATUS_DATA),
|
||||||
|
]
|
||||||
|
|
||||||
|
with self.mockRequests(expected_requests):
|
||||||
|
self.assertSnarfNoResponse(
|
||||||
|
"aaa https://example.org/users/someuser/statuses/1234 bbb",
|
||||||
|
timeout=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Sends a request, notices it's a profile, gives up
|
||||||
|
with conf.supybot.plugins.Fediverse.snarfers.profile.context(True):
|
||||||
|
expected_requests = [
|
||||||
|
(ACTOR_URL, ACTOR_DATA),
|
||||||
|
]
|
||||||
|
|
||||||
|
with self.mockRequests(expected_requests):
|
||||||
|
self.assertSnarfNoResponse(
|
||||||
|
"aaa https://example.org/users/someuser/ bbb",
|
||||||
|
timeout=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79:
|
# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79:
|
||||||
|
|
Loading…
Reference in New Issue