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.

163 lines
5.2 KiB

  1. <?php
  2. /**
  3. * @copyright Copyright (C) 2020, Friendica
  4. *
  5. * @license GNU AGPL version 3 or any later version
  6. *
  7. * This program is free software: you can redistribute it and/or modify
  8. * it under the terms of the GNU Affero General Public License as
  9. * published by the Free Software Foundation, either version 3 of the
  10. * License, or (at your option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU Affero General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU Affero General Public License
  18. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  19. *
  20. */
  21. namespace Friendica\Model;
  22. use Friendica\Core\Logger;
  23. use Friendica\Database\DBA;
  24. use Friendica\DI;
  25. use Friendica\Protocol\ActivityPub;
  26. use Friendica\Util\DateTimeFormat;
  27. use Friendica\Util\Strings;
  28. class ContactRelation
  29. {
  30. /**
  31. * No discovery of followers/followings
  32. */
  33. const DISCOVERY_NONE = 0;
  34. /**
  35. * Discover followers/followings of local contacts
  36. */
  37. const DISCOVERY_LOCAL = 1;
  38. /**
  39. * Discover followers/followings of local contacts and contacts that visibly interacted on the system
  40. */
  41. const DISCOVERY_INTERACTOR = 2;
  42. /**
  43. * Discover followers/followings of all contacts
  44. */
  45. const DISCOVERY_ALL = 3;
  46. public static function store(int $target, int $actor, string $interaction_date)
  47. {
  48. if ($actor == $target) {
  49. return;
  50. }
  51. DBA::update('contact-relation', ['last-interaction' => $interaction_date], ['cid' => $target, 'relation-cid' => $actor], true);
  52. }
  53. /**
  54. * Fetches the followers of a given profile and adds them
  55. *
  56. * @param string $url URL of a profile
  57. * @return void
  58. */
  59. public static function discoverByUrl(string $url)
  60. {
  61. $contact_discovery = DI::config()->get('system', 'contact_discovery');
  62. if ($contact_discovery == self::DISCOVERY_NONE) {
  63. return;
  64. }
  65. $contact = Contact::getByURL($url);
  66. if (empty($contact)) {
  67. return;
  68. }
  69. if ($contact['last-discovery'] > DateTimeFormat::utc('now - 1 month')) {
  70. Logger::info('No discovery - Last was less than a month ago.', ['id' => $contact['id'], 'url' => $url, 'discovery' => $contact['last-discovery']]);
  71. return;
  72. }
  73. if ($contact_discovery != self::DISCOVERY_ALL) {
  74. $local = DBA::exists('contact', ["`nurl` = ? AND `uid` != ?", Strings::normaliseLink($url), 0]);
  75. if (($contact_discovery == self::DISCOVERY_LOCAL) && !$local) {
  76. Logger::info('No discovery - This contact is not followed/following locally.', ['id' => $contact['id'], 'url' => $url]);
  77. return;
  78. }
  79. if ($contact_discovery == self::DISCOVERY_INTERACTOR) {
  80. $interactor = DBA::exists('contact-relation', ["`relation-cid` = ? AND `last-interaction` > ?", $contact['id'], DBA::NULL_DATETIME]);
  81. if (!$local && !$interactor) {
  82. Logger::info('No discovery - This contact is not interacting locally.', ['id' => $contact['id'], 'url' => $url]);
  83. return;
  84. }
  85. }
  86. } elseif ($contact['created'] > DateTimeFormat::utc('now - 1 day')) {
  87. Logger::info('Newly created contacs are not discovered to avoid DDoS attacks.', ['id' => $contact['id'], 'url' => $url, 'discovery' => $contact['created']]);
  88. return;
  89. }
  90. $apcontact = APContact::getByURL($url);
  91. if (!empty($apcontact['followers']) && is_string($apcontact['followers'])) {
  92. $followers = ActivityPub::fetchItems($apcontact['followers']);
  93. } else {
  94. $followers = [];
  95. }
  96. if (!empty($apcontact['following']) && is_string($apcontact['following'])) {
  97. $followings = ActivityPub::fetchItems($apcontact['following']);
  98. } else {
  99. $followings = [];
  100. }
  101. if (empty($followers) && empty($followings)) {
  102. return;
  103. }
  104. $target = $contact['id'];
  105. if (!empty($followers)) {
  106. // Clear the follower list, since it will be recreated in the next step
  107. DBA::update('contact-relation', ['follows' => false], ['cid' => $target]);
  108. }
  109. $contacts = [];
  110. foreach (array_merge($followers, $followings) as $contact) {
  111. if (is_string($contact)) {
  112. $contacts[] = $contact;
  113. } elseif (!empty($contact['url']) && is_string($contact['url'])) {
  114. $contacts[] = $contact['url'];
  115. }
  116. }
  117. $contacts = array_unique($contacts);
  118. Logger::info('Discover contacts', ['id' => $target, 'url' => $url, 'contacts' => count($contacts)]);
  119. foreach ($contacts as $contact) {
  120. $actor = Contact::getIdForURL($contact);
  121. if (!empty($actor)) {
  122. $fields = [];
  123. if (in_array($contact, $followers)) {
  124. $fields = ['cid' => $target, 'relation-cid' => $actor];
  125. } elseif (in_array($contact, $followings)) {
  126. $fields = ['cid' => $actor, 'relation-cid' => $target];
  127. } else {
  128. continue;
  129. }
  130. DBA::update('contact-relation', ['follows' => true, 'follow-updated' => DateTimeFormat::utcNow()], $fields, true);
  131. }
  132. }
  133. if (!empty($followers)) {
  134. // Delete all followers that aren't followers anymore (and aren't interacting)
  135. DBA::delete('contact-relation', ['cid' => $target, 'follows' => false, 'last-interaction' => DBA::NULL_DATETIME]);
  136. }
  137. DBA::update('contact', ['last-discovery' => DateTimeFormat::utcNow()], ['id' => $target]);
  138. Logger::info('Contacts discovery finished, "last-discovery" set', ['id' => $target, 'url' => $url]);
  139. return;
  140. }
  141. }