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.

179 lines
4.8 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 'Note':
  28. return self::parseAsHEntry($as2, $url, $http, $opts);
  29. }
  30. $result = [
  31. 'data' => [
  32. 'type' => 'unknown',
  33. ],
  34. 'url' => $url,
  35. ];
  36. return $result;
  37. }
  38. private static function parseAsHEntry($as2, $url, $http, $opts) {
  39. $data = [
  40. 'type' => 'entry'
  41. ];
  42. $refs = [];
  43. if(isset($as2['url']))
  44. $data['url'] = $as2['url'];
  45. elseif(isset($as2['id']))
  46. $data['url'] = $as2['id'];
  47. if(isset($as2['published'])) {
  48. try {
  49. $date = new DateTime($as2['published']);
  50. $data['published'] = $date->format('c');
  51. } catch(\Exception $e){}
  52. }
  53. if(isset($as2['content'])) {
  54. $html = trim(self::sanitizeHTML($as2['content']));
  55. $text = trim(self::stripHTML($html));
  56. $data['content'] = [
  57. 'text' => $text
  58. ];
  59. if($html && $text && $text != $html) {
  60. $data['content']['html'] = $html;
  61. }
  62. }
  63. if(isset($as2['tag']) && is_array($as2['tag'])) {
  64. $emoji = [];
  65. $category = [];
  66. foreach($as2['tag'] as $tag) {
  67. if(is_array($tag) && isset($tag['name']) && isset($tag['type']) && $tag['type'] == 'Hashtag')
  68. $category[] = trim($tag['name'], '#');
  69. if(is_array($tag) && isset($tag['type']) && $tag['type'] == 'Emoji' && isset($tag['icon']['url'])) {
  70. $emoji[$tag['name']] = $tag['icon']['url'];
  71. }
  72. }
  73. if(count($category))
  74. $data['category'] = $category;
  75. if(count($emoji) && isset($data['content']['html'])) {
  76. foreach($emoji as $code=>$img) {
  77. $data['content']['html'] = str_replace($code, '<img src="'.$img.'" alt="'.$code.'" title="'.$code.'" height="24" class="xray-custom-emoji">', $data['content']['html']);
  78. }
  79. }
  80. }
  81. if(isset($as2['inReplyTo'])) {
  82. $data['in-reply-to'] = [$as2['inReplyTo']];
  83. }
  84. // Photos and Videos
  85. if(isset($as2['attachment'])) {
  86. $photos = [];
  87. $videos = [];
  88. foreach($as2['attachment'] as $attachment) {
  89. if(strpos($attachment['mediaType'], 'image/') !== false) {
  90. $photos[] = $attachment['url'];
  91. }
  92. if(strpos($attachment['mediaType'], 'video/') !== false) {
  93. $videos[] = $attachment['url'];
  94. }
  95. }
  96. if(count($photos))
  97. $data['photo'] = $photos;
  98. if(count($videos))
  99. $data['video'] = $videos;
  100. }
  101. // Fetch the author info, which requires an HTTP request
  102. if(isset($as2['attributedTo']) && is_string($as2['attributedTo'])) {
  103. $authorResponse = $http->get($as2['attributedTo'], ['Accept: application/activity+json,application/json']);
  104. if($authorResponse && !empty($authorResponse['body'])) {
  105. $authorProfile = json_decode($authorResponse['body'], true);
  106. $author = self::parseAsHCard($authorProfile, $as2['attributedTo'], $http, $opts);
  107. if($author && !empty($author['data']))
  108. $data['author'] = $author['data'];
  109. }
  110. }
  111. $data['post-type'] = PostType::discover($data);
  112. $response = [
  113. 'data' => $data,
  114. ];
  115. if(count($refs)) {
  116. $response['data']['refs'] = $refs;
  117. }
  118. return $response;
  119. }
  120. private static function parseAsHCard($as2, $url, $http, $opts) {
  121. $data = [
  122. 'type' => 'card',
  123. 'name' => null,
  124. 'url' => null,
  125. 'photo' => null
  126. ];
  127. if(!empty($as2['name']))
  128. $data['name'] = $as2['name'];
  129. elseif(isset($as2['preferredUsername']))
  130. $data['name'] = $as2['preferredUsername'];
  131. if(isset($as2['preferredUsername']))
  132. $data['nickname'] = $as2['preferredUsername'];
  133. if(isset($as2['url']))
  134. $data['url'] = $as2['url'];
  135. if(isset($as2['icon']) && isset($as2['icon']['url']))
  136. $data['photo'] = $as2['icon']['url'];
  137. // TODO: featured image for h-cards?
  138. // if(isset($as2['image']) && isset($as2['image']['url']))
  139. // $data['featured'] = $as2['image']['url'];
  140. $response = [
  141. 'data' => $data
  142. ];
  143. return $response;
  144. }
  145. }