Friendica Communications Platform (please note that this is a clone of the repository at github, issues are handled there) https://friendi.ca
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.

204 lines
5.7KB

  1. <?php
  2. /**
  3. * @file src/Model/APContact.php
  4. */
  5. namespace Friendica\Model;
  6. use Friendica\BaseObject;
  7. use Friendica\Content\Text\HTML;
  8. use Friendica\Core\Logger;
  9. use Friendica\Database\DBA;
  10. use Friendica\Protocol\ActivityPub;
  11. use Friendica\Util\Network;
  12. use Friendica\Util\JsonLD;
  13. use Friendica\Util\DateTimeFormat;
  14. use Friendica\Util\Strings;
  15. class APContact extends BaseObject
  16. {
  17. /**
  18. * Resolves the profile url from the address by using webfinger
  19. *
  20. * @param string $addr profile address (user@domain.tld)
  21. * @return string url
  22. */
  23. private static function addrToUrl($addr)
  24. {
  25. $addr_parts = explode('@', $addr);
  26. if (count($addr_parts) != 2) {
  27. return false;
  28. }
  29. $webfinger = 'https://' . $addr_parts[1] . '/.well-known/webfinger?resource=acct:' . urlencode($addr);
  30. $curlResult = Network::curl($webfinger, false, $redirects, ['accept_content' => 'application/jrd+json,application/json']);
  31. if (!$curlResult->isSuccess() || empty($curlResult->getBody())) {
  32. return false;
  33. }
  34. $data = json_decode($curlResult->getBody(), true);
  35. if (empty($data['links'])) {
  36. return false;
  37. }
  38. foreach ($data['links'] as $link) {
  39. if (empty($link['href']) || empty($link['rel']) || empty($link['type'])) {
  40. continue;
  41. }
  42. if (($link['rel'] == 'self') && ($link['type'] == 'application/activity+json')) {
  43. return $link['href'];
  44. }
  45. }
  46. return false;
  47. }
  48. /**
  49. * Fetches a profile from a given url
  50. *
  51. * @param string $url profile url
  52. * @param boolean $update true = always update, false = never update, null = update when not found
  53. * @return array profile array
  54. */
  55. public static function getByURL($url, $update = null)
  56. {
  57. if (empty($url)) {
  58. return false;
  59. }
  60. if (empty($update)) {
  61. $apcontact = DBA::selectFirst('apcontact', [], ['url' => $url]);
  62. if (DBA::isResult($apcontact)) {
  63. return $apcontact;
  64. }
  65. $apcontact = DBA::selectFirst('apcontact', [], ['alias' => $url]);
  66. if (DBA::isResult($apcontact)) {
  67. return $apcontact;
  68. }
  69. $apcontact = DBA::selectFirst('apcontact', [], ['addr' => $url]);
  70. if (DBA::isResult($apcontact)) {
  71. return $apcontact;
  72. }
  73. if (!is_null($update)) {
  74. return false;
  75. }
  76. }
  77. if (empty(parse_url($url, PHP_URL_SCHEME))) {
  78. $url = self::addrToUrl($url);
  79. if (empty($url)) {
  80. return false;
  81. }
  82. }
  83. $data = ActivityPub::fetchContent($url);
  84. if (empty($data)) {
  85. return false;
  86. }
  87. $compacted = JsonLD::compact($data);
  88. if (empty($compacted['@id'])) {
  89. return false;
  90. }
  91. $apcontact = [];
  92. $apcontact['url'] = $compacted['@id'];
  93. $apcontact['uuid'] = JsonLD::fetchElement($compacted, 'diaspora:guid');
  94. $apcontact['type'] = str_replace('as:', '', JsonLD::fetchElement($compacted, '@type'));
  95. $apcontact['following'] = JsonLD::fetchElement($compacted, 'as:following', '@id');
  96. $apcontact['followers'] = JsonLD::fetchElement($compacted, 'as:followers', '@id');
  97. $apcontact['inbox'] = JsonLD::fetchElement($compacted, 'ldp:inbox', '@id');
  98. $apcontact['outbox'] = JsonLD::fetchElement($compacted, 'as:outbox', '@id');
  99. $apcontact['sharedinbox'] = '';
  100. if (!empty($compacted['as:endpoints'])) {
  101. $apcontact['sharedinbox'] = JsonLD::fetchElement($compacted['as:endpoints'], 'as:sharedInbox', '@id');
  102. }
  103. $apcontact['nick'] = JsonLD::fetchElement($compacted, 'as:preferredUsername');
  104. $apcontact['name'] = JsonLD::fetchElement($compacted, 'as:name');
  105. if (empty($apcontact['name'])) {
  106. $apcontact['name'] = $apcontact['nick'];
  107. }
  108. $apcontact['about'] = HTML::toBBCode(JsonLD::fetchElement($compacted, 'as:summary'));
  109. $apcontact['photo'] = JsonLD::fetchElement($compacted, 'as:icon', '@id');
  110. if (is_array($apcontact['photo'])) {
  111. $apcontact['photo'] = JsonLD::fetchElement($compacted['as:icon'], 'as:url', '@id');
  112. }
  113. $apcontact['alias'] = JsonLD::fetchElement($compacted, 'as:url', '@id');
  114. if (is_array($apcontact['alias'])) {
  115. $apcontact['alias'] = JsonLD::fetchElement($compacted['as:url'], 'as:href', '@id');
  116. }
  117. if (empty($apcontact['url']) || empty($apcontact['inbox'])) {
  118. return false;
  119. }
  120. $parts = parse_url($apcontact['url']);
  121. unset($parts['scheme']);
  122. unset($parts['path']);
  123. $apcontact['addr'] = $apcontact['nick'] . '@' . str_replace('//', '', Network::unparseURL($parts));
  124. $apcontact['pubkey'] = trim(JsonLD::fetchElement($compacted, 'w3id:publicKey', 'w3id:publicKeyPem'));
  125. // To-Do
  126. // manuallyApprovesFollowers
  127. // Unhandled
  128. // @context, tag, attachment, image, nomadicLocations, signature, following, followers, featured, movedTo, liked
  129. // Unhandled from Misskey
  130. // sharedInbox, isCat
  131. // Unhandled from Kroeg
  132. // kroeg:blocks, updated
  133. // Check if the address is resolvable
  134. if (self::addrToUrl($apcontact['addr']) == $apcontact['url']) {
  135. $parts = parse_url($apcontact['url']);
  136. unset($parts['path']);
  137. $apcontact['baseurl'] = Network::unparseURL($parts);
  138. } else {
  139. $apcontact['addr'] = null;
  140. $apcontact['baseurl'] = null;
  141. }
  142. if ($apcontact['url'] == $apcontact['alias']) {
  143. $apcontact['alias'] = null;
  144. }
  145. $apcontact['updated'] = DateTimeFormat::utcNow();
  146. DBA::update('apcontact', $apcontact, ['url' => $url], true);
  147. // Update some data in the contact table with various ways to catch them all
  148. $contact_fields = ['name' => $apcontact['name'], 'about' => $apcontact['about']];
  149. DBA::update('contact', $contact_fields, ['nurl' => Strings::normaliseLink($url)]);
  150. $contacts = DBA::select('contact', ['uid', 'id'], ['nurl' => Strings::normaliseLink($url)]);
  151. while ($contact = DBA::fetch($contacts)) {
  152. Contact::updateAvatar($apcontact['photo'], $contact['uid'], $contact['id']);
  153. }
  154. DBA::close($contacts);
  155. // Update the gcontact table
  156. DBA::update('gcontact', $contact_fields, ['nurl' => Strings::normaliseLink($url)]);
  157. Logger::log('Updated profile for ' . $url, Logger::DEBUG);
  158. return $apcontact;
  159. }
  160. }