You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

188 lines
4.9 KiB

  1. <?php
  2. namespace p3k\XRay\Formats;
  3. use DateTime;
  4. use \p3k\XRay\PostType;
  5. class ActivityStreams extends Format {
  6. public static function is_as2_json($document) {
  7. if(is_array($document) && isset($document['@context'])) {
  8. if(is_string($document['@context']) && $document['@context'] == 'https://www.w3.org/ns/activitystreams')
  9. return true;
  10. if(is_array($document['@context']) && in_array('https://www.w3.org/ns/activitystreams', $document['@context']))
  11. return true;
  12. }
  13. return false;
  14. }
  15. public static function matches_host($url) {
  16. return true;
  17. }
  18. public static function matches($url) {
  19. return true;
  20. }
  21. public static function parse($as2, $url, $http, $opts=[]) {
  22. if(!isset($as2['type']))
  23. return false;
  24. switch($as2['type']) {
  25. case 'Person':
  26. return self::parseAsHCard($as2, $url, $http, $opts);
  27. case 'Article':
  28. case 'Note':
  29. return self::parseAsHEntry($as2, $url, $http, $opts);
  30. }
  31. $result = [
  32. 'data' => [
  33. 'type' => 'unknown',
  34. ],
  35. 'url' => $url,
  36. ];
  37. return $result;
  38. }
  39. private static function parseAsHEntry($as2, $url, $http, $opts) {
  40. $data = [
  41. 'type' => 'entry'
  42. ];
  43. $refs = [];
  44. if(isset($as2['url']))
  45. $data['url'] = $as2['url'];
  46. elseif(isset($as2['id']))
  47. $data['url'] = $as2['id'];
  48. if(isset($as2['published'])) {
  49. try {
  50. $date = new DateTime($as2['published']);
  51. $data['published'] = $date->format('c');
  52. } catch(\Exception $e){}
  53. }
  54. if(isset($as2['name'])) {
  55. $data['name'] = $as2['name'];
  56. }
  57. if(isset($as2['summary'])) {
  58. $data['summary'] = $as2['summary'];
  59. }
  60. if(isset($as2['content'])) {
  61. $html = trim(self::sanitizeHTML($as2['content']));
  62. $text = trim(self::stripHTML($html));
  63. $data['content'] = [
  64. 'text' => $text
  65. ];
  66. if($html && $text && $text != $html) {
  67. $data['content']['html'] = $html;
  68. }
  69. }
  70. if(isset($as2['tag']) && is_array($as2['tag'])) {
  71. $emoji = [];
  72. $category = [];
  73. foreach($as2['tag'] as $tag) {
  74. if(is_array($tag) && isset($tag['name']) && isset($tag['type']) && $tag['type'] == 'Hashtag')
  75. $category[] = trim($tag['name'], '#');
  76. if(is_array($tag) && isset($tag['type']) && $tag['type'] == 'Emoji' && isset($tag['icon']['url'])) {
  77. $emoji[$tag['name']] = $tag['icon']['url'];
  78. }
  79. }
  80. if(count($category))
  81. $data['category'] = $category;
  82. if(count($emoji) && isset($data['content']['html'])) {
  83. foreach($emoji as $code=>$img) {
  84. $data['content']['html'] = str_replace($code, '<img src="'.$img.'" alt="'.$code.'" title="'.$code.'" height="24" class="xray-custom-emoji">', $data['content']['html']);
  85. }
  86. }
  87. }
  88. if(isset($as2['inReplyTo'])) {
  89. $data['in-reply-to'] = [$as2['inReplyTo']];
  90. }
  91. // Photos and Videos
  92. if(isset($as2['attachment'])) {
  93. $photos = [];
  94. $videos = [];
  95. foreach($as2['attachment'] as $attachment) {
  96. if(strpos($attachment['mediaType'], 'image/') !== false) {
  97. $photos[] = $attachment['url'];
  98. }
  99. if(strpos($attachment['mediaType'], 'video/') !== false) {
  100. $videos[] = $attachment['url'];
  101. }
  102. }
  103. if(count($photos))
  104. $data['photo'] = $photos;
  105. if(count($videos))
  106. $data['video'] = $videos;
  107. }
  108. // Fetch the author info, which requires an HTTP request
  109. if(isset($as2['attributedTo']) && is_string($as2['attributedTo'])) {
  110. $authorResponse = $http->get($as2['attributedTo'], ['Accept: application/activity+json,application/json']);
  111. if($authorResponse && !empty($authorResponse['body'])) {
  112. $authorProfile = json_decode($authorResponse['body'], true);
  113. $author = self::parseAsHCard($authorProfile, $as2['attributedTo'], $http, $opts);
  114. if($author && !empty($author['data']))
  115. $data['author'] = $author['data'];
  116. }
  117. }
  118. $data['post-type'] = PostType::discover($data);
  119. $response = [
  120. 'data' => $data,
  121. ];
  122. if(count($refs)) {
  123. $response['data']['refs'] = $refs;
  124. }
  125. return $response;
  126. }
  127. private static function parseAsHCard($as2, $url, $http, $opts) {
  128. $data = [
  129. 'type' => 'card',
  130. 'name' => null,
  131. 'url' => null,
  132. 'photo' => null
  133. ];
  134. if(!empty($as2['name']))
  135. $data['name'] = $as2['name'];
  136. elseif(isset($as2['preferredUsername']))
  137. $data['name'] = $as2['preferredUsername'];
  138. if(isset($as2['preferredUsername']))
  139. $data['nickname'] = $as2['preferredUsername'];
  140. if(isset($as2['url']))
  141. $data['url'] = $as2['url'];
  142. if(isset($as2['icon']) && isset($as2['icon']['url']))
  143. $data['photo'] = $as2['icon']['url'];
  144. // TODO: featured image for h-cards?
  145. // if(isset($as2['image']) && isset($as2['image']['url']))
  146. // $data['featured'] = $as2['image']['url'];
  147. $response = [
  148. 'data' => $data
  149. ];
  150. return $response;
  151. }
  152. }