ActivityPubPlugin.php 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. <?php
  2. require_once __DIR__ . DIRECTORY_SEPARATOR . "utils" . DIRECTORY_SEPARATOR . "postman.php";
  3. /**
  4. * GNU social - a federating social network
  5. *
  6. * ActivityPubPlugin implementation for GNU Social
  7. *
  8. * LICENCE: This program is free software: you can redistribute it and/or modify
  9. * it under the terms of the GNU Affero General Public License as published by
  10. * the Free Software Foundation, either version 3 of the License, or
  11. * (at your option) any later version.
  12. *
  13. * This program is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU Affero General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU Affero General Public License
  19. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  20. *
  21. * @category Plugin
  22. * @package GNUsocial
  23. * @author Daniel Supernault <danielsupernault@gmail.com>
  24. * @author Diogo Cordeiro <diogo@fc.up.pt>
  25. * @copyright 2018 Free Software Foundation http://fsf.org
  26. * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
  27. * @link https://www.gnu.org/software/social/
  28. */
  29. if (!defined ('GNUSOCIAL')) {
  30. exit (1);
  31. }
  32. /**
  33. * @category Plugin
  34. * @package GNUsocial
  35. * @author Daniel Supernault <danielsupernault@gmail.com>
  36. * @author Diogo Cordeiro <diogo@fc.up.pt>
  37. * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
  38. * @link http://www.gnu.org/software/social/
  39. */
  40. class ActivityPubPlugin extends Plugin
  41. {
  42. /**
  43. * Route/Reroute urls
  44. *
  45. * @param URLMapper $m
  46. * @return void
  47. */
  48. public function onRouterInitialized (URLMapper $m)
  49. {
  50. ActivityPubURLMapperOverwrite::overwrite_variable ($m, ':nickname',
  51. ['action' => 'showstream'],
  52. ['nickname' => Nickname::DISPLAY_FMT],
  53. 'apActorProfile');
  54. $m->connect (':nickname/liked.json',
  55. ['action' => 'apActorLikedCollection'],
  56. ['nickname' => Nickname::DISPLAY_FMT]);
  57. $m->connect (':nickname/followers.json',
  58. ['action' => 'apActorFollowers'],
  59. ['nickname' => Nickname::DISPLAY_FMT]);
  60. $m->connect (':nickname/following.json',
  61. ['action' => 'apActorFollowing'],
  62. ['nickname' => Nickname::DISPLAY_FMT]);
  63. $m->connect (':nickname/inbox.json',
  64. ['action' => 'apActorInbox'],
  65. ['nickname' => Nickname::DISPLAY_FMT]);
  66. $m->connect ('inbox.json',
  67. array ('action' => 'apSharedInbox'));
  68. $m->connect ('api/statuses/public_timeline.as2',
  69. array ('action' => 'apFeed'));
  70. }
  71. /**
  72. * Plugin version information
  73. *
  74. * @param array $versions
  75. * @return boolean true
  76. */
  77. public function onPluginVersion (array &$versions)
  78. {
  79. $versions[] = [ 'name' => 'ActivityPub',
  80. 'version' => GNUSOCIAL_VERSION,
  81. 'author' => 'Daniel Supernault, Diogo Cordeiro',
  82. 'homepage' => 'https://www.gnu.org/software/social/',
  83. 'rawdescription' =>
  84. // Todo: Translation
  85. 'Adds ActivityPub Support'];
  86. return true;
  87. }
  88. /**
  89. * Make sure necessary tables are filled out.
  90. */
  91. function onCheckSchema ()
  92. {
  93. $schema = Schema::get ();
  94. $schema->ensureTable ('Activitypub_profile', Activitypub_profile::schemaDef());
  95. return true;
  96. }
  97. /********************************************************
  98. * Delivery Events *
  99. ********************************************************/
  100. /**
  101. * Having established a remote subscription, send a notification to the
  102. * remote ActivityPub profile's endpoint.
  103. *
  104. * @param Profile $profile subscriber
  105. * @param Profile $other subscribee
  106. * @return hook return value
  107. * @throws Exception
  108. */
  109. function onEndSubscribe (Profile $profile, Profile $other)
  110. {
  111. if (!$profile->isLocal () || $other->isLocal ()) {
  112. return true;
  113. }
  114. try {
  115. $other = Activitypub_profile::fromProfile ($other);
  116. } catch (Exception $e) {
  117. return true;
  118. }
  119. $postman = new Activitypub_postman ($profile, array ($other));
  120. $postman->follow ();
  121. return true;
  122. }
  123. /**
  124. * Notify remote server on unsubscribe.
  125. *
  126. * @param Profile $profile
  127. * @param Profile $other
  128. * @return hook return value
  129. */
  130. function onEndUnsubscribe (Profile $profile, Profile $other)
  131. {
  132. if (!$profile->isLocal () || $other->isLocal ()) {
  133. return true;
  134. }
  135. try {
  136. $other = Activitypub_profile::fromProfile ($other);
  137. } catch (Exception $e) {
  138. return true;
  139. }
  140. $postman = new Activitypub_postman ($profile, array ($other));
  141. $postman->undo_follow ();
  142. return true;
  143. }
  144. function onEndShowExportData (Action $action)
  145. {
  146. $action->elementStart ('div', array ('id' => 'export_data',
  147. 'class' => 'section'));
  148. $action->elementStart ('ul', array ('class' => 'xoxo'));
  149. $action->elementStart ('li');
  150. $action->element ('a', array ('href' => common_root_url ()."api/statuses/public_timeline.as2",
  151. 'class' => "json",
  152. 'type' => "application/activity+json",
  153. 'title' => "Public Timeline Feed (Activity Streams 2 JSON)"),
  154. "Activity Streams 2");
  155. $action->elementEnd ('li');
  156. $action->elementEnd ('ul');
  157. $action->elementEnd ('div');
  158. }
  159. }
  160. /**
  161. * Overwrites variables in URL-mapping
  162. */
  163. class ActivityPubURLMapperOverwrite extends URLMapper
  164. {
  165. static function overwrite_variable ($m, $path, $args, $paramPatterns, $newaction) {
  166. $mimes = [
  167. 'application/activity+json',
  168. 'application/ld+json',
  169. 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
  170. ];
  171. if (in_array ($_SERVER["HTTP_ACCEPT"], $mimes) == false) {
  172. return true;
  173. }
  174. $m->connect ($path, array('action' => $newaction), $paramPatterns);
  175. $regex = self::makeRegex($path, $paramPatterns);
  176. foreach ($m->variables as $n => $v) {
  177. if ($v[1] == $regex) {
  178. $m->variables[$n][0]['action'] = $newaction;
  179. }
  180. }
  181. }
  182. }
  183. /**
  184. * Plugin return handler
  185. */
  186. class ActivityPubReturn
  187. {
  188. /**
  189. * Return a valid answer
  190. *
  191. * @param array $res
  192. * @return void
  193. */
  194. static function answer ($res)
  195. {
  196. header ('Content-Type: application/activity+json');
  197. echo json_encode ($res, JSON_UNESCAPED_SLASHES | (isset ($_GET["pretty"]) ? JSON_PRETTY_PRINT : null));
  198. exit;
  199. }
  200. /**
  201. * Return an error
  202. *
  203. * @param string $m
  204. * @param int32 $code
  205. * @return void
  206. */
  207. static function error ($m, $code = 500)
  208. {
  209. http_response_code ($code);
  210. header ('Content-Type: application/activity+json');
  211. $res[] = Activitypub_error::error_message_to_array ($m);
  212. echo json_encode ($res, JSON_UNESCAPED_SLASHES);
  213. exit;
  214. }
  215. }