[Contact::FOLLOWER, Contact::FRIEND], 'network' => Protocol::NATIVE_SUPPORT, 'uid' => $owner['uid'], 'self' => false, 'hidden' => false, 'archive' => false, 'pending' => false]; $count = DBA::count('contact', $condition); $data = ['@context' => ActivityPub::CONTEXT]; $data['id'] = System::baseUrl() . '/followers/' . $owner['nickname']; $data['type'] = 'OrderedCollection'; $data['totalItems'] = $count; // When we hide our friends we will only show the pure number but don't allow more. $profile = Profile::getByUID($owner['uid']); if (!empty($profile['hide-friends'])) { return $data; } if (empty($page)) { $data['first'] = System::baseUrl() . '/followers/' . $owner['nickname'] . '?page=1'; } else { $list = []; $contacts = DBA::select('contact', ['url'], $condition, ['limit' => [($page - 1) * 100, 100]]); while ($contact = DBA::fetch($contacts)) { $list[] = $contact['url']; } if (!empty($list)) { $data['next'] = System::baseUrl() . '/followers/' . $owner['nickname'] . '?page=' . ($page + 1); } $data['partOf'] = System::baseUrl() . '/followers/' . $owner['nickname']; $data['orderedItems'] = $list; } return $data; } /** * Create list of following contacts * * @param array $owner Owner array * @param integer $page Page numbe * * @return array of following contacts */ public static function getFollowing($owner, $page = null) { $condition = ['rel' => [Contact::SHARING, Contact::FRIEND], 'network' => Protocol::NATIVE_SUPPORT, 'uid' => $owner['uid'], 'self' => false, 'hidden' => false, 'archive' => false, 'pending' => false]; $count = DBA::count('contact', $condition); $data = ['@context' => ActivityPub::CONTEXT]; $data['id'] = System::baseUrl() . '/following/' . $owner['nickname']; $data['type'] = 'OrderedCollection'; $data['totalItems'] = $count; // When we hide our friends we will only show the pure number but don't allow more. $profile = Profile::getByUID($owner['uid']); if (!empty($profile['hide-friends'])) { return $data; } if (empty($page)) { $data['first'] = System::baseUrl() . '/following/' . $owner['nickname'] . '?page=1'; } else { $list = []; $contacts = DBA::select('contact', ['url'], $condition, ['limit' => [($page - 1) * 100, 100]]); while ($contact = DBA::fetch($contacts)) { $list[] = $contact['url']; } if (!empty($list)) { $data['next'] = System::baseUrl() . '/following/' . $owner['nickname'] . '?page=' . ($page + 1); } $data['partOf'] = System::baseUrl() . '/following/' . $owner['nickname']; $data['orderedItems'] = $list; } return $data; } /** * Public posts for the given owner * * @param array $owner Owner array * @param integer $page Page numbe * * @return array of posts */ public static function getOutbox($owner, $page = null) { $public_contact = Contact::getIdForURL($owner['url'], 0, true); $condition = ['uid' => 0, 'contact-id' => $public_contact, 'author-id' => $public_contact, 'private' => false, 'gravity' => [GRAVITY_PARENT, GRAVITY_COMMENT], 'deleted' => false, 'visible' => true]; $count = DBA::count('item', $condition); $data = ['@context' => ActivityPub::CONTEXT]; $data['id'] = System::baseUrl() . '/outbox/' . $owner['nickname']; $data['type'] = 'OrderedCollection'; $data['totalItems'] = $count; if (empty($page)) { $data['first'] = System::baseUrl() . '/outbox/' . $owner['nickname'] . '?page=1'; } else { $list = []; $condition['parent-network'] = Protocol::NATIVE_SUPPORT; $items = Item::select(['id'], $condition, ['limit' => [($page - 1) * 20, 20], 'order' => ['created' => true]]); while ($item = Item::fetch($items)) { $object = self::createObjectFromItemID($item['id']); unset($object['@context']); $list[] = $object; } if (!empty($list)) { $data['next'] = System::baseUrl() . '/outbox/' . $owner['nickname'] . '?page=' . ($page + 1); } $data['partOf'] = System::baseUrl() . '/outbox/' . $owner['nickname']; $data['orderedItems'] = $list; } return $data; } /** * Return the ActivityPub profile of the given user * * @param integer $uid User ID * @return array with profile data */ public static function getProfile($uid) { $condition = ['uid' => $uid, 'blocked' => false, 'account_expired' => false, 'account_removed' => false, 'verified' => true]; $fields = ['guid', 'nickname', 'pubkey', 'account-type', 'page-flags']; $user = DBA::selectFirst('user', $fields, $condition); if (!DBA::isResult($user)) { return []; } $fields = ['locality', 'region', 'country-name']; $profile = DBA::selectFirst('profile', $fields, ['uid' => $uid, 'is-default' => true]); if (!DBA::isResult($profile)) { return []; } $fields = ['name', 'url', 'location', 'about', 'avatar', 'photo']; $contact = DBA::selectFirst('contact', $fields, ['uid' => $uid, 'self' => true]); if (!DBA::isResult($contact)) { return []; } // On old installations and never changed contacts this might not be filled if (empty($contact['avatar'])) { $contact['avatar'] = $contact['photo']; } $data = ['@context' => ActivityPub::CONTEXT]; $data['id'] = $contact['url']; $data['diaspora:guid'] = $user['guid']; $data['type'] = ActivityPub::ACCOUNT_TYPES[$user['account-type']]; $data['following'] = System::baseUrl() . '/following/' . $user['nickname']; $data['followers'] = System::baseUrl() . '/followers/' . $user['nickname']; $data['inbox'] = System::baseUrl() . '/inbox/' . $user['nickname']; $data['outbox'] = System::baseUrl() . '/outbox/' . $user['nickname']; $data['preferredUsername'] = $user['nickname']; $data['name'] = $contact['name']; $data['vcard:hasAddress'] = ['@type' => 'vcard:Home', 'vcard:country-name' => $profile['country-name'], 'vcard:region' => $profile['region'], 'vcard:locality' => $profile['locality']]; $data['summary'] = $contact['about']; $data['url'] = $contact['url']; $data['manuallyApprovesFollowers'] = in_array($user['page-flags'], [Contact::PAGE_NORMAL, Contact::PAGE_PRVGROUP]); $data['publicKey'] = ['id' => $contact['url'] . '#main-key', 'owner' => $contact['url'], 'publicKeyPem' => $user['pubkey']]; $data['endpoints'] = ['sharedInbox' => System::baseUrl() . '/inbox']; $data['icon'] = ['type' => 'Image', 'url' => $contact['avatar']]; // tags: https://kitty.town/@inmysocks/100656097926961126.json return $data; } /** * Returns an array with permissions of a given item array * * @param array $item * * @return array with permissions */ private static function fetchPermissionBlockFromConversation($item) { if (empty($item['thr-parent'])) { return []; } $condition = ['item-uri' => $item['thr-parent'], 'protocol' => Conversation::PARCEL_ACTIVITYPUB]; $conversation = DBA::selectFirst('conversation', ['source'], $condition); if (!DBA::isResult($conversation)) { return []; } $activity = json_decode($conversation['source'], true); $actor = JsonLD::fetchElement($activity, 'actor', 'id'); $profile = APContact::getByURL($actor); $item_profile = APContact::getByURL($item['author-link']); $exclude[] = $item['author-link']; if ($item['gravity'] == GRAVITY_PARENT) { $exclude[] = $item['owner-link']; } $permissions['to'][] = $actor; foreach (['to', 'cc', 'bto', 'bcc'] as $element) { if (empty($activity[$element])) { continue; } if (is_string($activity[$element])) { $activity[$element] = [$activity[$element]]; } foreach ($activity[$element] as $receiver) { if ($receiver == $profile['followers'] && !empty($item_profile['followers'])) { $receiver = $item_profile['followers']; } if (!in_array($receiver, $exclude)) { $permissions[$element][] = $receiver; } } } return $permissions; } /** * Creates an array of permissions from an item thread * * @param array $item * * @return array with permission data */ private static function createPermissionBlockForItem($item) { // Will be activated in a later step // $networks = [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS]; // For now only send to these contacts: $networks = [Protocol::ACTIVITYPUB, Protocol::OSTATUS]; $data = ['to' => [], 'cc' => []]; $data = array_merge($data, self::fetchPermissionBlockFromConversation($item)); $actor_profile = APContact::getByURL($item['author-link']); $terms = Term::tagArrayFromItemId($item['id'], TERM_MENTION); if (!$item['private']) { $data['to'][] = ActivityPub::PUBLIC_COLLECTION; if (!empty($actor_profile['followers'])) { $data['cc'][] = $actor_profile['followers']; } foreach ($terms as $term) { $profile = APContact::getByURL($term['url'], false); if (!empty($profile)) { $data['to'][] = $profile['url']; } } } else { $receiver_list = Item::enumeratePermissions($item); $mentioned = []; foreach ($terms as $term) { $cid = Contact::getIdForURL($term['url'], $item['uid']); if (!empty($cid) && in_array($cid, $receiver_list)) { $contact = DBA::selectFirst('contact', ['url'], ['id' => $cid, 'network' => $networks]); if (DBA::isResult($contact) && !empty($profile = APContact::getByURL($contact['url'], false))) { $data['to'][] = $profile['url']; } } } foreach ($receiver_list as $receiver) { $contact = DBA::selectFirst('contact', ['url'], ['id' => $receiver, 'network' => $networks]); if (DBA::isResult($contact) && !empty($profile = APContact::getByURL($contact['url'], false))) { $data['cc'][] = $profile['url']; } } } $parents = Item::select(['id', 'author-link', 'owner-link', 'gravity', 'uri'], ['parent' => $item['parent']]); while ($parent = Item::fetch($parents)) { // Don't include data from future posts if ($parent['id'] >= $item['id']) { continue; } $profile = APContact::getByURL($parent['author-link'], false); if (!empty($profile)) { if ($parent['uri'] == $item['thr-parent']) { $data['to'][] = $profile['url']; } else { $data['cc'][] = $profile['url']; } } if ($item['gravity'] != GRAVITY_PARENT) { continue; } $profile = APContact::getByURL($parent['owner-link'], false); if (!empty($profile)) { $data['cc'][] = $profile['url']; } } DBA::close($parents); $data['to'] = array_unique($data['to']); $data['cc'] = array_unique($data['cc']); if (($key = array_search($item['author-link'], $data['to'])) !== false) { unset($data['to'][$key]); } if (($key = array_search($item['author-link'], $data['cc'])) !== false) { unset($data['cc'][$key]); } foreach ($data['to'] as $to) { if (($key = array_search($to, $data['cc'])) !== false) { unset($data['cc'][$key]); } } return ['to' => array_values($data['to']), 'cc' => array_values($data['cc'])]; } /** * Fetches a list of inboxes of followers of a given user * * @param integer $uid User ID * @param boolean $personal fetch personal inboxes * * @return array of follower inboxes */ public static function fetchTargetInboxesforUser($uid, $personal = false) { $inboxes = []; // Will be activated in a later step // $networks = [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS]; // For now only send to these contacts: $networks = [Protocol::ACTIVITYPUB, Protocol::OSTATUS]; $condition = ['uid' => $uid, 'network' => $networks, 'archive' => false, 'pending' => false]; if (!empty($uid)) { $condition['rel'] = [Contact::FOLLOWER, Contact::FRIEND]; } $contacts = DBA::select('contact', ['url'], $condition); while ($contact = DBA::fetch($contacts)) { $profile = APContact::getByURL($contact['url'], false); if (!empty($profile)) { if (empty($profile['sharedinbox']) || $personal) { $target = $profile['inbox']; } else { $target = $profile['sharedinbox']; } $inboxes[$target] = $target; } } DBA::close($contacts); return $inboxes; } /** * Fetches an array of inboxes for the given item and user * * @param array $item * @param integer $uid User ID * @param boolean $personal fetch personal inboxes * * @return array with inboxes */ public static function fetchTargetInboxes($item, $uid, $personal = false) { $permissions = self::createPermissionBlockForItem($item); if (empty($permissions)) { return []; } $inboxes = []; if ($item['gravity'] == GRAVITY_ACTIVITY) { $item_profile = APContact::getByURL($item['author-link'], false); } else { $item_profile = APContact::getByURL($item['owner-link'], false); } foreach (['to', 'cc', 'bto', 'bcc'] as $element) { if (empty($permissions[$element])) { continue; } foreach ($permissions[$element] as $receiver) { if ($receiver == $item_profile['followers']) { $inboxes = self::fetchTargetInboxesforUser($uid, $personal); } else { $profile = APContact::getByURL($receiver, false); if (!empty($profile)) { if (empty($profile['sharedinbox']) || $personal) { $target = $profile['inbox']; } else { $target = $profile['sharedinbox']; } $inboxes[$target] = $target; } } } } return $inboxes; } /** * Returns the activity type of a given item * * @param array $item * * @return string with activity type */ private static function getTypeOfItem($item) { if (!empty(Diaspora::isReshare($item['body'], false))) { $type = 'Announce'; } elseif ($item['verb'] == ACTIVITY_POST) { if ($item['created'] == $item['edited']) { $type = 'Create'; } else { $type = 'Update'; } } elseif ($item['verb'] == ACTIVITY_LIKE) { $type = 'Like'; } elseif ($item['verb'] == ACTIVITY_DISLIKE) { $type = 'Dislike'; } elseif ($item['verb'] == ACTIVITY_ATTEND) { $type = 'Accept'; } elseif ($item['verb'] == ACTIVITY_ATTENDNO) { $type = 'Reject'; } elseif ($item['verb'] == ACTIVITY_ATTENDMAYBE) { $type = 'TentativeAccept'; } else { $type = ''; } return $type; } /** * Creates the activity or fetches it from the cache * * @param integer $item_id * * @return array with the activity */ public static function createCachedActivityFromItem($item_id) { $cachekey = 'APDelivery:createActivity:' . $item_id; $data = Cache::get($cachekey); if (!is_null($data)) { return $data; } $data = ActivityPub\Transmitter::createActivityFromItem($item_id); Cache::set($cachekey, $data, CACHE_QUARTER_HOUR); return $data; } /** * Creates an activity array for a given item id * * @param integer $item_id * @param boolean $object_mode Is the activity item is used inside another object? * * @return array of activity */ public static function createActivityFromItem($item_id, $object_mode = false) { $item = Item::selectFirst([], ['id' => $item_id, 'parent-network' => Protocol::NATIVE_SUPPORT]); if (!DBA::isResult($item)) { return false; } $condition = ['item-uri' => $item['uri'], 'protocol' => Conversation::PARCEL_ACTIVITYPUB]; $conversation = DBA::selectFirst('conversation', ['source'], $condition); if (DBA::isResult($conversation)) { $data = json_decode($conversation['source']); if (!empty($data)) { return $data; } } $type = self::getTypeOfItem($item); if (!$object_mode) { $data = ['@context' => ActivityPub::CONTEXT]; if ($item['deleted'] && ($item['gravity'] == GRAVITY_ACTIVITY)) { $type = 'Undo'; } elseif ($item['deleted']) { $type = 'Delete'; } } else { $data = []; } $data['id'] = $item['uri'] . '#' . $type; $data['type'] = $type; $data['actor'] = $item['owner-link']; $data['published'] = DateTimeFormat::utc($item['created'] . '+00:00', DateTimeFormat::ATOM); $data['instrument'] = ['type' => 'Service', 'name' => BaseObject::getApp()->getUserAgent()]; $data = array_merge($data, self::createPermissionBlockForItem($item)); if (in_array($data['type'], ['Create', 'Update', 'Delete'])) { $data['object'] = self::createNote($item); } elseif ($data['type'] == 'Announce') { $data['object'] = self::createAnnounce($item); } elseif ($data['type'] == 'Undo') { $data['object'] = self::createActivityFromItem($item_id, true); } else { $data['diaspora:guid'] = $item['guid']; $data['object'] = $item['thr-parent']; } $owner = User::getOwnerDataById($item['uid']); if (!$object_mode) { return LDSignature::sign($data, $owner); } else { return $data; } /// @todo Create "conversation" entry } /** * Creates an object array for a given item id * * @param integer $item_id * * @return array with the object data */ public static function createObjectFromItemID($item_id) { $item = Item::selectFirst([], ['id' => $item_id, 'parent-network' => Protocol::NATIVE_SUPPORT]); if (!DBA::isResult($item)) { return false; } $data = ['@context' => ActivityPub::CONTEXT]; $data = array_merge($data, self::createNote($item)); return $data; } /** * Creates a location entry for a given item array * * @param array $item * * @return array with location array */ private static function createLocation($item) { $location = ['type' => 'Place']; if (!empty($item['location'])) { $location['name'] = $item['location']; } $coord = []; if (empty($item['coord'])) { $coord = Map::getCoordinates($item['location']); } else { $coords = explode(' ', $item['coord']); if (count($coords) == 2) { $coord = ['lat' => $coords[0], 'lon' => $coords[1]]; } } if (!empty($coord['lat']) && !empty($coord['lon'])) { $location['latitude'] = $coord['lat']; $location['longitude'] = $coord['lon']; } return $location; } /** * Returns a tag array for a given item array * * @param array $item * * @return array of tags */ private static function createTagList($item) { $tags = []; $terms = Term::tagArrayFromItemId($item['id']); foreach ($terms as $term) { if ($term['type'] == TERM_HASHTAG) { $tags[] = ['type' => 'Hashtag', 'href' => $term['url'], 'name' => '#' . $term['term']]; } elseif ($term['type'] == TERM_MENTION) { $contact = Contact::getDetailsByURL($term['url']); if (!empty($contact['addr'])) { $mention = '@' . $contact['addr']; } else { $mention = '@' . $term['url']; } $tags[] = ['type' => 'Mention', 'href' => $term['url'], 'name' => $mention]; } } return $tags; } /** * Adds attachment data to the JSON document * * @param array $item Data of the item that is to be posted * @param text $type Object type * * @return array with attachment data */ private static function createAttachmentList($item, $type) { $attachments = []; $arr = explode('[/attach],', $item['attach']); if (count($arr)) { foreach ($arr as $r) { $matches = false; $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|', $r, $matches); if ($cnt) { $attributes = ['type' => 'Document', 'mediaType' => $matches[3], 'url' => $matches[1], 'name' => null]; if (trim($matches[4]) != '') { $attributes['name'] = trim($matches[4]); } $attachments[] = $attributes; } } } if ($type != 'Note') { return $attachments; } // Simplify image codes $body = preg_replace("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism", '[img]$3[/img]', $item['body']); // Grab all pictures and create attachments out of them if (preg_match_all("/\[img\]([^\[\]]*)\[\/img\]/Usi", $body, $pictures)) { foreach ($pictures[1] as $picture) { $imgdata = Image::getInfoFromURL($picture); if ($imgdata) { $attachments[] = ['type' => 'Document', 'mediaType' => $imgdata['mime'], 'url' => $picture, 'name' => null]; } } } return $attachments; } /** * @brief Callback function to replace a Friendica style mention in a mention that is used on AP * * @param array $match Matching values for the callback * @return string Replaced mention */ private static function mentionCallback($match) { if (empty($match[1])) { return; } $data = Contact::getDetailsByURL($match[1]); if (empty($data) || empty($data['nick'])) { return; } return '@[url=' . $data['url'] . ']' . $data['nick'] . '[/url]'; } /** * Remove image elements and replaces them with links to the image * * @param string $body * * @return string with replaced elements */ private static function removePictures($body) { // Simplify image codes $body = preg_replace("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism", '[img]$3[/img]', $body); $body = preg_replace("/\[url=([^\[\]]*)\]\[img\](.*)\[\/img\]\[\/url\]/Usi", '[url]$1[/url]', $body); $body = preg_replace("/\[img\]([^\[\]]*)\[\/img\]/Usi", '[url]$1[/url]', $body); return $body; } /** * Fetches the "context" value for a givem item array from the "conversation" table * * @param array $item * * @return string with context url */ private static function fetchContextURLForItem($item) { $conversation = DBA::selectFirst('conversation', ['conversation-href', 'conversation-uri'], ['item-uri' => $item['parent-uri']]); if (DBA::isResult($conversation) && !empty($conversation['conversation-href'])) { $context_uri = $conversation['conversation-href']; } elseif (DBA::isResult($conversation) && !empty($conversation['conversation-uri'])) { $context_uri = $conversation['conversation-uri']; } else { $context_uri = $item['parent-uri'] . '#context'; } return $context_uri; } /** * Returns if the post contains sensitive content ("nsfw") * * @param integer $item_id * * @return boolean */ private static function isSensitive($item_id) { $condition = ['otype' => TERM_OBJ_POST, 'oid' => $item_id, 'type' => TERM_HASHTAG, 'term' => 'nsfw']; return DBA::exists('term', $condition); } /** * Creates a note/article object array * * @param array $item * * @return array with the object data */ public static function createNote($item) { if (!empty($item['title'])) { $type = 'Article'; } else { $type = 'Note'; } if ($item['deleted']) { $type = 'Tombstone'; } $data = []; $data['id'] = $item['uri']; $data['type'] = $type; if ($item['deleted']) { return $data; } $data['summary'] = null; // Ignore by now if ($item['uri'] != $item['thr-parent']) { $data['inReplyTo'] = $item['thr-parent']; } else { $data['inReplyTo'] = null; } $data['diaspora:guid'] = $item['guid']; $data['published'] = DateTimeFormat::utc($item['created'] . '+00:00', DateTimeFormat::ATOM); if ($item['created'] != $item['edited']) { $data['updated'] = DateTimeFormat::utc($item['edited'] . '+00:00', DateTimeFormat::ATOM); } $data['url'] = $item['plink']; $data['attributedTo'] = $item['author-link']; $data['sensitive'] = self::isSensitive($item['id']); $data['context'] = self::fetchContextURLForItem($item); if (!empty($item['title'])) { $data['name'] = BBCode::toPlaintext($item['title'], false); } $body = $item['body']; if ($type == 'Note') { $body = self::removePictures($body); } $regexp = "/[@!]\[url\=([^\[\]]*)\].*?\[\/url\]/ism"; $body = preg_replace_callback($regexp, ['self', 'mentionCallback'], $body); $data['content'] = BBCode::convert($body, false, 7); $data['source'] = ['content' => $item['body'], 'mediaType' => "text/bbcode"]; if (!empty($item['signed_text']) && ($item['uri'] != $item['thr-parent'])) { $data['diaspora:comment'] = $item['signed_text']; } $data['attachment'] = self::createAttachmentList($item, $type); $data['tag'] = self::createTagList($item); if (!empty($item['coord']) || !empty($item['location'])) { $data['location'] = self::createLocation($item); } if (!empty($item['app'])) { $data['generator'] = ['type' => 'Application', 'name' => $item['app']]; } $data = array_merge($data, self::createPermissionBlockForItem($item)); return $data; } /** * Creates an announce object entry * * @param array $item * * @return string with announced object url */ public static function createAnnounce($item) { $announce = api_share_as_retweet($item); if (empty($announce['plink'])) { return self::createNote($item); } return $announce['plink']; } /** * Transmits a contact suggestion to a given inbox * * @param integer $uid User ID * @param string $inbox Target inbox * @param integer $suggestion_id Suggestion ID */ public static function sendContactSuggestion($uid, $inbox, $suggestion_id) { $owner = User::getOwnerDataById($uid); $profile = APContact::getByURL($owner['url']); $suggestion = DBA::selectFirst('fsuggest', ['url', 'note', 'created'], ['id' => $suggestion_id]); $data = ['@context' => 'https://www.w3.org/ns/activitystreams', 'id' => System::baseUrl() . '/activity/' . System::createGUID(), 'type' => 'Announce', 'actor' => $owner['url'], 'object' => $suggestion['url'], 'content' => $suggestion['note'], 'instrument' => ['type' => 'Service', 'name' => BaseObject::getApp()->getUserAgent()], 'to' => [ActivityPub::PUBLIC_COLLECTION], 'cc' => []]; $signed = LDSignature::sign($data, $owner); logger('Deliver profile deletion for user ' . $uid . ' to ' . $inbox . ' via ActivityPub', LOGGER_DEBUG); HTTPSignature::transmit($signed, $inbox, $uid); } /** * Transmits a profile deletion to a given inbox * * @param integer $uid User ID * @param string $inbox Target inbox */ public static function sendProfileDeletion($uid, $inbox) { $owner = User::getOwnerDataById($uid); $profile = APContact::getByURL($owner['url']); $data = ['@context' => 'https://www.w3.org/ns/activitystreams', 'id' => System::baseUrl() . '/activity/' . System::createGUID(), 'type' => 'Delete', 'actor' => $owner['url'], 'object' => $owner['url'], 'published' => DateTimeFormat::utcNow(DateTimeFormat::ATOM), 'instrument' => ['type' => 'Service', 'name' => BaseObject::getApp()->getUserAgent()], 'to' => [ActivityPub::PUBLIC_COLLECTION], 'cc' => []]; $signed = LDSignature::sign($data, $owner); logger('Deliver profile deletion for user ' . $uid . ' to ' . $inbox . ' via ActivityPub', LOGGER_DEBUG); HTTPSignature::transmit($signed, $inbox, $uid); } /** * Transmits a profile change to a given inbox * * @param integer $uid User ID * @param string $inbox Target inbox */ public static function sendProfileUpdate($uid, $inbox) { $owner = User::getOwnerDataById($uid); $profile = APContact::getByURL($owner['url']); $data = ['@context' => 'https://www.w3.org/ns/activitystreams', 'id' => System::baseUrl() . '/activity/' . System::createGUID(), 'type' => 'Update', 'actor' => $owner['url'], 'object' => self::getProfile($uid), 'published' => DateTimeFormat::utcNow(DateTimeFormat::ATOM), 'instrument' => ['type' => 'Service', 'name' => BaseObject::getApp()->getUserAgent()], 'to' => [$profile['followers']], 'cc' => []]; $signed = LDSignature::sign($data, $owner); logger('Deliver profile update for user ' . $uid . ' to ' . $inbox . ' via ActivityPub', LOGGER_DEBUG); HTTPSignature::transmit($signed, $inbox, $uid); } /** * Transmits a given activity to a target * * @param array $activity * @param string $target Target profile * @param integer $uid User ID */ public static function sendActivity($activity, $target, $uid) { $profile = APContact::getByURL($target); $owner = User::getOwnerDataById($uid); $data = ['@context' => 'https://www.w3.org/ns/activitystreams', 'id' => System::baseUrl() . '/activity/' . System::createGUID(), 'type' => $activity, 'actor' => $owner['url'], 'object' => $profile['url'], 'instrument' => ['type' => 'Service', 'name' => BaseObject::getApp()->getUserAgent()], 'to' => $profile['url']]; logger('Sending activity ' . $activity . ' to ' . $target . ' for user ' . $uid, LOGGER_DEBUG); $signed = LDSignature::sign($data, $owner); HTTPSignature::transmit($signed, $profile['inbox'], $uid); } /** * Transmit a message that the contact request had been accepted * * @param string $target Target profile * @param $id * @param integer $uid User ID */ public static function sendContactAccept($target, $id, $uid) { $profile = APContact::getByURL($target); $owner = User::getOwnerDataById($uid); $data = ['@context' => 'https://www.w3.org/ns/activitystreams', 'id' => System::baseUrl() . '/activity/' . System::createGUID(), 'type' => 'Accept', 'actor' => $owner['url'], 'object' => ['id' => $id, 'type' => 'Follow', 'actor' => $profile['url'], 'object' => $owner['url']], 'instrument' => ['type' => 'Service', 'name' => BaseObject::getApp()->getUserAgent()], 'to' => $profile['url']]; logger('Sending accept to ' . $target . ' for user ' . $uid . ' with id ' . $id, LOGGER_DEBUG); $signed = LDSignature::sign($data, $owner); HTTPSignature::transmit($signed, $profile['inbox'], $uid); } /** * Reject a contact request or terminates the contact relation * * @param string $target Target profile * @param $id * @param integer $uid User ID */ public static function sendContactReject($target, $id, $uid) { $profile = APContact::getByURL($target); $owner = User::getOwnerDataById($uid); $data = ['@context' => 'https://www.w3.org/ns/activitystreams', 'id' => System::baseUrl() . '/activity/' . System::createGUID(), 'type' => 'Reject', 'actor' => $owner['url'], 'object' => ['id' => $id, 'type' => 'Follow', 'actor' => $profile['url'], 'object' => $owner['url']], 'instrument' => ['type' => 'Service', 'name' => BaseObject::getApp()->getUserAgent()], 'to' => $profile['url']]; logger('Sending reject to ' . $target . ' for user ' . $uid . ' with id ' . $id, LOGGER_DEBUG); $signed = LDSignature::sign($data, $owner); HTTPSignature::transmit($signed, $profile['inbox'], $uid); } /** * Transmits a message that we don't want to follow this contact anymore * * @param string $target Target profile * @param integer $uid User ID */ public static function sendContactUndo($target, $uid) { $profile = APContact::getByURL($target); $id = System::baseUrl() . '/activity/' . System::createGUID(); $owner = User::getOwnerDataById($uid); $data = ['@context' => 'https://www.w3.org/ns/activitystreams', 'id' => $id, 'type' => 'Undo', 'actor' => $owner['url'], 'object' => ['id' => $id, 'type' => 'Follow', 'actor' => $owner['url'], 'object' => $profile['url']], 'instrument' => ['type' => 'Service', 'name' => BaseObject::getApp()->getUserAgent()], 'to' => $profile['url']]; logger('Sending undo to ' . $target . ' for user ' . $uid . ' with id ' . $id, LOGGER_DEBUG); $signed = LDSignature::sign($data, $owner); HTTPSignature::transmit($signed, $profile['inbox'], $uid); } }