Allow providing a URL fragment for posts

A number of folks use a single page for multiple posts, using a URL
Fragment parameter to denote which of the posts on the given page should
be used, and are used to this experience with Bridgy.

To allow use of this on Bridgy Fed, too, we can add support for
discovering the ID of the `h-entry` to be Webmention'd in the same way
that we do with [Bridgy].

This largely copies the structure of the existing tests, copying
`test_activitypub_follow`, and adding in multiple posts on the page.

For debugging purposes, we can make sure to log out the `fragment`.

[Bridgy]: f760c0d10e
pull/297/head
Jamie Tanna 2022-11-18 10:18:35 +00:00 zatwierdzone przez Ryan Barrett
rodzic d0c16d8849
commit 39cb90e533
3 zmienionych plików z 100 dodań i 2 usunięć

Wyświetl plik

@ -9,6 +9,7 @@
<li><a href="#use">How do I use it?</a></li>
<li><a href="#setup">How do I set it up?</a></li>
<li><a href="#image">How do I include an image in a post?</a></li>
<li><a href="#fragment">Can I publish just one part of a page?</a></li>
<li><a href="#discovery">How can people on the fediverse find me?</a></li>
<li><a href="#troubleshooting">I tried it, and it didn't work!</a></li>
<li><a href="#cost">How much does it cost?</a></li>
@ -188,6 +189,16 @@ I love scotch. Scotchy scotchy scotch.
</p>
</li>
<li id="fragment" class="question">Can I publish just one part of a page?</li>
<li class="answer">
<p>If that HTML element has its own id, then sure! Just put the id in the fragment of the URL that you publish. For example, to publish the <code>bar</code> post here:</p>
<pre>&lt;div id="<span class='value'>a</span>" class="<span class='keyword'>h-entry</span>"&gt;<span class='value'>foo</span>&lt;/div&gt;
&lt;div id="<span class='value'>b</span>" class="<span class='keyword'>h-entry</span>"&gt;<span class='value'>bar</span>&lt;/div&gt;
&lt;div id="<span class='value'>c</span>" class="<span class='keyword'>h-entry</span>"&gt;<span class='value'>baz</span>&lt;/div&gt;
</pre>
<p>...just add the id to your page's URL in a fragment, e.g. <code>http://site/post#b</code> here.</p>
</li>
<li id="discovery" class="question">How can people on the fediverse find me?</li>
<li class="answer">
<p>In general, all you have to do is use Bridgy Fed to interact with the fediverse once. Send an original post from your site, like or repost something, follow someone, etc. Then, when other users search for <code>@yourdomain.com@yourdomain.com</code>, they should find your profile!

Wyświetl plik

@ -774,6 +774,92 @@ class WebmentionTest(testutil.TestCase):
self.assertEqual('a', followers[0].src)
self.assertEqual('http://followee', followers[0].dest)
def test_activitypub_follow_fragment(self, mock_get, mock_post):
self.follow_html = """\
<html>
<body>
<article class=h-entry id=1>
<h1>Ignored</h1>
</article>
<article class=h-entry id=2>
<a class="u-url" href="http://a/follow#2"></a>
<a class="u-follow-of" href="http://followee"></a>
<a class="p-author h-card" href="https://orig">Ms. Baz</a>
<a href="http://localhost/"></a>
</article>
</body>
</html>
"""
self.follow = requests_response(
self.follow_html, content_type=CONTENT_TYPE_HTML)
self.follow_mf2 = util.parse_mf2(self.follow_html, url='http://a/follow')
self.follow_as2 = {
'@context': 'https://www.w3.org/ns/activitystreams',
'type': 'Follow',
'id': 'http://localhost/r/http://a/follow#2',
'url': 'http://localhost/r/http://a/follow#2',
'object': 'http://followee',
'actor': {
'id': 'http://localhost/orig',
'name': 'Ms. ☕ Baz',
'preferredUsername': 'orig',
'type': 'Person',
'url': 'http://localhost/r/https://orig',
},
'to': [as2.PUBLIC_AUDIENCE],
}
mock_get.side_effect = [self.follow, self.actor]
mock_post.return_value = requests_response('abc xyz')
got = self.client.post('/webmention', data={
'source': 'http://a/follow#2',
'target': 'https://fed.brid.gy/',
})
self.assertEqual(200, got.status_code)
mock_get.assert_has_calls((
self.req('http://a/follow#2'),
self.req('http://followee/', headers=CONNEG_HEADERS_AS2_HTML),
))
args, kwargs = mock_post.call_args
self.assertEqual(('https://foo.com/inbox',), args)
self.assertEqual(self.follow_as2, json_loads(kwargs['data']))
headers = kwargs['headers']
self.assertEqual(CONTENT_TYPE_AS2, headers['Content-Type'])
rsa_key = kwargs['auth'].header_signer._rsa._key
self.assertEqual(self.key.private_pem(), rsa_key.exportKey())
activity = Activity.get_by_id('http://a/follow__2 http://followee/')
self.assertEqual(['a'], activity.domain)
self.assertEqual('out', activity.direction)
self.assertEqual('activitypub', activity.protocol)
self.assertEqual('complete', activity.status)
follow_html_for_fragement = """
<html>
<body>
<article class=h-entry id=2>
<a class="u-url" href="http://a/follow#2"></a>
<a class="u-follow-of" href="http://followee"></a>
<a class="p-author h-card" href="https://orig">Ms. Baz</a>
<a href="http://localhost/"></a>
</article>
</html>
"""
follow_mf2_for_fragment = util.parse_mf2(follow_html_for_fragement, url='http://a/follow')
# for some reason, this doesn't get picked up correctly when it's a fragment
follow_mf2_for_fragment['debug']['markup parser'] = 'unknown'
self.assertEqual(follow_mf2_for_fragment, json_loads(activity.source_mf2))
followers = Follower.query().fetch()
self.assertEqual(1, len(followers))
self.assertEqual('http://followee a', followers[0].key.id())
self.assertEqual('a', followers[0].src)
self.assertEqual('http://followee', followers[0].dest)
def test_activitypub_error_no_salmon_fallback(self, mock_get, mock_post):
mock_get.side_effect = [self.follow, self.actor]
mock_post.return_value = requests_response(

Wyświetl plik

@ -48,7 +48,8 @@ class Webmention(View):
source_resp = common.requests_get(source)
self.source_url = source_resp.url or source
self.source_domain = urllib.parse.urlparse(self.source_url).netloc.split(':')[0]
self.source_mf2 = util.parse_mf2(source_resp)
fragment = urllib.parse.urlparse(self.source_url).fragment
self.source_mf2 = util.parse_mf2(source_resp, id=fragment)
# logger.debug(f'Parsed mf2 for {source_resp.url} : {json_dumps(self.source_mf2 indent=2)}')
@ -64,7 +65,7 @@ class Webmention(View):
if not entry:
error(f'No microformats2 found on {self.source_url}')
logger.info(f'First entry: {json_dumps(entry, indent=2)}')
logger.info(f'First entry (id={fragment}: {json_dumps(entry, indent=2)}')
# make sure it has url, since we use that for AS2 id, which is required
# for ActivityPub.
props = entry.setdefault('properties', {})