activitypubqueuehandler.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. <?php
  2. // This file is part of GNU social - https://www.gnu.org/software/social
  3. //
  4. // GNU social is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU Affero General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // GNU social is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU Affero General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU Affero General Public License
  15. // along with GNU social. If not, see <http://www.gnu.org/licenses/>.
  16. /**
  17. * ActivityPub queue handler for notice distribution
  18. *
  19. * @package GNUsocial
  20. * @author Bruno Casteleiro <brunoccast@fc.up.pt>
  21. * @copyright 2019 Free Software Foundation, Inc http://www.fsf.org
  22. * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
  23. */
  24. defined('GNUSOCIAL') || die();
  25. /**
  26. * @copyright 2019 Free Software Foundation, Inc http://www.fsf.org
  27. * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
  28. */
  29. class ActivityPubQueueHandler extends QueueHandler
  30. {
  31. /**
  32. * Getter of the queue transport name.
  33. *
  34. * @return string transport name
  35. */
  36. public function transport(): string
  37. {
  38. return 'activitypub';
  39. }
  40. /**
  41. * Notice distribution handler.
  42. *
  43. * @param Notice $notice notice to be distributed.
  44. * @return bool true on success, false otherwise
  45. * @throws HTTP_Request2_Exception
  46. * @throws InvalidUrlException
  47. * @throws ServerException
  48. * @author Diogo Cordeiro <diogo@fc.up.pt>
  49. */
  50. public function handle($notice): bool
  51. {
  52. if (!($notice instanceof Notice)) {
  53. common_log(LOG_ERR, "Got a bogus notice, not distributing");
  54. return true;
  55. }
  56. $profile = $notice->getProfile();
  57. if (!$profile->isLocal()) {
  58. return true;
  59. }
  60. if ($notice->source == 'activity') {
  61. common_log(LOG_ERR, "Ignoring distribution of notice:{$notice->id}: activity source");
  62. return true;
  63. }
  64. $other = Activitypub_profile::from_profile_collection(
  65. $notice->getAttentionProfiles()
  66. );
  67. // Handling a Create?
  68. if (ActivityUtils::compareVerbs($notice->verb, [ActivityVerb::POST])) {
  69. return $this->handle_create($profile, $notice, $other);
  70. }
  71. // Handling a Like?
  72. if (ActivityUtils::compareVerbs($notice->verb, [ActivityVerb::FAVORITE])) {
  73. return $this->onEndFavorNotice($profile, $notice, $other);
  74. }
  75. // Handling a Undo Like?
  76. if (ActivityUtils::compareVerbs($notice->verb, [ActivityVerb::UNFAVORITE])) {
  77. return $this->onEndDisfavorNotice($profile, $notice, $other);
  78. }
  79. // Handling a Delete Note?
  80. if (ActivityUtils::compareVerbs($notice->verb, [ActivityVerb::DELETE])) {
  81. return $this->onStartDeleteOwnNotice($profile, $notice, $other);
  82. }
  83. return true;
  84. }
  85. private function handle_create($profile, $notice, $other)
  86. {
  87. // Handling a reply?
  88. if ($notice->reply_to) {
  89. try {
  90. $parent_notice = $notice->getParent();
  91. try {
  92. $other[] = Activitypub_profile::from_profile($parent_notice->getProfile());
  93. } catch (Exception $e) {
  94. // Local user can be ignored
  95. }
  96. foreach ($parent_notice->getAttentionProfiles() as $mention) {
  97. try {
  98. $other[] = Activitypub_profile::from_profile($mention);
  99. } catch (Exception $e) {
  100. // Local user can be ignored
  101. }
  102. }
  103. } catch (NoParentNoticeException $e) {
  104. // This is not a reply to something (has no parent)
  105. } catch (NoResultException $e) {
  106. // Parent author's profile not found! Complain louder?
  107. common_log(
  108. LOG_ERR,
  109. "Parent notice's author not found: " . $e->getMessage()
  110. );
  111. }
  112. }
  113. // Handling an Announce?
  114. if ($notice->isRepeat()) {
  115. $repeated_notice = Notice::getKV('id', $notice->repeat_of);
  116. if ($repeated_notice instanceof Notice) {
  117. $other = array_merge(
  118. $other,
  119. Activitypub_profile::from_profile_collection(
  120. $repeated_notice->getAttentionProfiles()
  121. )
  122. );
  123. try {
  124. $other[] = Activitypub_profile::from_profile(
  125. $repeated_notice->getProfile()
  126. );
  127. } catch (Exception $e) {
  128. // Local user can be ignored
  129. }
  130. // That was it
  131. $postman = new Activitypub_postman($profile, $other);
  132. $postman->announce($repeated_notice);
  133. }
  134. // either made the announce or found nothing to repeat
  135. return true;
  136. }
  137. // That was it
  138. $postman = new Activitypub_postman($profile, $other);
  139. $postman->create_note($notice);
  140. return true;
  141. }
  142. /**
  143. * Notify remote users when their notices get favourited.
  144. *
  145. * @param Profile $profile of local user doing the faving
  146. * @param Notice $notice Notice being favored
  147. * @return bool return value
  148. * @throws HTTP_Request2_Exception
  149. * @throws InvalidUrlException
  150. * @author Diogo Cordeiro <diogo@fc.up.pt>
  151. */
  152. public function onEndFavorNotice(Profile $profile, Notice $notice, $other)
  153. {
  154. if ($notice->reply_to) {
  155. try {
  156. $parent_notice = $notice->getParent();
  157. try {
  158. $other[] = Activitypub_profile::from_profile($parent_notice->getProfile());
  159. } catch (Exception $e) {
  160. // Local user can be ignored
  161. }
  162. $other = array_merge(
  163. $other,
  164. Activitypub_profile::from_profile_collection(
  165. $parent_notice->getAttentionProfiles()
  166. )
  167. );
  168. } catch (NoParentNoticeException $e) {
  169. // This is not a reply to something (has no parent)
  170. } catch (NoResultException $e) {
  171. // Parent author's profile not found! Complain louder?
  172. common_log(LOG_ERR, "Parent notice's author not found: ".$e->getMessage());
  173. }
  174. }
  175. $postman = new Activitypub_postman($profile, $other);
  176. $postman->like($notice);
  177. return true;
  178. }
  179. /**
  180. * Notify remote users when their notices get de-favourited.
  181. *
  182. * @param Profile $profile of local user doing the de-faving
  183. * @param Notice $notice Notice being favored
  184. * @return bool return value
  185. * @throws HTTP_Request2_Exception
  186. * @throws InvalidUrlException
  187. * @author Diogo Cordeiro <diogo@fc.up.pt>
  188. */
  189. public function onEndDisfavorNotice(Profile $profile, Notice $notice, $other)
  190. {
  191. if ($notice->reply_to) {
  192. try {
  193. $parent_notice = $notice->getParent();
  194. try {
  195. $other[] = Activitypub_profile::from_profile($parent_notice->getProfile());
  196. } catch (Exception $e) {
  197. // Local user can be ignored
  198. }
  199. $other = array_merge(
  200. $other,
  201. Activitypub_profile::from_profile_collection(
  202. $parent_notice->getAttentionProfiles()
  203. )
  204. );
  205. } catch (NoParentNoticeException $e) {
  206. // This is not a reply to something (has no parent)
  207. } catch (NoResultException $e) {
  208. // Parent author's profile not found! Complain louder?
  209. common_log(LOG_ERR, "Parent notice's author not found: ".$e->getMessage());
  210. }
  211. }
  212. $postman = new Activitypub_postman($profile, $other);
  213. $postman->undo_like($notice);
  214. return true;
  215. }
  216. /**
  217. * Notify remote users when their notices get deleted
  218. *
  219. * @param $user
  220. * @param $notice
  221. * @return boolean hook flag
  222. * @throws HTTP_Request2_Exception
  223. * @throws InvalidUrlException
  224. * @author Diogo Cordeiro <diogo@fc.up.pt>
  225. */
  226. public function onStartDeleteOwnNotice($profile, $notice, $other)
  227. {
  228. // Handle delete locally either because:
  229. // 1. There's no undo-share logic yet
  230. // 2. The deleting user has privileges to do so (locally)
  231. if ($notice->isRepeat() || ($notice->getProfile()->getID() != $profile->getID())) {
  232. return true;
  233. }
  234. $other = Activitypub_profile::from_profile_collection(
  235. $notice->getAttentionProfiles()
  236. );
  237. if ($notice->reply_to) {
  238. try {
  239. $parent_notice = $notice->getParent();
  240. try {
  241. $other[] = Activitypub_profile::from_profile($parent_notice->getProfile());
  242. } catch (Exception $e) {
  243. // Local user can be ignored
  244. }
  245. $other = array_merge(
  246. $other,
  247. Activitypub_profile::from_profile_collection(
  248. $parent_notice->getAttentionProfiles()
  249. )
  250. );
  251. } catch (NoParentNoticeException $e) {
  252. // This is not a reply to something (has no parent)
  253. } catch (NoResultException $e) {
  254. // Parent author's profile not found! Complain louder?
  255. common_log(LOG_ERR, "Parent notice's author not found: ".$e->getMessage());
  256. }
  257. }
  258. $postman = new Activitypub_postman($profile, $other);
  259. $postman->delete_note($notice);
  260. return true;
  261. }
  262. }