Browse Source

check for a rel=alternate to existing parsed mf2 JSON and use that instead

pull/78/head
Aaron Parecki 5 years ago
parent
commit
154b7e874a
No known key found for this signature in database GPG Key ID: 276C2817346D6056
14 changed files with 1474 additions and 16 deletions
  1. +2
    -0
      controllers/Parse.php
  2. +5
    -6
      lib/XRay.php
  3. +1
    -1
      lib/XRay/Fetcher.php
  4. +25
    -1
      lib/XRay/Formats/HTML.php
  5. +1
    -1
      lib/XRay/Formats/Mf2.php
  6. +9
    -7
      lib/XRay/Parser.php
  7. +52
    -0
      tests/ParseTest.php
  8. +19
    -0
      tests/data/source.example.com/rel-alternate-fallback
  9. +16
    -0
      tests/data/source.example.com/rel-alternate-fallback.json
  10. +580
    -0
      tests/data/source.example.com/rel-alternate-mf2-json
  11. +692
    -0
      tests/data/source.example.com/rel-alternate-mf2-json.json
  12. +20
    -0
      tests/data/source.example.com/rel-alternate-not-found
  13. +19
    -0
      tests/data/source.example.com/rel-alternate-priority
  14. +33
    -0
      tests/data/source.example.com/rel-alternate-priority.json

+ 2
- 0
controllers/Parse.php View File

@ -128,6 +128,8 @@ class Parse {
$data['original'] = $parsed['original']; $data['original'] = $parsed['original'];
if(isset($parsed['source-format'])) if(isset($parsed['source-format']))
$data['source-format'] = $parsed['source-format']; $data['source-format'] = $parsed['source-format'];
if(isset($parsed['url']) && $parsed['url'] != $result['url'])
$data['parsed-url'] = $parsed['url'];
return $this->respond($response, 200, $data); return $this->respond($response, 200, $data);
} }

+ 5
- 6
lib/XRay.php View File

@ -38,9 +38,9 @@ class XRay {
$result = $parser->parse($body, $url, $opts); $result = $parser->parse($body, $url, $opts);
if(!isset($opts['include_original']) || !$opts['include_original']) if(!isset($opts['include_original']) || !$opts['include_original'])
unset($result['original']); unset($result['original']);
$result['url'] = $url;
$result['code'] = isset($result['code']) ? $result['code'] : $code;
$result['source-format'] = isset($result['source-format']) ? $result['source-format'] : null;
if(!isset($result['url'])) $result['url'] = $url;
if(!isset($result['code'])) $result['code'] = $code;
if(!isset($result['source-format'])) $result['source-format'] = null;
return $result; return $result;
} }
@ -49,10 +49,9 @@ class XRay {
$result = $parser->parse($mf2json, $url, $opts); $result = $parser->parse($mf2json, $url, $opts);
if(!isset($opts['include_original']) || !$opts['include_original']) if(!isset($opts['include_original']) || !$opts['include_original'])
unset($result['original']); unset($result['original']);
$result['url'] = $url;
$result['source-format'] = isset($result['source-format']) ? $result['source-format'] : null;
if(!isset($result['url'])) $result['url'] = $url;
if(!isset($result['source-format'])) $result['source-format'] = null;
return $result; return $result;
} }
} }

+ 1
- 1
lib/XRay/Fetcher.php View File

@ -99,7 +99,7 @@ class Fetcher {
return [ return [
'error' => 'invalid_content', 'error' => 'invalid_content',
'error_description' => 'The server did not return a recognized content type', 'error_description' => 'The server did not return a recognized content type',
'content_type' => $result['headers']['Content-Type'],
'content_type' => $result['headers']['Content-Type'] ?? null,
'url' => $result['url'], 'url' => $result['url'],
'code' => $result['code'] 'code' => $result['code']
]; ];

+ 25
- 1
lib/XRay/Formats/HTML.php View File

@ -91,9 +91,33 @@ class HTML extends Format {
} }
} }
// Now start pulling in the data from the page. Start by looking for microformats2
$mf2 = \mf2\Parse($html, $url); $mf2 = \mf2\Parse($html, $url);
// Check for a rel=alternate link to a Microformats JSON representation, and use that instead
if(isset($mf2['rel-urls'])) {
foreach($mf2['rel-urls'] as $relurl => $reltype) {
if(in_array('alternate', $reltype['rels']) && $reltype['type'] == 'application/mf2+json') {
// Fetch and parse the MF2 JSON link instead
$jsonpage = $http->get($relurl, [
'Accept' => 'application/mf2+json,application/json'
]);
// Skip and fall back to parsing the HTML if anything about this request fails
if(!$jsonpage['error'] && $jsonpage['body']) {
$jsondata = json_decode($jsonpage['body'],true);
if($jsondata) {
$data = Formats\Mf2::parse($jsondata, $url, $http, $opts);
if($data && is_array($data) && isset($data['data']['type'])) {
$data['url'] = $relurl;
$data['source-format'] = 'mf2+json';
return $data;
}
}
}
}
}
}
// Now start pulling in the data from the page. Start by looking for microformats2
if($mf2 && count($mf2['items']) > 0) { if($mf2 && count($mf2['items']) > 0) {
$data = Formats\Mf2::parse($mf2, $url, $http, $opts); $data = Formats\Mf2::parse($mf2, $url, $http, $opts);
if($data) { if($data) {

+ 1
- 1
lib/XRay/Formats/Mf2.php View File

@ -16,7 +16,7 @@ class Mf2 extends Format {
} }
public static function parse($mf2, $url, $http, $opts=[]) { public static function parse($mf2, $url, $http, $opts=[]) {
if(count($mf2['items']) == 0)
if(!isset($mf2['items']) || count($mf2['items']) == 0)
return false; return false;
// If they are expecting a feed, always return a feed or an error // If they are expecting a feed, always return a feed or an error

+ 9
- 7
lib/XRay/Parser.php View File

@ -54,12 +54,13 @@ class Parser {
} }
if(substr($body, 0, 1) == '{') { if(substr($body, 0, 1) == '{') {
$feeddata = json_decode($body, true);
if($feeddata && isset($feeddata['version']) && $feeddata['version'] == 'https://jsonfeed.org/version/1') {
return Formats\JSONFeed::parse($feeddata, $url);
} elseif($feeddata && isset($feeddata['items'][0]['type']) && isset($feeddata['items'][0]['properties'])) {
// Check if an mf2 JSON object was passed in
$data = Formats\Mf2::parse($feeddata, $url, $this->http, $opts);
$parsed = json_decode($body, true);
if($parsed && isset($parsed['version']) && $parsed['version'] == 'https://jsonfeed.org/version/1') {
return Formats\JSONFeed::parse($parsed, $url);
// TODO: check for an activitystreams object too
} elseif($parsed && isset($parsed['items'][0]['type']) && isset($parsed['items'][0]['properties'])) {
// Check if an mf2 JSON string was passed in
$data = Formats\Mf2::parse($parsed, $url, $this->http, $opts);
$data['source-format'] = 'mf2+json'; $data['source-format'] = 'mf2+json';
return $data; return $data;
} }
@ -67,7 +68,8 @@ class Parser {
// No special parsers matched, parse for Microformats now // No special parsers matched, parse for Microformats now
$data = Formats\HTML::parse($this->http, $body, $url, $opts); $data = Formats\HTML::parse($this->http, $body, $url, $opts);
$data['source-format'] = 'mf2+html';
if(!isset($data['source-format']))
$data['source-format'] = 'mf2+html';
return $data; return $data;
} }

+ 52
- 0
tests/ParseTest.php View File

@ -876,4 +876,56 @@ class ParseTest extends PHPUnit_Framework_TestCase {
$this->assertEquals('The content of the blog post', $data['data']['content']['text']); $this->assertEquals('The content of the blog post', $data['data']['content']['text']);
} }
public function testRelAlternateToMf2JSON() {
$url = 'http://source.example.com/rel-alternate-mf2-json';
$response = $this->parse(['url' => $url]);
$body = $response->getContent();
$this->assertEquals(200, $response->getStatusCode());
$data = json_decode($body, true);
$this->assertEquals('mf2+json', $data['source-format']);
$this->assertEquals('http://source.example.com/rel-alternate-mf2-json.json', $data['parsed-url']);
$this->assertEquals('Pretty great to see a new self-hosted IndieAuth server! Congrats @nilshauk, and great project name! https://twitter.com/nilshauk/status/1017485223716630528', $data['data']['content']['text']);
}
public function testRelAlternateToNotFoundURL() {
$url = 'http://source.example.com/rel-alternate-not-found';
$response = $this->parse(['url' => $url]);
$body = $response->getContent();
$this->assertEquals(200, $response->getStatusCode());
$data = json_decode($body, true);
$this->assertEquals('mf2+html', $data['source-format']);
$this->assertArrayNotHasKey('parsed-url', $data);
$this->assertEquals('Test content with a rel alternate link to a 404 page', $data['data']['content']['text']);
}
public function testRelAlternatePrioritizesJSON() {
$url = 'http://source.example.com/rel-alternate-priority';
$response = $this->parse(['url' => $url]);
$body = $response->getContent();
$this->assertEquals(200, $response->getStatusCode());
$data = json_decode($body, true);
$this->assertEquals('mf2+json', $data['source-format']);
$this->assertEquals('http://source.example.com/rel-alternate-priority.json', $data['parsed-url']);
$this->assertEquals('This should be the content from XRay', $data['data']['content']['text']);
}
public function testRelAlternateFallsBackOnInvalidJSON() {
$url = 'http://source.example.com/rel-alternate-fallback';
$response = $this->parse(['url' => $url]);
$body = $response->getContent();
$this->assertEquals(200, $response->getStatusCode());
$data = json_decode($body, true);
$this->assertEquals('mf2+html', $data['source-format']);
$this->assertArrayNotHasKey('parsed-url', $data);
$this->assertEquals('XRay should use this content since the JSON in the rel-alternate is invalid', $data['data']['content']['text']);
}
} }

+ 19
- 0
tests/data/source.example.com/rel-alternate-fallback View File

@ -0,0 +1,19 @@
HTTP/1.1 200 OK
Server: Apache
Date: Wed, 09 Dec 2015 03:29:14 GMT
Content-Type: text/html
Connection: keep-alive
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Test</title>
<link rel="alternate" type="application/mf2+json" href="http://source.example.com/rel-alternate-fallback.json" />
</head>
<body>
<div class="h-entry">
<p class="p-content">XRay should use this content since the JSON in the rel-alternate is invalid</p>
</div>
</body>
</html>

+ 16
- 0
tests/data/source.example.com/rel-alternate-fallback.json View File

@ -0,0 +1,16 @@
HTTP/1.1 200 OK
Server: Apache
Date: Wed, 09 Dec 2015 03:29:14 GMT
Content-Type: application/mf2+json
Connection: keep-alive
{
"type": [
"h-entry"
],
"properties": {
"content": [
"XRay should not use this content since the JSON must be an entire page parsed result"
]
}
}

+ 580
- 0
tests/data/source.example.com/rel-alternate-mf2-json View File

@ -0,0 +1,580 @@
HTTP/1.1 200 OK
Server: Apache
Date: Wed, 09 Dec 2015 03:29:14 GMT
Content-Type: text/html
Connection: keep-alive
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Pretty great to see a new self-hosted IndieAuth server! ... &bull; Aaron Parecki</title>
<link rel="alternate" type="application/mf2+json" href="http://source.example.com/rel-alternate-mf2-json.json" />
<link rel="alternate" type="application/jf2+json" href="http://source.example.com/rel-alternate-mf2-json.jf2" />
<link rel="alternate" type="application/activity+json" href="http://source.example.com/rel-alternate-mf2-json.as2" />
<meta name="twitter:card" content="summary" />
<meta name="twitter:site" content="@aaronpk" />
<meta name="twitter:creator" content="@aaronpk" />
<meta property="og:url" content="https://aaronparecki.com/2018/07/12/10/indieauth" />
<meta property="og:type" content="website" />
<meta property="og:title" content="Pretty great to see a new self-hosted IndieAuth server! ..." />
<meta property="og:description" content="Pretty great to see a new self-hosted IndieAuth server! Congrats @nilshauk, and great project name!" />
<meta property="og:site_name" content="Aaron Parecki" />
<link rel="webmention" href="https://webmention.io/aaronpk/webmention">
<link rel="stylesheet" type="text/css" href="/semantic/2.2.6/semantic.min.css">
<script src="/assets/jquery-1.12.0.min.js"></script>
<script src="/semantic/2.2.6/semantic.min.js"></script>
<link rel="stylesheet" href="/assets/icomoon/style.css">
<link rel="stylesheet" href="/assets/weather-icons/css/weather-icons.css">
<script src="/assets/featherlight-1.5.0/featherlight.min.js"></script>
<link rel="stylesheet" href="/assets/featherlight-1.5.0/featherlight.min.css">
<script src="/assets/jquery-ui-1.11.4.custom/jquery-ui.min.js"></script>
<link rel="stylesheet" href="/assets/jquery-ui-1.11.4.custom/jquery-ui.min.css">
<link rel="stylesheet" href="/assets/admin.css">
<link rel="stylesheet" href="/assets/pulse.css">
<link rel="stylesheet" href="/assets/styles.3.css">
<link rel="stylesheet" href="/site/styles.1.css">
<link rel="stylesheet" href="/assets/carbon.css">
<script src="/assets/photo-albums/justified-layout.js"></script>
<script src="/assets/photo-albums/photo-layout.js"></script>
<script src="/assets/js-cookie.js"></script>
<link rel="stylesheet" href="/assets/story.css">
<script src="/assets/story.js"></script>
<link rel="openid.delegate" href="https://aaronparecki.com/">
<link rel="openid.server" href="https://openid.indieauth.com/openid">
</head>
<body>
<div class="sticky-footer-content">
<div class="top-bar">
<div class="ui container">
<form action="/search" method="get" class="search item">
<div class="ui icon input">
<input type="text" name="q" placeholder="Search..." value="">
<i class="search icon"></i>
</div>
</form>
<span class="item">89&deg;F</span>
<span class="weather item">
<i class="wi wi-day-cloudy" title="Partly Cloudy in Portland"></i>
</span>
<span class="time item"></span>
<span class="battery item"></span>
<div id="logged-in-menu">
<span class="item action-buttons">
<a href="#" class="admin-edit-post"><i class="pencil icon"></i></a>
<a href="/settings"><i class="setting icon"></i></a>
<a href="/logout"><i class="sign out icon"></i></a>
</span>
<div class="ui modal admin-edit-post-modal">
<i class="close icon"></i>
<div class="header">Edit Post</div>
<div class="content">
<div class="ui top attached tabular menu">
<a class="item active" data-tab="details">Details</a>
<a class="item" data-tab="syndication">Syndication</a>
<a class="item" data-tab="visibility">Visibility</a>
<a class="item" data-tab="maintenance">Maintenance</a>
</div>
<div class="ui bottom attached tab segment active" data-tab="details">
<div class="inline fields">
<label>Date</label>
<div class="ui left icon input" style="min-width: 300px;">
<input type="text" class="edit-date" style="padding:0.3em 1em;">
<i class="calendar icon"></i>
</div>
</div>
<div class="inline fields" style="margin-top: 4px;">
<label>Slug</label>
<div class="ui left icon input" style="min-width: 300px;">
<input type="text" class="edit-slug" style="padding:0.3em 1em;">
<i class="linkify icon"></i>
</div>
</div>
<h3>Tags</h3>
<div class="tags" style="margin-right: 4px; margin-bottom: 4px; float: left"></div>
<div class="ui small left icon input new-tags">
<input type="text" placeholder="" style="padding:0.3em 1em;">
<i class="tag icon"></i>
</div>
<h3>Channels</h3>
<div class="ui form new-channels">
<select class="ui fluid dropdown">
<option value="">--</option>
<option value="articles">articles</option>
<option value="ate">ate</option>
<option value="bookmarks">bookmarks</option>
<option value="checkins">checkins</option>
<option value="code">code</option>
<option value="drank">drank</option>
<option value="events">events</option>
<option value="flights">flights</option>
<option value="grid">grid</option>
<option value="health">health</option>
<option value="likes">likes</option>
<option value="music">music</option>
<option value="notes">notes</option>
<option value="photos">photos</option>
<option value="presentations">presentations</option>
<option value="primary">primary</option>
<option value="pushups">pushups</option>
<option value="recipes">recipes</option>
<option value="replies">replies</option>
<option value="reposts">reposts</option>
<option value="reviews">reviews</option>
<option value="rides">rides</option>
<option value="rsvps">rsvps</option>
<option value="runs">runs</option>
<option value="scrobbles">scrobbles</option>
<option value="sleep">sleep</option>
<option value="transport">transport</option>
<option value="travel">travel</option>
<option value="venues">venues</option>
<option value="videos">videos</option>
<option value="walks">walks</option>
<option value="watched">watched</option>
<option value="weight">weight</option>
</select>
</div>
<div class="channels" style="margin-right: 4px; margin-top: 4px;"></div>
</div>
<div class="ui bottom attached tab segment" data-tab="syndication">
<h3>Syndication URLs</h3>
<div class="syndications"></div>
<div class="ui left icon input new-syndication">
<input type="text" placeholder="" style="padding:0.3em 1em;">
<i class="linkify icon"></i>
</div>
<h3>Syndicate</h3>
<div class="syndicate-to"></div>
<div style="clear:both;"></div>
</div>
<div class="ui bottom attached tab segment" data-tab="visibility">
<h3>Status</h3>
<div class="ui form post-status">
<select class="ui fluid dropdown">
<option value="published">Published</option>
<option value="draft">Draft</option>
</select>
</div>
<h3>Audience</h3>
<h3>Privacy</h3>
</div>
<div class="ui bottom attached tab segment" data-tab="maintenance">
<h3>Rebuild Index</h3>
<p>Rebuild the database index from the copy on disk.</p>
<button class="ui button maintenance" data-task="rebuild_index">Rebuild Index</button>
<h3>Regenerate Map</h3>
<p>If this post contains geo data, this button will regenerate the map.</p>
<button class="ui button maintenance" data-task="generate_map">Regenerate Map</button>
</div>
</div>
<div class="actions">
<div class="error" style="float: left; color: #963131; padding-top: 0.7em;"></div>
<button class="ui red delete button">Delete</button>
<button class="ui cancel button">Cancel</button>
<button class="ui primary save button">Save</button>
</div>
</div>
</div>
</div>
</div>
<div class="ui container">
<div class="site-header">
<div class="align-bottom">
<div class="left">
<h1><a href="/">Aaron Parecki</a></h1>
</div>
<div class="right">
<ul>
<li><a href="/articles">Articles</a></li>
<li><a href="/notes">Notes</a></li>
<li><a href="/projects/">Projects</a></li>
</ul>
</div>
</div>
</div>
<div class="post-list">
<ul>
<li class="h-entry post-entry post " id="post-id-41472">
<div style="" class="content-area has-responses ">
<div class="pad">
<div class="post-text e-content p-name content-type-plain">Pretty great to see a new self-hosted IndieAuth server! Congrats <a href="https://twitter.com/nilshauk">@nilshauk</a>, and great project name! <a href="https://twitter.com/nilshauk/status/1017485223716630528"><span class="protocol">https://</span>twitter.com/nilshauk/status/1017485223716630528</a></div>
</div>
<div class="metaline pad">
<i class="marker icon"></i>
<span class="p-location h-adr">
<span class="p-locality">Portland</span>,
<span class="p-region">Oregon</span>,
<span class="p-country">USA</span>
<span class="weather">
<span>&bull;</span>
<i class="wi wi-day-sunny" title="Clear"></i>
84&deg;F
</span>
<data class="p-latitude" value="45.5354"></data>
<data class="p-longitude" value="-122.62099"></data>
</span>
</div>
<div class="metaline tags pad" style="float: right;">
#<a href="/tag/indieauth" class="p-category">indieauth</a>
</div>
<div class="metaline pad">
<a href="https://aaronparecki.com/2018/07/12/10/indieauth" class="u-url">
<time class="dt-published" datetime="2018-07-12T13:02:04-07:00">
Thu, Jul 12, 2018 1:02pm -07:00
</time>
</a>
<span class="syndications">
<a href="https://twitter.com/aaronpk/status/1017500609631567872" class="u-syndication syndication"><i class="twitter icon"></i></a>
</span>
</div>
<a class="u-author" href="/"></a>
<div class="metaline responses-summary pad">
<span><i class="star empty icon"></i> <span class="p-pk-num-likes">6</span> likes</span>
<span><i class="comment outline icon"></i> <span class="p-pk-num-replies">3</span> replies</span>
</div>
<div style="clear:both;"></div>
</div>
<div class="responses" id="responses">
<ul class="facepile">
<li class="group"><i class="star empty icon"></i></li>
<!-- TODO: add overlay with twitter/facebook/instagram icon depending on author URL -->
<li class="p-like h-cite">
<a href="https://twitter.com/aaronpk/status/1017500609631567872#favorited-by-75276568" class="u-url">
<span class="p-author h-card">
<img class="u-photo" src="https://pkcdn.xyz/pbs.twimg.com/40aa1ddaef3cf2abc9392d731dd6f150b2ec097243351c2c1a46f061c4127e2c.jpg" height="36">
<a style="display:none;" class="p-name u-url" href="https://twitter.com/nilshauk">Nils Norman Hauk&aring;s</a>
</span>
</a>
</li>
<li class="p-like h-cite">
<a href="https://twitter.com/aaronpk/status/1017500609631567872#favorited-by-798123" class="u-url">
<span class="p-author h-card">
<img class="u-photo" src="https://pkcdn.xyz/pbs.twimg.com/fc0678c0424632fb0ce6a0d5bdd2697bf8edbb9086e819f2453aadf1eeb58d5a.jpg" height="36">
<a style="display:none;" class="p-name u-url" href="https://twitter.com/kmelve">Knut Melv&aelig;r</a>
</span>
</a>
</li>
<li class="p-like h-cite">
<a href="https://twitter.com/aaronpk/status/1017500609631567872#favorited-by-1202561" class="u-url">
<span class="p-author h-card">
<img class="u-photo" src="https://pkcdn.xyz/pbs.twimg.com/2a0f14011833a9deca627931442e780a6e00fb3221e7c98a4b1045794d05f8dc.jpg" height="36">
<a style="display:none;" class="p-name u-url" href="https://twitter.com/sull">Michael Sullivan</a>
</span>
</a>
</li>
<li class="p-like h-cite">
<a href="https://twitter.com/aaronpk/status/1017500609631567872#favorited-by-16114199" class="u-url">
<span class="p-author h-card">
<img class="u-photo" src="https://pkcdn.xyz/pbs.twimg.com/36de4c45d8c4c4b420607c8e490e8777ec5b658dfc57b1d9f90b54636dc8c3e7.jpg" height="36">
<a style="display:none;" class="p-name u-url" href="https://twitter.com/cathieleblanc">cathieleblanc</a>
</span>
</a>
</li>
<li class="p-like h-cite">
<a href="https://twitter.com/aaronpk/status/1017500609631567872#favorited-by-961661" class="u-url">
<span class="p-author h-card">
<img class="u-photo" src="https://pkcdn.xyz/pbs.twimg.com/ce4f2f04b015ad5bd826584ceac5e6dbf0c8c1b9be2bdce80df7bc7a1d83bdcd.jpeg" height="36">
<a style="display:none;" class="p-name u-url" href="https://twitter.com/tomwiththeweath">TomWithTheWeather</a>
</span>
</a>
</li>
<li class="p-like h-cite">
<a href="https://twitter.com/aaronpk/status/1017500609631567872#favorited-by-18331731" class="u-url">
<span class="p-author h-card">
<img class="u-photo" src="https://pkcdn.xyz/pbs.twimg.com/500a4883279015b8c0bf56f607034761ddc0c366c0275632662441f25af5351c.png" height="36">
<a style="display:none;" class="p-name u-url" href="https://twitter.com/voxpelli">Pelle Wessman</a>
</span>
</a>
</li>
</ul>
<div style="clear:both;"></div>
<form class="webmention-form ui form" action="https://webmention.io/aaronpk/webmention" method="post">
<div class="fields">
<div class="twelve wide field">
<label>Have you written a <a href="https://indieweb.org/responses">response</a> to this? Let me know the URL:</label>
<input type="url" name="source" class="url">
</div>
<div class="four wide field">
<label>&nbsp;</label>
<input type="submit" class="ui submit button" value="Send Webmention">
</div>
</div>
<div class="status hidden">
<div class="ui message"></div>
</div>
<input type="hidden" name="target" value="https://aaronparecki.com/2018/07/12/10/indieauth">
</form>
<ul>
<li class="p-comment h-cite comment">
<div class="p-author h-card author">
<img class="u-photo" src="https://pkcdn.xyz/pbs.twimg.com/40aa1ddaef3cf2abc9392d731dd6f150b2ec097243351c2c1a46f061c4127e2c.jpg" width="48">
<a class="p-name u-url" href="http://nilsnh.no">Nils Norman Hauk&aring;s</a>
<a class="author_url" href="http://nilsnh.no">nilsnh.no</a>
</div>
<!-- TODO: should this be e-summary if I've truncated it or if it came from the summary? -->
<div class="e-content p-name comment-content">Thank you very much. :)</div>
<div class="metaline">
<a href="https://twitter.com/nilshauk/status/1017501170376560640" class="u-url">
<time class="dt-published" datetime="2018-07-12T20:09:17+00:00">
Thu, Jul 12, 2018 8:09pm +00:00
</time>
</a>
(<a href="https://brid-gy.appspot.com/comment/twitter/aaronpk/1017500609631567872/1017501170376560640">via brid-gy.appspot.com</a>)
</div>
</li>
<li class="p-comment h-cite comment">
<div class="p-author h-card author">
<img class="u-photo" src="https://pkcdn.xyz/pbs.twimg.com/40aa1ddaef3cf2abc9392d731dd6f150b2ec097243351c2c1a46f061c4127e2c.jpg" width="48">
<a class="p-name u-url" href="http://nilsnh.no">Nils Norman Hauk&aring;s</a>
<a class="author_url" href="http://nilsnh.no">nilsnh.no</a>
</div>
<!-- TODO: should this be e-summary if I've truncated it or if it came from the summary? -->
<div class="e-content p-name comment-content">Looking forward to learning more about IndieWeb and hopefully contribute some more. :) I'm like, "surely we can build cooler and more usable services than present silo offerings."</div>
<div class="metaline">
<a href="https://twitter.com/nilshauk/status/1017512103199068160" class="u-url">
<time class="dt-published" datetime="2018-07-12T20:52:44+00:00">
Thu, Jul 12, 2018 8:52pm +00:00
</time>
</a>
(<a href="https://brid-gy.appspot.com/comment/twitter/aaronpk/1017500609631567872/1017512103199068160">via brid-gy.appspot.com</a>)
</div>
</li>
<li class="p-comment h-cite comment">
<div class="p-author h-card author">
<img class="u-photo" src="https://pkcdn.xyz/pbs.twimg.com/2a0f14011833a9deca627931442e780a6e00fb3221e7c98a4b1045794d05f8dc.jpg" width="48">
<a class="p-name u-url" href="https://keybase.io/sull">Michael Sullivan</a>
<a class="author_url" href="https://keybase.io/sull">keybase.io/sull</a>
</div>
<!-- TODO: should this be e-summary if I've truncated it or if it came from the summary? -->
<div class="e-content p-name comment-content">I was just talking about the words “cellar door” the other day. Cool project. <br /><br /><a href="https://en.m.wikipedia.org/wiki/Cellar_door" rel="nofollow">en.m.wikipedia.org/wiki/Cellar_do…</a></div>
<div class="metaline">
<a href="https://twitter.com/sull/status/1017546958590922758" class="u-url">
<time class="dt-published" datetime="2018-07-12T23:11:14+00:00">
Thu, Jul 12, 2018 11:11pm +00:00
</time>
</a>
(<a href="https://brid-gy.appspot.com/comment/twitter/aaronpk/1017500609631567872/1017546958590922758">via brid-gy.appspot.com</a>)
</div>
</li>
</ul>
<div style="clear:both;"></div>
</div>
</li>
</ul>
<div class="additional-info">
Posted in
<a href="/notes" class="u-p3k-channel">/notes</a>
using
<a href="https://quill.p3k.io/">quill.p3k.io</a>
<!-- TODO: show privacy/visibility here, and allow editing if logged in -->
<!-- TODO: allow editing tags and channels here -->
</div>
</div>
</div>
</div>
<footer class="sticky-footer">
<div class="subfooter">
<div class="ui container h-card">
<div class="about">
<div class="image"><a href="/" class="u-url u-uid"><img src="/images/profile.jpg" class="u-photo"></a></div>
<div class="bio">
<div class="p-note">
<p>Hi, I'm <span class="p-name">Aaron<span style="display:none;"> Parecki</span></span>, co-founder of
<a class="p-org h-card" href="https://indieweb.org/">IndieWebCamp</a>.
I maintain <a class="p-org h-card" href="https://oauth.net/">oauth.net</a>, <a href="/oauth/">write and consult about OAuth</a>, and
am the editor of several <a href="/w3c/">W3C specfications</a>. I record <a href="https://backpedal.tv">videos for local conferences</a> and help run a <a href="https://streampdx.com">podcast studio in Portland</a>.</p>
<p>I wrote <a href="https://100.aaronparecki.com/">100 songs in 100 days</a>! I've been <a href="/gps/">tracking my location</a> since 2008,
and write down everything I <a href="/ate">eat</a> and <a href="/drank">drink</a>.
I've <a href="/presentations">spoken</a> at conferences around the world about
<a href="/presentations?tag=indieweb">owning your data</a>,
<a href="/oauth/">OAuth</a>,
<a href="/presentations?tag=quantifiedself">quantified self</a>,
and explained <a href="https://www.youtube.com/watch?v=FGVJ0eXTRpw">why R is a vowel</a>.</p> <time class="dt-bday" datetime="--12-28"></time> </div>
</div>
<div class="right">
<div class="follow">
<a href="/follow?path=primary" class="ui primary button">Follow</a>
</div>
<div class="orgs">
<ul>
<li class="p-org h-card">
<img src="/images/okta.png" alt="" class="u-photo">
<a href="https://developer.okta.com/" class="u-url">
<span class="p-name">Okta</span>
</a>
<a class="p-role" href="https://developer.okta.com/blog/2018/03/27/welcome-aaron-okta">Developer Advocate</a>
</li>
<li class="p-org h-card">
<img src="/images/indiewebcamp.png" alt="" class="u-photo">
<a href="https://indieweb.org/" class="u-url">
<span class="p-name">IndieWebCamp</span>
</a>
<a class="p-role" href="https://indieweb.org/founders">Founder</a>
</li>
<li class="p-org h-card">
<img src="/images/w3c.png" alt="" class="u-photo">
<a href="https://www.w3.org/" class="u-url">
<span class="p-name">W3C</span>
</a>
<a class="p-role" href="/w3c/">Editor</a>
</li>
<li class="p-org h-card">
<img src="/images/streampdx.png" alt="" class="u-photo">
<a href="https://streampdx.com" class="u-url">
<span class="p-name">Stream PDX</span>
</a>
<a class="p-role" href="https://streampdx.com/who">Co-Founder</a>
</li>
<li class="p-org h-card">
<img src="/images/backpedal.png" alt="" class="u-photo">
<a href="https://backpedal.tv" class="u-url">backpedal.tv</a>
</li>
<li><br></li>
<!--
<li><img src="/images/spotify.ico" alt=""> <a href="/sunshine-indie-pop/">Sunshine Indie Pop</a></li>
-->
<li>
<img src="/images/microphone.png" alt="">
<a class="p-callsign u-url" href="https://w7apk.com">W7APK</a>
</li>
<li>⭐️ <a href="https://aaronparecki.com/life-stack/">Life Stack</a></li>
</ul>
<link rel="pgpkey" href="/key.txt">
<link rel="me" href="sms:+15035678642">
<link rel="me" href="https://micro.blog/aaronpk"> </div>
<div class="search">
<form action="/search" method="get">
<div class="ui fluid icon input">
<input type="text" name="q" placeholder="Search..." value="">
<i class="search icon"></i>
</div>
</form>
</div>
</div>
</div>
<div class="channels">
<ul class="footer-links">
<li><a href="/all">All</a></li>
<li><a href="/articles">Articles</a></li>
<li><a href="/bookmarks">Bookmarks</a></li>
<li><a href="/notes">Notes</a></li>
<li><a href="/photos">Photos</a></li>
<li><a href="/replies">Replies</a></li>
<li><a href="/reviews">Reviews</a></li>
<li><a href="/sleep">Sleep</a></li>
<li><a href="/travel">Travel</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</div>
<div class="elsewhere">
<ul class="footer-links">
<li><a href="https://twitter.com/aaronpk" rel=""><i class="twitter icon"></i></a></li>
<li><a href="https://twitter.com/w7apk" rel=""><i class="twitter icon"></i></a></li>
<li><a href="https://facebook.com/aaronpk" rel=""><i class="facebook icon"></i></a></li>
<li><a href="https://instagram.com/aaronpk" rel=""><i class="instagram icon"></i></a></li>
<li><a href="http://flickr.com/aaronpk" rel=""><i class="flickr icon"></i></a></li>
<li><a href="https://github.com/aaronpk" rel=""><i class="github icon"></i></a></li>
<li><a href="https://youtube.com/TheAaronpk" rel=""><i class="youtube play icon"></i></a></li>
<li><a href="https://google.com/+aaronpk" rel=""><i class="google plus icon"></i></a></li>
<li><a href="http://foursquare.com/aaronpk" rel=""><i class="foursquare icon"></i></a></li>
<li><a href="http://www.linkedin.com/in/aaronparecki" rel=""><i class="linkedin icon"></i></a></li>
<li><a href="http://aaronpk.eventbrite.com/" rel=""><i class="icon-eventbrite icon"></i></a></li>
<li><a href="http://www.slideshare.net/aaronpk" rel=""><i class="slideshare icon"></i></a></li>
<li><a href="https://www.beeminder.com/aaronpk" rel=""><i class="icon-beeminder icon"></i></a></li>
<li><a href="http://www.colourlovers.com/lover/aaronpk" rel=""><i class="icon-colourlovers icon"></i></a></li>
<li><a href="http://www.last.fm/user/aaron_pk" rel=""><i class="lastfm icon"></i></a></li>
<li><a href="https://www.w3.org/users/59996" rel=""><i class="icon-w3c icon"></i></a></li>
<li><a href="https://keybase.io/aaronpk/" rel=""><i class="key icon"></i></a></li>
<li><a href="https://cash.me/$aaronpk" rel=""><i class="icon-squarecash icon"></i></a></li>
<li><a href="https://venmo.com/aaronpk" rel=""><i class="icon-venmo icon"></i></a></li>
<li><a href="https://paypal.me/apk" rel=""><i class="paypal icon"></i></a></li>
<li><a href="mailto:aaron@parecki.com" rel=""><i class="mail icon"></i></a></li>
</ul>
</div>
</div>
</div>
<div class="footer">
<a href="/login" class="hidden-login"></a>
<div>
<span>&copy; 1999-2018 by Aaron Parecki.</span>
<span>Powered by <a href="http://p3k.io/">p3k</a>.</span>
<span>This site supports <a href="https://webmention.net/">Webmention</a>.</span>
</div>
<div>
<span>Except where otherwise noted, text content on this site is licensed
under a <a href="http://creativecommons.org/licenses/by/3.0/" rel="license">Creative Commons Attribution 3.0 License</a>.</span>
</div>
<div class="badges" style="padding-top: 8px;">
<a href="https://indieweb.org/"><img src="/assets/badges/indieweb.png" width="80" height="15" alt="IndieWebCamp" style="image-rendering: pixelated;"></a>
<a href="http://microformats.org/"><img src="/assets/badges/microformats.png" width="80" height="15" alt="Microformats" style="image-rendering: pixelated;"></a>
<a href="https://indieweb.org/Webmention"><img src="/assets/badges/webmention.png" width="80" height="15" alt="Webmention" style="image-rendering: pixelated;"></a>
<img src="/assets/badges/w3c-valid-html.png" width="80" height="15" alt="W3C HTML5" style="image-rendering: pixelated;">
<a href="http://creativecommons.org/licenses/by/3.0/"><img src="/assets/badges/cc-commons.png" width="80" height="15" alt="Creative Commons" style="image-rendering: pixelated;"></a>
</div>
</div>
</footer>
<input type="hidden" id="permalink" value="https://aaronparecki.com/2018/07/12/10/indieauth">
<input type="hidden" id="csrf-token" value="Y0HcOvr1tvnBQM4HyRMieFgli4302ByUPMC8w24Q">
<script src="/assets/script.js"></script>
<script src="/assets/webmention.js"></script>
<script src="/assets/admin.js"></script>
</body>
</html>

+ 692
- 0
tests/data/source.example.com/rel-alternate-mf2-json.json View File

@ -0,0 +1,692 @@
HTTP/1.1 200 OK
Server: Apache
Date: Wed, 09 Dec 2015 03:29:14 GMT
Content-Type: application/mf2+json
Connection: keep-alive
{
"items": [
{
"type": [
"h-entry"
],
"properties": {
"name": [
"Pretty great to see a new self-hosted IndieAuth server! Congrats @nilshauk, and great project name! https://twitter.com/nilshauk/status/1017485223716630528"
],
"category": [
"indieauth"
],
"url": [
"https://aaronparecki.com/2018/07/12/10/indieauth"
],
"syndication": [
"https://twitter.com/aaronpk/status/1017500609631567872"
],
"author": [
"https://aaronparecki.com/"
],
"published": [
"2018-07-12T13:02:04-07:00"
],
"content": [
{
"html": "Pretty great to see a new self-hosted IndieAuth server! Congrats <a href=\"https://twitter.com/nilshauk\">@nilshauk</a>, and great project name! <a href=\"https://twitter.com/nilshauk/status/1017485223716630528\"><span class=\"protocol\">https://</span>twitter.com/nilshauk/status/1017485223716630528</a>",
"value": "Pretty great to see a new self-hosted IndieAuth server! Congrats @nilshauk, and great project name! https://twitter.com/nilshauk/status/1017485223716630528"
}
],
"location": [
{
"type": [
"h-adr"
],
"properties": {
"locality": [
"Portland"
],
"region": [
"Oregon"
],
"country": [
"USA"
],
"latitude": [
"45.5354"
],
"longitude": [
"-122.62099"
]
},
"value": "Portland, Oregon, USA \u2022 84\u00b0F"
}
],
"like": [
{
"type": [
"h-cite"
],
"properties": {
"url": [
"https://twitter.com/aaronpk/status/1017500609631567872#favorited-by-75276568"
],
"author": [
{
"type": [
"h-card"
],
"properties": {
"name": [
"Nils Norman Hauk\u00e5s"
],
"photo": [
"https://pkcdn.xyz/pbs.twimg.com/40aa1ddaef3cf2abc9392d731dd6f150b2ec097243351c2c1a46f061c4127e2c.jpg"
],
"url": [
"https://twitter.com/nilshauk"
]
},
"value": "Nils Norman Hauk\u00e5s"
}
]
},
"value": "https://pkcdn.xyz/pbs.twimg.com/40aa1ddaef3cf2abc9392d731dd6f150b2ec097243351c2c1a46f061c4127e2c.jpg Nils Norman Hauk\u00e5s"
},
{
"type": [
"h-cite"
],
"properties": {
"url": [
"https://twitter.com/aaronpk/status/1017500609631567872#favorited-by-798123"
],
"author": [
{
"type": [
"h-card"
],
"properties": {
"name": [
"Knut Melv\u00e6r"
],
"photo": [
"https://pkcdn.xyz/pbs.twimg.com/fc0678c0424632fb0ce6a0d5bdd2697bf8edbb9086e819f2453aadf1eeb58d5a.jpg"
],
"url": [
"https://twitter.com/kmelve"
]
},
"value": "Knut Melv\u00e6r"
}
]
},
"value": "https://pkcdn.xyz/pbs.twimg.com/fc0678c0424632fb0ce6a0d5bdd2697bf8edbb9086e819f2453aadf1eeb58d5a.jpg Knut Melv\u00e6r"
},
{
"type": [
"h-cite"
],
"properties": {
"url": [
"https://twitter.com/aaronpk/status/1017500609631567872#favorited-by-1202561"
],
"author": [
{
"type": [
"h-card"
],
"properties": {
"name": [
"Michael Sullivan"
],
"photo": [
"https://pkcdn.xyz/pbs.twimg.com/2a0f14011833a9deca627931442e780a6e00fb3221e7c98a4b1045794d05f8dc.jpg"
],
"url": [
"https://twitter.com/sull"
]
},
"value": "Michael Sullivan"
}
]
},
"value": "https://pkcdn.xyz/pbs.twimg.com/2a0f14011833a9deca627931442e780a6e00fb3221e7c98a4b1045794d05f8dc.jpg Michael Sullivan"
},
{
"type": [
"h-cite"
],
"properties": {
"url": [
"https://twitter.com/aaronpk/status/1017500609631567872#favorited-by-16114199"
],
"author": [
{
"type": [
"h-card"
],
"properties": {
"name": [
"cathieleblanc"
],
"photo": [
"https://pkcdn.xyz/pbs.twimg.com/36de4c45d8c4c4b420607c8e490e8777ec5b658dfc57b1d9f90b54636dc8c3e7.jpg"
],
"url": [
"https://twitter.com/cathieleblanc"
]
},
"value": "cathieleblanc"
}
]
},
"value": "https://pkcdn.xyz/pbs.twimg.com/36de4c45d8c4c4b420607c8e490e8777ec5b658dfc57b1d9f90b54636dc8c3e7.jpg cathieleblanc"
},
{
"type": [
"h-cite"
],
"properties": {
"url": [
"https://twitter.com/aaronpk/status/1017500609631567872#favorited-by-961661"
],
"author": [
{
"type": [
"h-card"
],
"properties": {
"name": [
"TomWithTheWeather"
],
"photo": [
"https://pkcdn.xyz/pbs.twimg.com/ce4f2f04b015ad5bd826584ceac5e6dbf0c8c1b9be2bdce80df7bc7a1d83bdcd.jpeg"
],
"url": [
"https://twitter.com/tomwiththeweath"
]
},
"value": "TomWithTheWeather"
}
]
},
"value": "https://pkcdn.xyz/pbs.twimg.com/ce4f2f04b015ad5bd826584ceac5e6dbf0c8c1b9be2bdce80df7bc7a1d83bdcd.jpeg TomWithTheWeather"
},
{
"type": [
"h-cite"
],
"properties": {
"url": [
"https://twitter.com/aaronpk/status/1017500609631567872#favorited-by-18331731"
],
"author": [
{
"type": [
"h-card"
],
"properties": {
"name": [
"Pelle Wessman"
],
"photo": [
"https://pkcdn.xyz/pbs.twimg.com/500a4883279015b8c0bf56f607034761ddc0c366c0275632662441f25af5351c.png"
],
"url": [
"https://twitter.com/voxpelli"
]
},
"value": "Pelle Wessman"
}
]
},
"value": "https://pkcdn.xyz/pbs.twimg.com/500a4883279015b8c0bf56f607034761ddc0c366c0275632662441f25af5351c.png Pelle Wessman"
}
],
"comment": [
{
"type": [
"h-cite"
],
"properties": {
"name": [
"Thank you very much. :)"
],
"url": [
"https://twitter.com/nilshauk/status/1017501170376560640"
],
"published": [
"2018-07-12T20:09:17+00:00"
],
"content": [
{
"html": "Thank you very much. :)",
"value": "Thank you very much. :)"
}
],
"author": [
{
"type": [
"h-card"
],
"properties": {
"name": [
"Nils Norman Hauk\u00e5s"
],
"photo": [
"https://pkcdn.xyz/pbs.twimg.com/40aa1ddaef3cf2abc9392d731dd6f150b2ec097243351c2c1a46f061c4127e2c.jpg"
],
"url": [
"http://nilsnh.no"
]
},
"value": "Nils Norman Hauk\u00e5s"
}
]
},
"value": "Thank you very much. :)"
},
{
"type": [
"h-cite"
],
"properties": {
"name": [
"Looking forward to learning more about IndieWeb and hopefully contribute some more. :) I'm like, \"surely we can build cooler and more usable services than present silo offerings.\""
],
"url": [
"https://twitter.com/nilshauk/status/1017512103199068160"
],
"published": [
"2018-07-12T20:52:44+00:00"
],
"content": [
{
"html": "Looking forward to learning more about IndieWeb and hopefully contribute some more. :) I'm like, \"surely we can build cooler and more usable services than present silo offerings.\"",
"value": "Looking forward to learning more about IndieWeb and hopefully contribute some more. :) I'm like, \"surely we can build cooler and more usable services than present silo offerings.\""
}
],
"author": [
{
"type": [
"h-card"
],
"properties": {
"name": [
"Nils Norman Hauk\u00e5s"
],
"photo": [
"https://pkcdn.xyz/pbs.twimg.com/40aa1ddaef3cf2abc9392d731dd6f150b2ec097243351c2c1a46f061c4127e2c.jpg"
],
"url": [
"http://nilsnh.no"
]
},
"value": "Nils Norman Hauk\u00e5s"
}
]
},
"value": "Looking forward to learning more about IndieWeb and hopefully contribute some more. :) I'm like, \"surely we can build cooler and more usable services than present silo offerings.\""
},
{
"type": [
"h-cite"
],
"properties": {
"name": [
"I was just talking about the words \u201ccellar door\u201d the other day. Cool project.\n\nen.m.wikipedia.org/wiki/Cellar_do\u2026"
],
"url": [
"https://twitter.com/sull/status/1017546958590922758"
],
"published": [
"2018-07-12T23:11:14+00:00"
],
"content": [
{
"html": "I was just talking about the words &#x201C;cellar door&#x201D; the other day. Cool project. <br><br><a href=\"https://en.m.wikipedia.org/wiki/Cellar_door\" rel=\"nofollow\">en.m.wikipedia.org/wiki/Cellar_do&#x2026;</a>",
"value": "I was just talking about the words \u201ccellar door\u201d the other day. Cool project.\n\nen.m.wikipedia.org/wiki/Cellar_do\u2026"
}
],
"author": [
{
"type": [
"h-card"
],
"properties": {
"name": [
"Michael Sullivan"
],
"photo": [
"https://pkcdn.xyz/pbs.twimg.com/2a0f14011833a9deca627931442e780a6e00fb3221e7c98a4b1045794d05f8dc.jpg"
],
"url": [
"https://keybase.io/sull"
]
},
"value": "Michael Sullivan"
}
]
},
"value": "I was just talking about the words \u201ccellar door\u201d the other day. Cool project.\n\nen.m.wikipedia.org/wiki/Cellar_do\u2026"
}
]
}
},
{
"type": [
"h-card"
],
"properties": {
"note": [
"Hi, I'm Aaron Parecki, co-founder of IndieWebCamp. I maintain oauth.net, write and consult about OAuth, and am the editor of several W3C specfications. I record videos for local conferences and help run a podcast studio in Portland.\nI wrote 100 songs in 100 days! I've been tracking my location since 2008, and write down everything I eat and drink. I've spoken at conferences around the world about owning your data, OAuth, quantified self, and explained why R is a vowel."
],
"name": [
"Aaron Parecki"
],
"callsign": [
"W7APK"
],
"url": [
"https://aaronparecki.com/",
"https://w7apk.com"
],
"uid": [
"https://aaronparecki.com/"
],
"photo": [
"https://aaronparecki.com/images/profile.jpg"
],
"bday": [
"--12-28"
],
"org": [
{
"type": [
"h-card"
],
"properties": {
"name": [
"IndieWebCamp"
],
"url": [
"https://indieweb.org/"
]
},
"value": "IndieWebCamp"
},
{
"type": [
"h-card"
],
"properties": {
"name": [
"oauth.net"
],
"url": [
"https://oauth.net/"
]
},
"value": "oauth.net"
},
{
"type": [
"h-card"
],
"properties": {
"name": [
"Okta"
],
"role": [
"Developer Advocate"
],
"photo": [
"https://aaronparecki.com/images/okta.png"
],
"url": [
"https://developer.okta.com/"
]
},
"value": "Okta"
},
{
"type": [
"h-card"
],
"properties": {
"name": [
"IndieWebCamp"
],
"role": [
"Founder"
],
"photo": [
"https://aaronparecki.com/images/indiewebcamp.png"
],
"url": [
"https://indieweb.org/"
]
},
"value": "IndieWebCamp"
},
{
"type": [
"h-card"
],
"properties": {
"name": [
"W3C"
],
"role": [
"Editor"
],
"photo": [
"https://aaronparecki.com/images/w3c.png"
],
"url": [
"https://www.w3.org/"
]
},
"value": "W3C"
},
{
"type": [
"h-card"
],
"properties": {
"name": [
"Stream PDX"
],
"role": [
"Co-Founder"
],
"photo": [
"https://aaronparecki.com/images/streampdx.png"
],
"url": [
"https://streampdx.com"
]
},
"value": "Stream PDX"
},
{
"type": [
"h-card"
],
"properties": {
"photo": [
"https://aaronparecki.com/images/backpedal.png"
],
"url": [
"https://backpedal.tv"
],
"name": [
"backpedal.tv"
]
},
"value": "backpedal.tv"
}
]
}
}
],
"rels": {
"alternate": [
"https://aaronparecki.com/2018/07/12/10/indieauth.json",
"https://aaronparecki.com/2018/07/12/10/indieauth.jf2",
"https://aaronparecki.com/2018/07/12/10/indieauth.as2"
],
"webmention": [
"https://webmention.io/aaronpk/webmention"
],
"stylesheet": [
"https://aaronparecki.com/semantic/2.2.6/semantic.min.css",
"https://aaronparecki.com/assets/icomoon/style.css",
"https://aaronparecki.com/assets/weather-icons/css/weather-icons.css",
"https://aaronparecki.com/assets/featherlight-1.5.0/featherlight.min.css",
"https://aaronparecki.com/assets/jquery-ui-1.11.4.custom/jquery-ui.min.css",
"https://aaronparecki.com/assets/admin.css",
"https://aaronparecki.com/assets/pulse.css",
"https://aaronparecki.com/assets/styles.3.css",
"https://aaronparecki.com/site/styles.1.css",
"https://aaronparecki.com/assets/carbon.css",
"https://aaronparecki.com/assets/story.css"
],
"openid.delegate": [
"https://aaronparecki.com/"
],
"openid.server": [
"https://openid.indieauth.com/openid"
],
"nofollow": [
"https://en.m.wikipedia.org/wiki/Cellar_door"
],
"pgpkey": [
"https://aaronparecki.com/key.txt"
],
"me": [
"sms:+15035678642",
"https://micro.blog/aaronpk"
],
"license": [
"http://creativecommons.org/licenses/by/3.0/"
]
},
"rel-urls": {
"https://aaronparecki.com/2018/07/12/10/indieauth.json": {
"type": "application/mf2+json",
"rels": [
"alternate"
]
},
"https://aaronparecki.com/2018/07/12/10/indieauth.jf2": {
"type": "application/jf2+json",
"rels": [
"alternate"
]
},
"https://aaronparecki.com/2018/07/12/10/indieauth.as2": {
"type": "application/activity+json",
"rels": [
"alternate"
]
},
"https://webmention.io/aaronpk/webmention": {
"rels": [
"webmention"
]
},
"https://aaronparecki.com/semantic/2.2.6/semantic.min.css": {
"type": "text/css",
"rels": [
"stylesheet"
]
},
"https://aaronparecki.com/assets/icomoon/style.css": {
"rels": [
"stylesheet"
]
},
"https://aaronparecki.com/assets/weather-icons/css/weather-icons.css": {
"rels": [
"stylesheet"
]
},
"https://aaronparecki.com/assets/featherlight-1.5.0/featherlight.min.css": {
"rels": [
"stylesheet"
]
},
"https://aaronparecki.com/assets/jquery-ui-1.11.4.custom/jquery-ui.min.css": {
"rels": [
"stylesheet"
]
},
"https://aaronparecki.com/assets/admin.css": {
"rels": [
"stylesheet"
]
},
"https://aaronparecki.com/assets/pulse.css": {
"rels": [
"stylesheet"
]
},
"https://aaronparecki.com/assets/styles.3.css": {
"rels": [
"stylesheet"
]
},
"https://aaronparecki.com/site/styles.1.css": {
"rels": [
"stylesheet"
]
},
"https://aaronparecki.com/assets/carbon.css": {
"rels": [
"stylesheet"
]
},
"https://aaronparecki.com/assets/story.css": {
"rels": [
"stylesheet"
]
},
"https://aaronparecki.com/": {
"rels": [
"openid.delegate"
]
},
"https://openid.indieauth.com/openid": {
"rels": [
"openid.server"
]
},
"https://en.m.wikipedia.org/wiki/Cellar_door": {
"text": "en.m.wikipedia.org/wiki/Cellar_do\u2026",
"rels": [
"nofollow"
]
},
"https://aaronparecki.com/key.txt": {
"rels": [
"pgpkey"
]
},
"sms:+15035678642": {
"rels": [
"me"
]
},
"https://micro.blog/aaronpk": {
"rels": [
"me"
]
},
"http://creativecommons.org/licenses/by/3.0/": {
"text": "Creative Commons Attribution 3.0 License",
"rels": [
"license"
]
}
}
}

+ 20
- 0
tests/data/source.example.com/rel-alternate-not-found View File

@ -0,0 +1,20 @@
HTTP/1.1 200 OK
Server: Apache
Date: Wed, 09 Dec 2015 03:29:14 GMT
Content-Type: text/html
Connection: keep-alive
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Test</title>
<link rel="alternate" type="application/mf2+json" href="http://source.example.com/rel-alternate-not-found.json" />
</head>
<body>
<div class="h-entry">
<p class="p-content">Test content with a rel alternate link to a 404 page</p>
</div>
</body>
</html>

+ 19
- 0
tests/data/source.example.com/rel-alternate-priority View File

@ -0,0 +1,19 @@
HTTP/1.1 200 OK
Server: Apache
Date: Wed, 09 Dec 2015 03:29:14 GMT
Content-Type: text/html
Connection: keep-alive
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Test</title>
<link rel="alternate" type="application/mf2+json" href="http://source.example.com/rel-alternate-priority.json" />
</head>
<body>
<div class="h-entry">
<p class="p-content">This should not be the content from XRay</p>
</div>
</body>
</html>

+ 33
- 0
tests/data/source.example.com/rel-alternate-priority.json View File

@ -0,0 +1,33 @@
HTTP/1.1 200 OK
Server: Apache
Date: Wed, 09 Dec 2015 03:29:14 GMT
Content-Type: application/mf2+json
Connection: keep-alive
{
"items": [
{
"type": [
"h-entry"
],
"properties": {
"content": [
"This should be the content from XRay"
]
}
}
],
"rels": {
"alternate": [
"http://source.example.com/rel-alternate-priority.json"
]
},
"rel-urls": {
"http://source.example.com/rel-alternate-priority.json": {
"type": "application/mf2+json",
"rels": [
"alternate"
]
}
}
}

Loading…
Cancel
Save