' . \wp_kses( __( 'The post\'s excerpt (default 400 chars). length attribute is optional.', 'activitypub' ), 'default' ) . '
' .
+ '
[ap_permalink type="url"]
' .
+ '
' . \wp_kses( __( 'The post\'s permalink. Type can be either: url or html (an <a /> tag). type attribute is optional.', 'activitypub' ), 'default' ) . '
' .
+ '
[ap_shortlink type="url"]
' .
+ '
' . \wp_kses( __( 'The post\'s shortlink. Type can be either url or html (an <a /> tag). I can recommend Hum, to prettify the Shortlinks. type attribute is optional.', 'activitypub' ), 'default' ) . '
' .
+ '
[ap_hashtags]
' .
+ '
' . \wp_kses( __( 'The post\'s tags as hashtags.', 'activitypub' ), 'default' ) . '
' .
+ '
[ap_hashcats]
' .
+ '
' . \wp_kses( __( 'The post\'s categories as hashtags.', 'activitypub' ), 'default' ) . '
' .
+ '
[ap_image type=full]
' .
+ '
' . \wp_kses( __( 'The URL for the post\'s featured image, defaults to full size. The type attribute can be any of the following: thumbnail, medium, large, full. type attribute is optional.', 'activitypub' ), 'default' ) . '
' . \wp_kses( __( 'The post\'s date/time formated as "date @ time".', 'activitypub' ), 'default' ) . '
' .
+ '
[ap_blogurl]
' .
+ '
' . \wp_kses( __( 'The URL to the site.', 'activitypub' ), 'default' ) . '
' .
+ '
[ap_blogname]
' .
+ '
' . \wp_kses( __( 'The name of the site.', 'activitypub' ), 'default' ) . '
' .
+ '
[ap_blogdesc]
' .
+ '
' . \wp_kses( __( 'The description of the site.', 'activitypub' ), 'default' ) . '
' .
+ '
' .
+ '
' . __( 'You may also use any Shortcode normally available to you on your site, however be aware that Shortcodes may significantly increase the size of your content depending on what they do.', 'activitypub' ) . '
' .
+ '
' . __( 'Note: the old Template Tags are now deprecated and automatically converted to the new ones.', 'activitypub' ) . '
' .
+ '
' . \wp_kses( \__( 'Let me know if you miss a Template Tag.', 'activitypub' ), 'activitypub' ) . '
' . \__( 'The Fediverse is a new word made of two words: "federation" + "universe"', 'activitypub' ) . '
' .
'
' . \__( 'It is a federated social network running on free open software on a myriad of computers across the globe. Many independent servers are interconnected and allow people to interact with one another. There\'s no one central site: you choose a server to register. This ensures some decentralization and sovereignty of data. Fediverse (also called Fedi) has no built-in advertisements, no tricky algorithms, no one big corporation dictating the rules. Instead we have small cozy communities of like-minded people. Welcome!', 'activitypub' ) . '
' . \__( 'What is ActivityPub?', 'activitypub' ) . '
' .
- '
' . \__( 'ActivityPub is a decentralized social networking protocol based on the ActivityStreams 2.0 data format. ActivityPub is an official W3C recommended standard published by the W3C Social Web Working Group. It provides a client to server API for creating, updating and deleting content, as well as a federated server to server API for delivering notifications and subscribing to content.', 'activitypub' ) . '
' . \__( 'ActivityPub is a decentralized social networking protocol based on the ActivityStreams 2.0 data format. ActivityPub is an official W3C recommended standard published by the W3C Social Web Working Group. It provides a client to server API for creating, updating and deleting content, as well as a federated server to server API for delivering notifications and subscribing to content.', 'activitypub' ) . '
' .
+ '
' . \__( 'WebFinger', 'activitypub' ) . '
' .
'
' . \__( 'WebFinger is used to discover information about people or other entities on the Internet that are identified by a URI using standard Hypertext Transfer Protocol (HTTP) methods over a secure transport. A WebFinger resource returns a JavaScript Object Notation (JSON) object describing the entity that is queried. The JSON object is referred to as the JSON Resource Descriptor (JRD).', 'activitypub' ) . '
' .
'
' . \__( 'For a person, the type of information that might be discoverable via WebFinger includes a personal profile address, identity service, telephone number, or preferred avatar. For other entities on the Internet, a WebFinger resource might return JRDs containing link relations that enable a client to discover, for example, that a printer can print in color on A4 paper, the physical location of a server, or other static information.', 'activitypub' ) . '
' .
'
' . \__( 'On Mastodon [and other Plattforms], user profiles can be hosted either locally on the same website as yours, or remotely on a completely different website. The same username may be used on a different domain. Therefore, a Mastodon user\'s full mention consists of both the username and the domain, in the form @username@domain. In practical terms, @user@example.com is not the same as @user@example.org. If the domain is not included, Mastodon will try to find a local user named @username. However, in order to deliver to someone over ActivityPub, the @username@domain mention is not enough – mentions must be translated to an HTTPS URI first, so that the remote actor\'s inbox and outbox can be found. (This paragraph is copied from the Mastodon Documentation)', 'activitypub' ) . '
' . \__( 'NodeInfo is an effort to create a standardized way of exposing metadata about a server running one of the distributed social networks. The two key goals are being able to get better insights into the user base of distributed social networking and the ability to build tools that allow users to choose the best fitting software and server for their needs.', 'activitypub' ) . '
- Leave list empty to support all HTML elements. Default: %s', 'activitypub' ),
- \esc_html( ACTIVITYPUB_ALLOWED_HTML )
- ),
- 'default'
- );
- ?>
+
diff --git a/tests/bootstrap.php b/tests/bootstrap.php
index 3867ab2..9a4fd6b 100644
--- a/tests/bootstrap.php
+++ b/tests/bootstrap.php
@@ -19,14 +19,9 @@ require_once $_tests_dir . '/includes/functions.php';
*/
function _manually_load_plugin() {
require \dirname( \dirname( __FILE__ ) ) . '/activitypub.php';
-
- // Load the Friends plugin if available to test the integrations.
- $friends_plugin = \dirname( \dirname( \dirname( __FILE__ ) ) ) . '/friends/friends.php';
- if ( file_exists( $friends_plugin ) ) {
- require $friends_plugin;
- }
}
\tests_add_filter( 'muplugins_loaded', '_manually_load_plugin' );
// Start up the WP testing environment.
require $_tests_dir . '/includes/bootstrap.php';
+require __DIR__ . '/class-activitypub-testcase-cache-http.php';
diff --git a/tests/class-activitypub-testcase-cache-http.php b/tests/class-activitypub-testcase-cache-http.php
new file mode 100644
index 0000000..b8b0e43
--- /dev/null
+++ b/tests/class-activitypub-testcase-cache-http.php
@@ -0,0 +1,105 @@
+server = $wp_rest_server;
+ do_action( 'rest_api_init' );
+
+ add_filter(
+ 'rest_url',
+ function() {
+ return get_option( 'home' ) . '/wp-json/';
+ }
+ );
+
+ add_filter( 'pre_http_request', array( get_called_class(), 'pre_http_request' ), 10, 3 );
+ add_filter( 'http_response', array( get_called_class(), 'http_response' ), 10, 3 );
+ }
+
+ public function tear_down() {
+ remove_filter( 'pre_http_request', array( get_called_class(), 'pre_http_request' ) );
+ remove_filter( 'http_response', array( get_called_class(), 'http_response' ) );
+ parent::tear_down();
+ }
+
+
+ public static function pre_http_request( $preempt, $request, $url ) {
+ $p = wp_parse_url( $url );
+ $cache = __DIR__ . '/fixtures/' . sanitize_title( $p['host'] . '-' . $p['path'] ) . '.json';
+ if ( file_exists( $cache ) ) {
+ return apply_filters(
+ 'fake_http_response',
+ json_decode( file_get_contents( $cache ), true ), // phpcs:ignore
+ $p['scheme'] . '://' . $p['host'],
+ $url,
+ $request
+ );
+ }
+
+ $home_url = home_url();
+
+ // Pretend the url now is the requested one.
+ update_option( 'home', $p['scheme'] . '://' . $p['host'] );
+ $rest_prefix = home_url() . '/wp-json';
+
+ if ( false === strpos( $url, $rest_prefix ) ) {
+ // Restore the old home_url.
+ update_option( 'home', $home_url );
+ return $preempt;
+ }
+
+ $url = substr( $url, strlen( $rest_prefix ) );
+ $r = new \WP_REST_Request( $request['method'], $url );
+ if ( ! empty( $request['body'] ) ) {
+ foreach ( $request['body'] as $key => $value ) {
+ $r->set_param( $key, $value );
+ }
+ }
+ global $wp_rest_server;
+ $response = $wp_rest_server->dispatch( $r );
+ // Restore the old url.
+ update_option( 'home', $home_url );
+
+ return apply_filters(
+ 'fake_http_response',
+ array(
+ 'headers' => array(
+ 'content-type' => 'text/json',
+ ),
+ 'body' => wp_json_encode( $response->data ),
+ 'response' => array(
+ 'code' => $response->status,
+ ),
+ ),
+ $p['scheme'] . '://' . $p['host'],
+ $url,
+ $request
+ );
+ }
+
+ public static function http_response( $response, $args, $url ) {
+ $p = wp_parse_url( $url );
+ $cache = __DIR__ . '/fixtures/' . sanitize_title( $p['host'] . '-' . $p['path'] ) . '.json';
+ if ( ! file_exists( $cache ) ) {
+ $headers = wp_remote_retrieve_headers( $response );
+ file_put_contents( // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_file_put_contents
+ $cache,
+ wp_json_encode(
+ array(
+ 'headers' => $headers->getAll(),
+ 'body' => wp_remote_retrieve_body( $response ),
+ 'response' => array(
+ 'code' => wp_remote_retrieve_response_code( $response ),
+ ),
+ )
+ )
+ );
+ }
+ return $response;
+ }
+}
diff --git a/tests/fixtures/notiz-blog-2022-11-14-the-at-protocol.response b/tests/fixtures/notiz-blog-2022-11-14-the-at-protocol.response
deleted file mode 100644
index d6705a1..0000000
--- a/tests/fixtures/notiz-blog-2022-11-14-the-at-protocol.response
+++ /dev/null
@@ -1 +0,0 @@
-a:3:{s:7:"headers";a:15:{s:4:"date";s:29:"Fri, 02 Dec 2022 12:09:11 GMT";s:12:"content-type";s:25:"application/activity+json";s:6:"server";s:5:"nginx";s:15:"x-xrds-location";s:24:"https://notiz.blog/?xrds";s:16:"x-yadis-location";s:24:"https://notiz.blog/?xrds";s:10:"x-pingback";s:29:"https://notiz.blog/xmlrpc.php";s:4:"link";s:541:"; rel="micropub_media", ; rel="micropub", ; rel="friends-base-url", ; rel="webmention", ; rel="http://webmention.org/", ; rel="https://api.w.org/", ; rel="alternate"; type="application/json", ; rel=shortlink";s:13:"cache-control";s:17:"max-age=0, public";s:7:"expires";s:29:"Fri, 02 Dec 2022 12:09:11 GMT";s:16:"x-xss-protection";s:13:"1; mode=block";s:22:"x-content-type-options";s:7:"nosniff";s:25:"strict-transport-security";s:16:"max-age=31536000";s:15:"x-frame-options";s:10:"SAMEORIGIN";s:15:"referrer-policy";s:31:"strict-origin-when-cross-origin";s:17:"x-clacks-overhead";s:19:"GNU Terry Pratchett";}s:4:"body";s:18048:"{"@context":["https:\/\/www.w3.org\/ns\/activitystreams","https:\/\/w3id.org\/security\/v1",{"manuallyApprovesFollowers":"as:manuallyApprovesFollowers","PropertyValue":"schema:PropertyValue","schema":"http:\/\/schema.org#","pt":"https:\/\/joinpeertube.org\/ns#","toot":"http:\/\/joinmastodon.org\/ns#","value":"schema:value","Hashtag":"as:Hashtag","featured":{"@id":"toot:featured","@type":"@id"},"featuredTags":{"@id":"toot:featuredTags","@type":"@id"}}],"id":"https:\/\/notiz.blog\/2022\/11\/14\/the-at-protocol\/","type":"Note","published":"2022-11-14T16:49:01Z","attributedTo":"https:\/\/notiz.blog\/author\/matthias-pfefferle\/","summary":null,"inReplyTo":null,"content":"\u003Cp\u003EVor zwei Jahren wollte Twitter in das \u201eDezentrale Netzwerke\u201c-Business einsteigen und gr\u00fcndete eigens daf\u00fcr das \u003Ca href=\u0022https:\/\/notiz.blog\/2019\/12\/13\/twitiverse\/\u0022 data-type=\u0022post\u0022 data-id=\u002218831\u0022\u003EProjekt Bluesky\u003C\/a\u003E. In den folgenden zwei Jahren wurde viel evaluiert und diskutiert, was wohl die beste L\u00f6sung f\u00fcr Twitter sei und wir alle \u003Cem\u003Efieberten\u003C\/em\u003E mit ob es nun \u003Ca href=\u0022https:\/\/www.w3.org\/TR\/activitypub\/\u0022 data-type=\u0022URL\u0022 data-id=\u0022https:\/\/www.w3.org\/TR\/activitypub\/\u0022\u003EActivityPub\u003C\/a\u003E oder doch \u003Ca href=\u0022https:\/\/matrix.org\/\u0022 data-type=\u0022URL\u0022 data-id=\u0022https:\/\/matrix.org\/\u0022\u003EMatrix\u003C\/a\u003E werden w\u00fcrde\u2026 \u003C\/p\u003E\u003Cp\u003EAber das Warten hat ein Ende! \u003Ca href=\u0022https:\/\/blueskyweb.xyz\/blog\/10-18-2022-the-at-protocol\u0022 data-type=\u0022URL\u0022 data-id=\u0022https:\/\/blueskyweb.xyz\/blog\/10-18-2022-the-at-protocol\u0022\u003EBluesky hat verk\u00fcndet wie es weiter geht\u003C\/a\u003E!\u003C\/p\u003E\u003Cp\u003E\u003Cstrong\u003ESie entwickeln ein neues Protokoll!\u003C\/strong\u003E\u003C\/p\u003E\u003Cp\u003EDas \u003Cem\u003EAT Protocol\u003C\/em\u003E, kurz f\u00fcr \u003Cem\u003EAuthenticated Transfer Protocol\u003C\/em\u003E!\u003C\/p\u003E\u003Cp\u003EIch hab mir die \u003Ca href=\u0022https:\/\/atproto.com\/guides\/faq\u0022 data-type=\u0022URL\u0022 data-id=\u0022https:\/\/atproto.com\/guides\/faq\u0022\u003EFAQ\u003C\/a\u003E mal angeschaut und dort steht warum Bluesky sich gegen ActivityPub entschieden hat:\u003C\/p\u003E\u003Cblockquote class=\u0022wp-block-quote\u0022\u003E\n\u003Cp\u003EAccount portability is the major reason why we chose to build a separate protocol. We consider portability to be crucial because it protects users from sudden bans, server shutdowns, and policy disagreements. Our solution for portability requires both \u003Ca href=\u0022https:\/\/atproto.com\/guides\/data-repos\u0022\u003Esigned data repositories\u003C\/a\u003E and \u003Ca href=\u0022https:\/\/atproto.com\/guides\/identity\u0022\u003EDIDs\u003C\/a\u003E, neither of which are easy to retrofit into ActivityPub. The migration tools for ActivityPub are comparatively limited; they require the original server to provide a redirect and cannot migrate the user\u2019s previous data.\u003C\/p\u003E\n\u003C\/blockquote\u003E\u003Cp\u003EDas erinnert mich ein bisschen an die Subline von meinem \u003Ca href=\u0022https:\/\/pfefferle.dev\/openwebicons\/\u0022 data-type=\u0022URL\u0022 data-id=\u0022https:\/\/pfefferle.dev\/openwebicons\/\u0022\u003EOpenWeb-Icons Font\u003C\/a\u003E:\u003C\/p\u003E\u003Cblockquote class=\u0022wp-block-quote\u0022\u003E\n\u003Cp\u003EWhy \u003Cem\u003EOpenWeb Icons\u003C\/em\u003E? Because \u003Ca href=\u0022https:\/\/fortawesome.com\/\u0022 data-type=\u0022URL\u0022 data-id=\u0022https:\/\/fortawesome.com\/\u0022\u003EFont Awesome\u003C\/a\u003E had no RSS-icon [\u2026]\u003C\/p\u003E\n\u003C\/blockquote\u003E\u003Cp\u003E\u003Cstrong\u003EWeil ActivityPub keine perfekte L\u00f6sung f\u00fcr \u201eAccount portability\u201c hat, bauen sie ein komplett neues Protokoll?\u003C\/strong\u003E\u003C\/p\u003E\u003Cp\u003EActivityPub ist sicherlich nicht \u201efeature complete\u201c, aber ein guter erster Wurf, was das Fediverse erfolgreich bewiesen hat! Warum arbeitet Twitter also lieber an einem eigen Format anstatt mit dem W3C zusammen an ActivityPub v2?\u003C\/p\u003E\u003Cp\u003EWarum macht sich das W3C \u00fcberhaupt noch die M\u00fche \u201eStandards\u201c zu definieren?\u003C\/p\u003E\u003Cp\u003E\u003Cstrong\u003EWegen der Interoperabilit\u00e4t!\u003C\/strong\u003E\u003C\/p\u003E\u003Cp\u003EW\u00fcrde Twitter mit HTTP(S), HTML oder CSS \u00e4hnlich umgehen, w\u00fcrde der Browser einfach leer bleiben, weil das \u0026$%\u00a7\u0026 Internet nur mit einheitlichen Standards funktioniert!\u003C\/p\u003E\u003Cp\u003EUnd das gleiche gilt auch f\u00fcr dezentralte Netze, zumindest wenn sie erfolgreich sein wollen! Dar\u00fcber hab ich tragischerweise schon vor \u003Cstrong\u003E10 Jahren\u003C\/strong\u003E geschrieben!\u003C\/p\u003E\u003Cblockquote class=\u0022wp-block-quote\u0022\u003E\n\u003Cp\u003EDiaspora* wurde kaum \u003Ca href=\u0022https:\/\/web.archive.org\/web\/20130630113539\/http:\/\/blog.diasporafoundation.org\/2012\/08\/27\/announcement-diaspora-will-now-be-a-community-project.html\u0022\u003Ef\u00fcr \u201etot\u201c erkl\u00e4rt\u003C\/a\u003E und schon steht das n\u00e4chste Projekt in den Startl\u00f6chern! \u003Ca href=\u0022https:\/\/web.archive.org\/web\/20190603031810\/https:\/\/tent.io\/\u0022\u003ETent.io\u003C\/a\u003E soll ein protocol for distributed social networking and personal data storage werden. Alles neu, alles anders, alles besser als OStatus, DiSo oder Diaspora*. Aber mal ganz ehrlich\u2026 was haben die Diasporas \u0026 Co. bisher geschaffen? Ziel war es Facebooks \u201eWalled Gardens\u201c aufzubrechen und was kam wirklich dabei rum? Eine ganze Reihe an dezentralen \u201eWalled Gardens\u201c. Na danke!\u003C\/p\u003E\n\u003Ccite\u003E\u003Ca href=\u0022https:\/\/notiz.blog\/2012\/11\/15\/dezentrale-walled-gardens\/\u0022\u003EDezentrale \u201eWalled Gardens\u201c\u003C\/a\u003E\u003C\/cite\u003E\u003C\/blockquote\u003E\u003Cp\u003EDas \u003Ca href=\u0022https:\/\/the-federation.info\/\u0022 data-type=\u0022URL\u0022 data-id=\u0022https:\/\/the-federation.info\/\u0022\u003Efediverse\u003C\/a\u003E hat (wie schon erw\u00e4hnt) bisher einen gro\u00dfartigen Job gemacht und verschiedenste Netzwerke mit den verschiedensten Auspr\u00e4gungen vernetzt! Ich \u003Cs\u003Eglaube\u003C\/s\u003E bin der festen \u00dcberzeugung, dass sich diesmal wirklich das offene Format (\u003Cem\u003EActivityPub\u003C\/em\u003E) durchsetzen wird und Blueskys \u003Cem\u003EAuthenticated Transfer Protocol\u003C\/em\u003E auch in ein paar Monaten oder Jahren keine Rolle spielen wird! \u003C\/p\u003E\u003Cp\u003E\u003Ca href=\u0022https:\/\/twitter.com\/benwerd\u0022 data-type=\u0022URL\u0022 data-id=\u0022https:\/\/twitter.com\/benwerd\u0022\u003EBen Werdmuller\u003C\/a\u003E hat eine gesunde Einstellung zu dem Thema:\u003C\/p\u003E\u003Cblockquote class=\u0022wp-block-quote\u0022\u003E\n\u003Cp\u003EI\u2019m so burned out by open source social, but I\u2019m glad to see people throw energy at the problem, even if it\u2019s not how I would have gone about it.\u003C\/p\u003E\n\u003Ccite\u003E\u003Ca href=\u0022https:\/\/twitter.com\/benwerd\/status\/1582554417693270016\u0022 data-type=\u0022URL\u0022 data-id=\u0022https:\/\/twitter.com\/benwerd\/status\/1582554417693270016\u0022\u003ETwitter\u003C\/a\u003E\u003C\/cite\u003E\u003C\/blockquote\u003E\u003Cp\u003EMehr hab ich dazu eigentlich nicht zu sagen, au\u00dfer dass wir in der \u003Ca href=\u0022https:\/\/neunetz.fm\/neunetzcast-93-was-wir-unter-dezentralitaet-verstehen-und-was-wir-uns-davon-erhoffen\/\u0022 data-type=\u0022URL\u0022 data-id=\u0022https:\/\/neunetz.fm\/neunetzcast-93-was-wir-unter-dezentralitaet-verstehen-und-was-wir-uns-davon-erhoffen\/\u0022\u003Eaktuellen Folge\u003C\/a\u003E des neunetzcasts sehr ausgiebig \u00fcber genau dieses Problem gesprochen haben!\u003C\/p\u003E\u003Cp\u003E\u003Ca rel=\u0022tag\u0022 class=\u0022u-tag u-category\u0022 href=\u0022https:\/\/notiz.blog\/tag\/activitypub\/\u0022\u003E#activitypub\u003C\/a\u003E \u003Ca rel=\u0022tag\u0022 class=\u0022u-tag u-category\u0022 href=\u0022https:\/\/notiz.blog\/tag\/bluesky\/\u0022\u003E#bluesky\u003C\/a\u003E \u003Ca rel=\u0022tag\u0022 class=\u0022u-tag u-category\u0022 href=\u0022https:\/\/notiz.blog\/tag\/fediverse\/\u0022\u003E#fediverse\u003C\/a\u003E \u003Ca rel=\u0022tag\u0022 class=\u0022u-tag u-category\u0022 href=\u0022https:\/\/notiz.blog\/tag\/matrix\/\u0022\u003E#matrix\u003C\/a\u003E \u003Ca rel=\u0022tag\u0022 class=\u0022u-tag u-category\u0022 href=\u0022https:\/\/notiz.blog\/tag\/twitter\/\u0022\u003E#twitter\u003C\/a\u003E\u003C\/p\u003E\u003Cp\u003E\u003Ca href=\u0022https:\/\/notiz.blog\/2022\/11\/14\/the-at-protocol\/\u0022\u003Ehttps:\/\/notiz.blog\/2022\/11\/14\/the-at-protocol\/\u003C\/a\u003E\u003C\/p\u003E","contentMap":{"de":"\u003Cp\u003EVor zwei Jahren wollte Twitter in das \u201eDezentrale Netzwerke\u201c-Business einsteigen und gr\u00fcndete eigens daf\u00fcr das \u003Ca href=\u0022https:\/\/notiz.blog\/2019\/12\/13\/twitiverse\/\u0022 data-type=\u0022post\u0022 data-id=\u002218831\u0022\u003EProjekt Bluesky\u003C\/a\u003E. In den folgenden zwei Jahren wurde viel evaluiert und diskutiert, was wohl die beste L\u00f6sung f\u00fcr Twitter sei und wir alle \u003Cem\u003Efieberten\u003C\/em\u003E mit ob es nun \u003Ca href=\u0022https:\/\/www.w3.org\/TR\/activitypub\/\u0022 data-type=\u0022URL\u0022 data-id=\u0022https:\/\/www.w3.org\/TR\/activitypub\/\u0022\u003EActivityPub\u003C\/a\u003E oder doch \u003Ca href=\u0022https:\/\/matrix.org\/\u0022 data-type=\u0022URL\u0022 data-id=\u0022https:\/\/matrix.org\/\u0022\u003EMatrix\u003C\/a\u003E werden w\u00fcrde\u2026 \u003C\/p\u003E\u003Cp\u003EAber das Warten hat ein Ende! \u003Ca href=\u0022https:\/\/blueskyweb.xyz\/blog\/10-18-2022-the-at-protocol\u0022 data-type=\u0022URL\u0022 data-id=\u0022https:\/\/blueskyweb.xyz\/blog\/10-18-2022-the-at-protocol\u0022\u003EBluesky hat verk\u00fcndet wie es weiter geht\u003C\/a\u003E!\u003C\/p\u003E\u003Cp\u003E\u003Cstrong\u003ESie entwickeln ein neues Protokoll!\u003C\/strong\u003E\u003C\/p\u003E\u003Cp\u003EDas \u003Cem\u003EAT Protocol\u003C\/em\u003E, kurz f\u00fcr \u003Cem\u003EAuthenticated Transfer Protocol\u003C\/em\u003E!\u003C\/p\u003E\u003Cp\u003EIch hab mir die \u003Ca href=\u0022https:\/\/atproto.com\/guides\/faq\u0022 data-type=\u0022URL\u0022 data-id=\u0022https:\/\/atproto.com\/guides\/faq\u0022\u003EFAQ\u003C\/a\u003E mal angeschaut und dort steht warum Bluesky sich gegen ActivityPub entschieden hat:\u003C\/p\u003E\u003Cblockquote class=\u0022wp-block-quote\u0022\u003E\n\u003Cp\u003EAccount portability is the major reason why we chose to build a separate protocol. We consider portability to be crucial because it protects users from sudden bans, server shutdowns, and policy disagreements. Our solution for portability requires both \u003Ca href=\u0022https:\/\/atproto.com\/guides\/data-repos\u0022\u003Esigned data repositories\u003C\/a\u003E and \u003Ca href=\u0022https:\/\/atproto.com\/guides\/identity\u0022\u003EDIDs\u003C\/a\u003E, neither of which are easy to retrofit into ActivityPub. The migration tools for ActivityPub are comparatively limited; they require the original server to provide a redirect and cannot migrate the user\u2019s previous data.\u003C\/p\u003E\n\u003C\/blockquote\u003E\u003Cp\u003EDas erinnert mich ein bisschen an die Subline von meinem \u003Ca href=\u0022https:\/\/pfefferle.dev\/openwebicons\/\u0022 data-type=\u0022URL\u0022 data-id=\u0022https:\/\/pfefferle.dev\/openwebicons\/\u0022\u003EOpenWeb-Icons Font\u003C\/a\u003E:\u003C\/p\u003E\u003Cblockquote class=\u0022wp-block-quote\u0022\u003E\n\u003Cp\u003EWhy \u003Cem\u003EOpenWeb Icons\u003C\/em\u003E? Because \u003Ca href=\u0022https:\/\/fortawesome.com\/\u0022 data-type=\u0022URL\u0022 data-id=\u0022https:\/\/fortawesome.com\/\u0022\u003EFont Awesome\u003C\/a\u003E had no RSS-icon [\u2026]\u003C\/p\u003E\n\u003C\/blockquote\u003E\u003Cp\u003E\u003Cstrong\u003EWeil ActivityPub keine perfekte L\u00f6sung f\u00fcr \u201eAccount portability\u201c hat, bauen sie ein komplett neues Protokoll?\u003C\/strong\u003E\u003C\/p\u003E\u003Cp\u003EActivityPub ist sicherlich nicht \u201efeature complete\u201c, aber ein guter erster Wurf, was das Fediverse erfolgreich bewiesen hat! Warum arbeitet Twitter also lieber an einem eigen Format anstatt mit dem W3C zusammen an ActivityPub v2?\u003C\/p\u003E\u003Cp\u003EWarum macht sich das W3C \u00fcberhaupt noch die M\u00fche \u201eStandards\u201c zu definieren?\u003C\/p\u003E\u003Cp\u003E\u003Cstrong\u003EWegen der Interoperabilit\u00e4t!\u003C\/strong\u003E\u003C\/p\u003E\u003Cp\u003EW\u00fcrde Twitter mit HTTP(S), HTML oder CSS \u00e4hnlich umgehen, w\u00fcrde der Browser einfach leer bleiben, weil das \u0026$%\u00a7\u0026 Internet nur mit einheitlichen Standards funktioniert!\u003C\/p\u003E\u003Cp\u003EUnd das gleiche gilt auch f\u00fcr dezentralte Netze, zumindest wenn sie erfolgreich sein wollen! Dar\u00fcber hab ich tragischerweise schon vor \u003Cstrong\u003E10 Jahren\u003C\/strong\u003E geschrieben!\u003C\/p\u003E\u003Cblockquote class=\u0022wp-block-quote\u0022\u003E\n\u003Cp\u003EDiaspora* wurde kaum \u003Ca href=\u0022https:\/\/web.archive.org\/web\/20130630113539\/http:\/\/blog.diasporafoundation.org\/2012\/08\/27\/announcement-diaspora-will-now-be-a-community-project.html\u0022\u003Ef\u00fcr \u201etot\u201c erkl\u00e4rt\u003C\/a\u003E und schon steht das n\u00e4chste Projekt in den Startl\u00f6chern! \u003Ca href=\u0022https:\/\/web.archive.org\/web\/20190603031810\/https:\/\/tent.io\/\u0022\u003ETent.io\u003C\/a\u003E soll ein protocol for distributed social networking and personal data storage werden. Alles neu, alles anders, alles besser als OStatus, DiSo oder Diaspora*. Aber mal ganz ehrlich\u2026 was haben die Diasporas \u0026 Co. bisher geschaffen? Ziel war es Facebooks \u201eWalled Gardens\u201c aufzubrechen und was kam wirklich dabei rum? Eine ganze Reihe an dezentralen \u201eWalled Gardens\u201c. Na danke!\u003C\/p\u003E\n\u003Ccite\u003E\u003Ca href=\u0022https:\/\/notiz.blog\/2012\/11\/15\/dezentrale-walled-gardens\/\u0022\u003EDezentrale \u201eWalled Gardens\u201c\u003C\/a\u003E\u003C\/cite\u003E\u003C\/blockquote\u003E\u003Cp\u003EDas \u003Ca href=\u0022https:\/\/the-federation.info\/\u0022 data-type=\u0022URL\u0022 data-id=\u0022https:\/\/the-federation.info\/\u0022\u003Efediverse\u003C\/a\u003E hat (wie schon erw\u00e4hnt) bisher einen gro\u00dfartigen Job gemacht und verschiedenste Netzwerke mit den verschiedensten Auspr\u00e4gungen vernetzt! Ich \u003Cs\u003Eglaube\u003C\/s\u003E bin der festen \u00dcberzeugung, dass sich diesmal wirklich das offene Format (\u003Cem\u003EActivityPub\u003C\/em\u003E) durchsetzen wird und Blueskys \u003Cem\u003EAuthenticated Transfer Protocol\u003C\/em\u003E auch in ein paar Monaten oder Jahren keine Rolle spielen wird! \u003C\/p\u003E\u003Cp\u003E\u003Ca href=\u0022https:\/\/twitter.com\/benwerd\u0022 data-type=\u0022URL\u0022 data-id=\u0022https:\/\/twitter.com\/benwerd\u0022\u003EBen Werdmuller\u003C\/a\u003E hat eine gesunde Einstellung zu dem Thema:\u003C\/p\u003E\u003Cblockquote class=\u0022wp-block-quote\u0022\u003E\n\u003Cp\u003EI\u2019m so burned out by open source social, but I\u2019m glad to see people throw energy at the problem, even if it\u2019s not how I would have gone about it.\u003C\/p\u003E\n\u003Ccite\u003E\u003Ca href=\u0022https:\/\/twitter.com\/benwerd\/status\/1582554417693270016\u0022 data-type=\u0022URL\u0022 data-id=\u0022https:\/\/twitter.com\/benwerd\/status\/1582554417693270016\u0022\u003ETwitter\u003C\/a\u003E\u003C\/cite\u003E\u003C\/blockquote\u003E\u003Cp\u003EMehr hab ich dazu eigentlich nicht zu sagen, au\u00dfer dass wir in der \u003Ca href=\u0022https:\/\/neunetz.fm\/neunetzcast-93-was-wir-unter-dezentralitaet-verstehen-und-was-wir-uns-davon-erhoffen\/\u0022 data-type=\u0022URL\u0022 data-id=\u0022https:\/\/neunetz.fm\/neunetzcast-93-was-wir-unter-dezentralitaet-verstehen-und-was-wir-uns-davon-erhoffen\/\u0022\u003Eaktuellen Folge\u003C\/a\u003E des neunetzcasts sehr ausgiebig \u00fcber genau dieses Problem gesprochen haben!\u003C\/p\u003E\u003Cp\u003E\u003Ca rel=\u0022tag\u0022 class=\u0022u-tag u-category\u0022 href=\u0022https:\/\/notiz.blog\/tag\/activitypub\/\u0022\u003E#activitypub\u003C\/a\u003E \u003Ca rel=\u0022tag\u0022 class=\u0022u-tag u-category\u0022 href=\u0022https:\/\/notiz.blog\/tag\/bluesky\/\u0022\u003E#bluesky\u003C\/a\u003E \u003Ca rel=\u0022tag\u0022 class=\u0022u-tag u-category\u0022 href=\u0022https:\/\/notiz.blog\/tag\/fediverse\/\u0022\u003E#fediverse\u003C\/a\u003E \u003Ca rel=\u0022tag\u0022 class=\u0022u-tag u-category\u0022 href=\u0022https:\/\/notiz.blog\/tag\/matrix\/\u0022\u003E#matrix\u003C\/a\u003E \u003Ca rel=\u0022tag\u0022 class=\u0022u-tag u-category\u0022 href=\u0022https:\/\/notiz.blog\/tag\/twitter\/\u0022\u003E#twitter\u003C\/a\u003E\u003C\/p\u003E\u003Cp\u003E\u003Ca href=\u0022https:\/\/notiz.blog\/2022\/11\/14\/the-at-protocol\/\u0022\u003Ehttps:\/\/notiz.blog\/2022\/11\/14\/the-at-protocol\/\u003C\/a\u003E\u003C\/p\u003E"},"to":["https:\/\/www.w3.org\/ns\/activitystreams#Public"],"cc":["https:\/\/www.w3.org\/ns\/activitystreams#Public"],"attachment":[{"type":"Image","url":"https:\/\/notiz.blog\/wp-content\/uploads\/2022\/11\/the-at-protocol.png","mediaType":"image\/png","name":"The Logo of the AT Protocol"}],"tag":[{"type":"Hashtag","href":"https:\/\/notiz.blog\/tag\/activitypub\/","name":"#activitypub"},{"type":"Hashtag","href":"https:\/\/notiz.blog\/tag\/bluesky\/","name":"#bluesky"},{"type":"Hashtag","href":"https:\/\/notiz.blog\/tag\/fediverse\/","name":"#fediverse"},{"type":"Hashtag","href":"https:\/\/notiz.blog\/tag\/matrix\/","name":"#matrix"},{"type":"Hashtag","href":"https:\/\/notiz.blog\/tag\/twitter\/","name":"#twitter"}]}";s:8:"response";a:1:{s:4:"code";i:200;}}
\ No newline at end of file
diff --git a/tests/fixtures/notiz-blog-author-matthias-pfefferle.json b/tests/fixtures/notiz-blog-author-matthias-pfefferle.json
new file mode 100644
index 0000000..d93d7fa
--- /dev/null
+++ b/tests/fixtures/notiz-blog-author-matthias-pfefferle.json
@@ -0,0 +1,22 @@
+{
+ "headers": {
+ "date": "Fri, 09 Dec 2022 10:39:51 GMT",
+ "content-type": "application\/activity+json",
+ "server": "nginx",
+ "x-xrds-location": "https:\/\/notiz.blog\/?xrds",
+ "x-yadis-location": "https:\/\/notiz.blog\/?xrds",
+ "link": "; rel=\"micropub_media\", ; rel=\"micropub\", ; rel=\"friends-base-url\", ; rel=\"authorization_endpoint\", ; rel=\"token_endpoint\", ; rel=\"indieauth-metadata\", ; rel=\"https:\/\/api.w.org\/\", ; rel=\"alternate\"; type=\"application\/json\"",
+ "cache-control": "max-age=0, public",
+ "expires": "Fri, 09 Dec 2022 10:39:51 GMT",
+ "x-xss-protection": "1; mode=block",
+ "x-content-type-options": "nosniff",
+ "strict-transport-security": "max-age=31536000",
+ "x-frame-options": "SAMEORIGIN",
+ "referrer-policy": "strict-origin-when-cross-origin",
+ "x-clacks-overhead": "GNU Terry Pratchett"
+ },
+ "body": "{\"@context\":[\"https:\\\/\\\/www.w3.org\\\/ns\\\/activitystreams\",\"https:\\\/\\\/w3id.org\\\/security\\\/v1\",{\"manuallyApprovesFollowers\":\"as:manuallyApprovesFollowers\",\"PropertyValue\":\"schema:PropertyValue\",\"schema\":\"http:\\\/\\\/schema.org#\",\"pt\":\"https:\\\/\\\/joinpeertube.org\\\/ns#\",\"toot\":\"http:\\\/\\\/joinmastodon.org\\\/ns#\",\"value\":\"schema:value\",\"Hashtag\":\"as:Hashtag\",\"featured\":{\"@id\":\"toot:featured\",\"@type\":\"@id\"},\"featuredTags\":{\"@id\":\"toot:featuredTags\",\"@type\":\"@id\"}}],\"id\":\"https:\\\/\\\/notiz.blog\\\/author\\\/matthias-pfefferle\\\/\",\"type\":\"Person\",\"name\":\"Matthias Pfefferle\",\"summary\":\"Ich bin Webworker und arbeite als \\u0022Head of WordPress Development\\u0022 f\\u00fcr IONOS in Karlsruhe. Ich blogge, podcaste und schreibe \\u003Cdel\\u003Eeine Kolumne\\u003C\\\/del\\u003E \\u00fcber das open, independent und federated social Web. \\u003Ca href=\\u0022https:\\\/\\\/notiz.blog\\\/about\\\/\\u0022\\u003EMehr \\u00fcber mich.\\u003C\\\/a\\u003E\",\"preferredUsername\":\"pfefferle\",\"url\":\"https:\\\/\\\/notiz.blog\\\/author\\\/matthias-pfefferle\\\/\",\"icon\":{\"type\":\"Image\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/75512bb584bbceae57dfc503692b16b2?s=120\\u0026d=mm\\u0026r=g\"},\"image\":{\"type\":\"Image\",\"url\":\"https:\\\/\\\/notiz.blog\\\/wp-content\\\/uploads\\\/2017\\\/02\\\/cropped-Unknown-2.jpeg\"},\"inbox\":\"https:\\\/\\\/notiz.blog\\\/wp-api\\\/activitypub\\\/1.0\\\/users\\\/1\\\/inbox\",\"outbox\":\"https:\\\/\\\/notiz.blog\\\/wp-api\\\/activitypub\\\/1.0\\\/users\\\/1\\\/outbox\",\"followers\":\"https:\\\/\\\/notiz.blog\\\/wp-api\\\/activitypub\\\/1.0\\\/users\\\/1\\\/followers\",\"following\":\"https:\\\/\\\/notiz.blog\\\/wp-api\\\/activitypub\\\/1.0\\\/users\\\/1\\\/following\",\"manuallyApprovesFollowers\":false,\"publicKey\":{\"id\":\"https:\\\/\\\/notiz.blog\\\/author\\\/matthias-pfefferle\\\/#main-key\",\"owner\":\"https:\\\/\\\/notiz.blog\\\/author\\\/matthias-pfefferle\\\/\",\"publicKeyPem\":\"-----BEGIN PUBLIC KEY-----\\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA039CnlArzn6nsRjcC2RJ\\nrjY3K5ZrLnFUbPtHLGNXMJUGW+rFYE1DzhdKPTj9giiXE+J7ADI0Tme5rSWw14bT\\nLhOMBs2ma8d03\\\/wnF1+kxDBeRyvyoki2TjtiJdoPu1jwZLLYTuzWTXdDiqrwSKOL\\nncKFGIkjyzOLoYuIKPgIuFg3Mt8rI6teQ2Q65YsGvOG\\\/mjBOUwl5FjgcGt9aQARd\\nmFxW5XydxfNrCZwuE34Zbq\\\/IC7rvaUx98zvrEHrD237YQ8O4M3afC9Kbu5Xp7k8Q\\n5JG80RItV7n8xjyt0i9LaVwlZDDYmLDYv50VhjcwRvtVFVfaN7yxDnHttd1NNENK\\nCwIDAQAB\\n-----END PUBLIC KEY-----\"},\"tag\":[],\"attachment\":[{\"type\":\"PropertyValue\",\"name\":\"Blog\",\"value\":\"\\u003Ca rel=\\u0022me\\u0022 title=\\u0022https:\\\/\\\/notiz.blog\\\/\\u0022 target=\\u0022_blank\\u0022 href=\\u0022https:\\\/\\\/notiz.blog\\\/\\u0022\\u003Enotiz.blog\\u003C\\\/a\\u003E\"},{\"type\":\"PropertyValue\",\"name\":\"Profil\",\"value\":\"\\u003Ca rel=\\u0022me\\u0022 title=\\u0022https:\\\/\\\/notiz.blog\\\/author\\\/matthias-pfefferle\\\/\\u0022 target=\\u0022_blank\\u0022 href=\\u0022https:\\\/\\\/notiz.blog\\\/author\\\/matthias-pfefferle\\\/\\u0022\\u003Enotiz.blog\\u003C\\\/a\\u003E\"},{\"type\":\"PropertyValue\",\"name\":\"Website\",\"value\":\"\\u003Ca rel=\\u0022me\\u0022 title=\\u0022https:\\\/\\\/pfefferle.org\\\/\\u0022 target=\\u0022_blank\\u0022 href=\\u0022https:\\\/\\\/pfefferle.org\\\/\\u0022\\u003Epfefferle.org\\u003C\\\/a\\u003E\"}]}",
+ "response": {
+ "code": 200
+ }
+}
diff --git a/tests/fixtures/notiz-blog-well-known-webfinger.json b/tests/fixtures/notiz-blog-well-known-webfinger.json
new file mode 100644
index 0000000..3578ef1
--- /dev/null
+++ b/tests/fixtures/notiz-blog-well-known-webfinger.json
@@ -0,0 +1,22 @@
+{
+ "headers": {
+ "date": "Fri, 09 Dec 2022 10:39:51 GMT",
+ "content-type": "application\/jrd+json; charset=UTF-8",
+ "server": "nginx",
+ "x-xrds-location": "https:\/\/notiz.blog\/?xrds",
+ "x-yadis-location": "https:\/\/notiz.blog\/?xrds",
+ "access-control-allow-origin": "*",
+ "cache-control": "max-age=2592000, public",
+ "expires": "Sun, 08 Jan 2023 10:39:50 GMT",
+ "x-xss-protection": "1; mode=block",
+ "x-content-type-options": "nosniff",
+ "strict-transport-security": "max-age=31536000",
+ "x-frame-options": "SAMEORIGIN",
+ "referrer-policy": "strict-origin-when-cross-origin",
+ "x-clacks-overhead": "GNU Terry Pratchett"
+ },
+ "body": "{\"subject\":\"acct:pfefferle@notiz.blog\",\"aliases\":[\"acct:pfefferle@notiz.blog\",\"https:\\\/\\\/notiz.blog\\\/author\\\/matthias-pfefferle\\\/\",\"mailto:pfefferle@notiz.blog\"],\"links\":[{\"rel\":\"http:\\\/\\\/webfinger.net\\\/rel\\\/profile-page\",\"href\":\"https:\\\/\\\/notiz.blog\\\/author\\\/matthias-pfefferle\\\/\",\"type\":\"text\\\/html\"},{\"rel\":\"http:\\\/\\\/webfinger.net\\\/rel\\\/avatar\",\"href\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/75512bb584bbceae57dfc503692b16b2?s=96&d=mm&r=g\"},{\"rel\":\"http:\\\/\\\/webfinger.net\\\/rel\\\/profile-page\",\"href\":\"https:\\\/\\\/pfefferle.org\\\/\",\"type\":\"text\\\/html\"},{\"rel\":\"payment\",\"href\":\"https:\\\/\\\/www.paypal.me\\\/matthiaspfefferle\"},{\"rel\":\"payment\",\"href\":\"https:\\\/\\\/liberapay.com\\\/pfefferle\\\/\"},{\"rel\":\"payment\",\"href\":\"https:\\\/\\\/notiz.blog\\\/donate\\\/\"},{\"rel\":\"payment\",\"href\":\"https:\\\/\\\/flattr.com\\\/@pfefferle\"},{\"href\":\"https:\\\/\\\/notiz.blog\\\/\",\"rel\":\"http:\\\/\\\/specs.openid.net\\\/auth\\\/2.0\\\/provider\"},{\"rel\":\"self\",\"type\":\"application\\\/activity+json\",\"href\":\"https:\\\/\\\/notiz.blog\\\/author\\\/matthias-pfefferle\\\/\"},{\"rel\":\"micropub_media\",\"href\":\"https:\\\/\\\/notiz.blog\\\/wp-api\\\/micropub\\\/1.0\\\/media\"},{\"rel\":\"micropub\",\"href\":\"https:\\\/\\\/notiz.blog\\\/wp-api\\\/micropub\\\/1.0\\\/endpoint\"},{\"rel\":\"http:\\\/\\\/nodeinfo.diaspora.software\\\/ns\\\/schema\\\/2.0\",\"href\":\"https:\\\/\\\/notiz.blog\\\/wp-api\\\/nodeinfo\\\/2.0\"},{\"rel\":\"http:\\\/\\\/nodeinfo.diaspora.software\\\/ns\\\/schema\\\/1.1\",\"href\":\"https:\\\/\\\/notiz.blog\\\/wp-api\\\/nodeinfo\\\/1.1\"},{\"rel\":\"http:\\\/\\\/nodeinfo.diaspora.software\\\/ns\\\/schema\\\/1.0\",\"href\":\"https:\\\/\\\/notiz.blog\\\/wp-api\\\/nodeinfo\\\/1.0\"},{\"rel\":\"https:\\\/\\\/feneas.org\\\/ns\\\/serviceinfo\",\"type\":\"application\\\/ld+json\",\"href\":\"https:\\\/\\\/notiz.blog\\\/wp-api\\\/serviceinfo\\\/1.0\",\"properties\":{\"https:\\\/\\\/feneas.org\\\/ns\\\/serviceinfo#software.name\":\"notizBlog\"}},{\"rel\":\"http:\\\/\\\/schemas.google.com\\\/g\\\/2010#updates-from\",\"href\":\"https:\\\/\\\/notiz.blog\\\/author\\\/matthias-pfefferle\\\/feed\\\/ostatus\\\/\",\"type\":\"application\\\/atom+xml\"},{\"rel\":\"http:\\\/\\\/ostatus.org\\\/schema\\\/1.0\\\/subscribe\",\"template\":\"https:\\\/\\\/notiz.blog\\\/?profile={uri}\"},{\"rel\":\"magic-public-key\",\"href\":\"data:application\\\/magic-public-key,RSA.039CnlArzn6nsRjcC2RJrjY3K5ZrLnFUbPtHLGNXMJUGW-rFYE1DzhdKPTj9giiXE-J7ADI0Tme5rSWw14bTLhOMBs2ma8d03_wnF1-kxDBeRyvyoki2TjtiJdoPu1jwZLLYTuzWTXdDiqrwSKOLncKFGIkjyzOLoYuIKPgIuFg3Mt8rI6teQ2Q65YsGvOG_mjBOUwl5FjgcGt9aQARdmFxW5XydxfNrCZwuE34Zbq_IC7rvaUx98zvrEHrD237YQ8O4M3afC9Kbu5Xp7k8Q5JG80RItV7n8xjyt0i9LaVwlZDDYmLDYv50VhjcwRvtVFVfaN7yxDnHttd1NNENKCw==.AQAB\"},{\"rel\":\"salmon\",\"href\":\"https:\\\/\\\/notiz.blog\\\/author\\\/matthias-pfefferle\\\/?salmon=endpoint\"},{\"rel\":\"http:\\\/\\\/salmon-protocol.org\\\/ns\\\/salmon-replies\",\"href\":\"https:\\\/\\\/notiz.blog\\\/author\\\/matthias-pfefferle\\\/?salmon=endpoint\"},{\"rel\":\"http:\\\/\\\/salmon-protocol.org\\\/ns\\\/salmon-mention\",\"href\":\"https:\\\/\\\/notiz.blog\\\/author\\\/matthias-pfefferle\\\/?salmon=endpoint\"},{\"rel\":\"feed\",\"type\":\"application\\\/stream+json\",\"title\":\"Activity-Streams 1.0 Feed\",\"href\":\"https:\\\/\\\/notiz.blog\\\/author\\\/matthias-pfefferle\\\/feed\\\/as1\\\/\"},{\"rel\":\"feed\",\"type\":\"application\\\/activity+json\",\"title\":\"Activity-Streams 2.0 Feed\",\"href\":\"https:\\\/\\\/notiz.blog\\\/author\\\/matthias-pfefferle\\\/feed\\\/as2\\\/\"},{\"rel\":\"http:\\\/\\\/oexchange.org\\\/spec\\\/0.8\\\/rel\\\/user-target\",\"href\":\"https:\\\/\\\/notiz.blog\\\/?oexchange=xrd\",\"type\":\"application\\\/xrd+xml\"},{\"rel\":\"http:\\\/\\\/a9.com\\\/-\\\/spec\\\/opensearch\\\/1.1\\\/\",\"href\":\"https:\\\/\\\/notiz.blog\\\/wp-api\\\/opensearch\\\/1.1\\\/document\",\"type\":\"application\\\/opensearchdescription+xml\"},{\"rel\":\"describedby\",\"href\":\"https:\\\/\\\/notiz.blog\\\/author\\\/matthias-pfefferle\\\/feed\\\/foaf\\\/\",\"type\":\"application\\\/rdf+xml\"},{\"rel\":\"webmention\",\"href\":\"https:\\\/\\\/notiz.blog\\\/wp-api\\\/webmention\\\/1.0\\\/endpoint\"},{\"rel\":\"http:\\\/\\\/webmention.org\\\/\",\"href\":\"https:\\\/\\\/notiz.blog\\\/wp-api\\\/webmention\\\/1.0\\\/endpoint\"}],\"properties\":{\"http:\\\/\\\/salmon-protocol.org\\\/ns\\\/magic-key\":\"RSA.039CnlArzn6nsRjcC2RJrjY3K5ZrLnFUbPtHLGNXMJUGW-rFYE1DzhdKPTj9giiXE-J7ADI0Tme5rSWw14bTLhOMBs2ma8d03_wnF1-kxDBeRyvyoki2TjtiJdoPu1jwZLLYTuzWTXdDiqrwSKOLncKFGIkjyzOLoYuIKPgIuFg3Mt8rI6teQ2Q65YsGvOG_mjBOUwl5FjgcGt9aQARdmFxW5XydxfNrCZwuE34Zbq_IC7rvaUx98zvrEHrD237YQ8O4M3afC9Kbu5Xp7k8Q5JG80RItV7n8xjyt0i9LaVwlZDDYmLDYv50VhjcwRvtVFVfaN7yxDnHttd1NNENKCw==.AQAB\"}}",
+ "response": {
+ "code": 200
+ }
+}
diff --git a/tests/test-class-activitypub-activity-dispatcher.php b/tests/test-class-activitypub-activity-dispatcher.php
new file mode 100644
index 0000000..6693ba1
--- /dev/null
+++ b/tests/test-class-activitypub-activity-dispatcher.php
@@ -0,0 +1,135 @@
+ array(
+ 'url' => 'https://example.org/users/username',
+ 'inbox' => 'https://example.org/users/username/inbox',
+ 'name' => 'username',
+ ),
+ 'jon@example.com' => array(
+ 'url' => 'https://example.com/author/jon',
+ 'inbox' => 'https://example.com/author/jon/inbox',
+ 'name' => 'jon',
+ ),
+ );
+
+ public function test_dispatch_activity() {
+ $followers = array( 'https://example.com/author/jon', 'https://example.org/users/username' );
+ \update_user_meta( 1, 'activitypub_followers', $followers );
+
+ $post = \wp_insert_post(
+ array(
+ 'post_author' => 1,
+ 'post_content' => 'hello',
+ )
+ );
+
+ $pre_http_request = new MockAction();
+ add_filter( 'pre_http_request', array( $pre_http_request, 'filter' ), 10, 3 );
+
+ $activitypub_post = new \Activitypub\Model\Post( $post );
+ \Activitypub\Activity_Dispatcher::send_post_activity( $activitypub_post );
+
+ $this->assertSame( 2, $pre_http_request->get_call_count() );
+ $all_args = $pre_http_request->get_args();
+ $first_call_args = array_shift( $all_args );
+ $this->assertEquals( 'https://example.com/author/jon/inbox', $first_call_args[2] );
+
+ $second_call_args = array_shift( $all_args );
+ $this->assertEquals( 'https://example.org/users/username/inbox', $second_call_args[2] );
+
+ remove_filter( 'pre_http_request', array( $pre_http_request, 'filter' ), 10 );
+ }
+ public function test_dispatch_mentions() {
+ $post = \wp_insert_post(
+ array(
+ 'post_author' => 1,
+ 'post_content' => '@alex hello',
+ )
+ );
+
+ self::$users['https://example.com/alex'] = array(
+ 'url' => 'https://example.com/alex',
+ 'inbox' => 'https://example.com/alex/inbox',
+ 'name' => 'alex',
+ );
+
+ add_filter(
+ 'activitypub_extract_mentions',
+ function( $mentions ) {
+ $mentions[] = 'https://example.com/alex';
+ return $mentions;
+ },
+ 10
+ );
+
+ $pre_http_request = new MockAction();
+ add_filter( 'pre_http_request', array( $pre_http_request, 'filter' ), 10, 3 );
+
+ $activitypub_post = new \Activitypub\Model\Post( $post );
+ \Activitypub\Activity_Dispatcher::send_post_activity( $activitypub_post );
+
+ $this->assertSame( 1, $pre_http_request->get_call_count() );
+ $all_args = $pre_http_request->get_args();
+ $first_call_args = $all_args[0];
+ $this->assertEquals( 'https://example.com/alex/inbox', $first_call_args[2] );
+
+ $body = json_decode( $first_call_args[1]['body'], true );
+ $this->assertArrayHasKey( 'id', $body );
+
+ remove_all_filters( 'activitypub_from_post_object' );
+ remove_filter( 'pre_http_request', array( $pre_http_request, 'filter' ), 10 );
+ }
+
+ public function set_up() {
+ parent::set_up();
+ add_filter( 'pre_get_remote_metadata_by_actor', array( get_called_class(), 'pre_get_remote_metadata_by_actor' ), 10, 2 );
+ _delete_all_posts();
+ }
+
+ public function tear_down() {
+ remove_filter( 'pre_get_remote_metadata_by_actor', array( get_called_class(), 'pre_get_remote_metadata_by_actor' ) );
+ parent::tear_down();
+ }
+
+ public static function pre_get_remote_metadata_by_actor( $pre, $actor ) {
+ if ( isset( self::$users[ $actor ] ) ) {
+ return self::$users[ $actor ];
+ }
+ foreach ( self::$users as $username => $data ) {
+ if ( $data['url'] === $actor ) {
+ return $data;
+ }
+ }
+ return $pre;
+ }
+
+ public static function http_request_host_is_external( $in, $host ) {
+ if ( in_array( $host, array( 'example.com', 'example.org' ), true ) ) {
+ return true;
+ }
+ return $in;
+ }
+ public static function http_request_args( $args, $url ) {
+ if ( in_array( wp_parse_url( $url, PHP_URL_HOST ), array( 'example.com', 'example.org' ), true ) ) {
+ $args['reject_unsafe_urls'] = false;
+ }
+ return $args;
+ }
+
+ public static function pre_http_request( $preempt, $request, $url ) {
+ return array(
+ 'headers' => array(
+ 'content-type' => 'text/json',
+ ),
+ 'body' => '',
+ 'response' => array(
+ 'code' => 202,
+ ),
+ );
+ }
+
+ public static function http_response( $response, $args, $url ) {
+ return $response;
+ }
+}
diff --git a/tests/test-class-activitypub-activity.php b/tests/test-class-activitypub-activity.php
new file mode 100644
index 0000000..254d860
--- /dev/null
+++ b/tests/test-class-activitypub-activity.php
@@ -0,0 +1,31 @@
+ 1,
+ 'post_content' => '@alex hello',
+ )
+ );
+
+ add_filter(
+ 'activitypub_extract_mentions',
+ function( $mentions ) {
+ $mentions['@alex'] = 'https://example.com/alex';
+ return $mentions;
+ },
+ 10
+ );
+
+ $activitypub_post = new \Activitypub\Model\Post( $post );
+
+ $activitypub_activity = new \Activitypub\Model\Activity( 'Create', \Activitypub\Model\Activity::TYPE_FULL );
+ $activitypub_activity->from_post( $activitypub_post );
+
+ $this->assertContains( \get_rest_url( null, '/activitypub/1.0/users/1/followers' ), $activitypub_activity->get_cc() );
+ $this->assertContains( 'https://example.com/alex', $activitypub_activity->get_cc() );
+
+ remove_all_filters( 'activitypub_extract_mentions' );
+ \wp_trash_post( $post );
+ }
+}
diff --git a/tests/test-class-activitypub-hashtag.php b/tests/test-class-activitypub-hashtag.php
index e7699d2..5c207bd 100644
--- a/tests/test-class-activitypub-hashtag.php
+++ b/tests/test-class-activitypub-hashtag.php
@@ -4,26 +4,26 @@ class Test_Activitypub_Hashtag extends WP_UnitTestCase {
* @dataProvider the_content_provider
*/
public function test_the_content( $content, $content_with_hashtag ) {
- $content = \Activitypub\Hashtag::the_content( $content );
-
- $this->assertEquals( $content_with_hashtag, $content );
- }
-
- public function the_content_provider() {
\wp_create_term( 'object', 'post_tag' );
$object = \get_term_by( 'name', 'object', 'post_tag' );
$link = \get_term_link( $object, 'post_tag' );
+ $content = \Activitypub\Hashtag::the_content( $content );
+
+ $this->assertEquals( sprintf( $content_with_hashtag, $link ), $content );
+ }
+
+ public function the_content_provider() {
return array(
array( 'test', 'test' ),
array( '#test', '#test' ),
array( 'hallo #test test', 'hallo #test test' ),
- array( 'hallo #object test', 'hallo #object test' ),
- array( '#object test', '#object test' ),
+ array( 'hallo #object test', 'hallo #object test' ),
+ array( '#object test', '#object test' ),
array( 'hallo test test', 'hallo test test' ),
array( 'hallo #test test', 'hallo #test test' ),
- array( '