{"action":"created","comment":{"url":"https://api.github.com/repos/friendica/friendica/pulls/comments/222285454","pull_request_review_id":161129030,"id":222285454,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDIyMjI4NTQ1NA==","diff_hunk":"@@ -0,0 +1,843 @@\n+<?php\n+/**\n+ * @file src/Protocol/ActivityPub/Transmitter.php\n+ */\n+namespace Friendica\\Protocol\\ActivityPub;\n+\n+use Friendica\\Database\\DBA;\n+use Friendica\\Core\\System;\n+use Friendica\\BaseObject;\n+use Friendica\\Util\\HTTPSignature;\n+use Friendica\\Core\\Protocol;\n+use Friendica\\Model\\Conversation;\n+use Friendica\\Model\\Contact;\n+use Friendica\\Model\\APContact;\n+use Friendica\\Model\\Item;\n+use Friendica\\Model\\Term;\n+use Friendica\\Model\\User;\n+use Friendica\\Util\\DateTimeFormat;\n+use Friendica\\Content\\Text\\BBCode;\n+use Friendica\\Util\\JsonLD;\n+use Friendica\\Util\\LDSignature;\n+use Friendica\\Protocol\\ActivityPub;\n+use Friendica\\Model\\Profile;\n+\n+/**\n+ * @brief ActivityPub Transmitter Protocol class\n+ *\n+ * To-Do:\n+ * - Event\n+ *\n+ * Complicated:\n+ * - Announce\n+ * - Undo Announce\n+ *\n+ * General:\n+ * - Attachments\n+ * - nsfw (sensitive)\n+ * - Queueing unsucessful deliveries\n+ */\n+class Transmitter\n+{\n+\t/**\n+\t * @brief collects the lost of followers of the given owner\n+\t *\n+\t * @param array $owner Owner array\n+\t * @param integer $page Page number\n+\t *\n+\t * @return array of owners\n+\t */\n+\tpublic static function getFollowers($owner, $page = null)\n+\t{\n+\t\t$condition = ['rel' => [Contact::FOLLOWER, Contact::FRIEND], 'network' => Protocol::NATIVE_SUPPORT, 'uid' => $owner['uid'],\n+\t\t\t'self' => false, 'hidden' => false, 'archive' => false, 'pending' => false];\n+\t\t$count = DBA::count('contact', $condition);\n+\n+\t\t$data = ['@context' => ActivityPub::CONTEXT];\n+\t\t$data['id'] = System::baseUrl() . '/followers/' . $owner['nickname'];\n+\t\t$data['type'] = 'OrderedCollection';\n+\t\t$data['totalItems'] = $count;\n+\n+\t\t// When we hide our friends we will only show the pure number but don't allow more.\n+\t\t$profile = Profile::getByUID($owner['uid']);\n+\t\tif (!empty($profile['hide-friends'])) {\n+\t\t\treturn $data;\n+\t\t}\n+\n+\t\tif (empty($page)) {\n+\t\t\t$data['first'] = System::baseUrl() . '/followers/' . $owner['nickname'] . '?page=1';\n+\t\t} else {\n+\t\t\t$list = [];\n+\n+\t\t\t$contacts = DBA::select('contact', ['url'], $condition, ['limit' => [($page - 1) * 100, 100]]);\n+\t\t\twhile ($contact = DBA::fetch($contacts)) {\n+\t\t\t\t$list[] = $contact['url'];\n+\t\t\t}\n+\n+\t\t\tif (!empty($list)) {\n+\t\t\t\t$data['next'] = System::baseUrl() . '/followers/' . $owner['nickname'] . '?page=' . ($page + 1);\n+\t\t\t}\n+\n+\t\t\t$data['partOf'] = System::baseUrl() . '/followers/' . $owner['nickname'];\n+\n+\t\t\t$data['orderedItems'] = $list;\n+\t\t}\n+\n+\t\treturn $data;\n+\t}\n+\n+\t/**\n+\t * @brief Create list of following contacts\n+\t *\n+\t * @param array $owner Owner array\n+\t * @param integer $page Page numbe\n+\t *\n+\t * @return array of following contacts\n+\t */\n+\tpublic static function getFollowing($owner, $page = null)\n+\t{\n+\t\t$condition = ['rel' => [Contact::SHARING, Contact::FRIEND], 'network' => Protocol::NATIVE_SUPPORT, 'uid' => $owner['uid'],\n+\t\t\t'self' => false, 'hidden' => false, 'archive' => false, 'pending' => false];\n+\t\t$count = DBA::count('contact', $condition);\n+\n+\t\t$data = ['@context' => ActivityPub::CONTEXT];\n+\t\t$data['id'] = System::baseUrl() . '/following/' . $owner['nickname'];\n+\t\t$data['type'] = 'OrderedCollection';\n+\t\t$data['totalItems'] = $count;\n+\n+\t\t// When we hide our friends we will only show the pure number but don't allow more.\n+\t\t$profile = Profile::getByUID($owner['uid']);\n+\t\tif (!empty($profile['hide-friends'])) {\n+\t\t\treturn $data;\n+\t\t}\n+\n+\t\tif (empty($page)) {\n+\t\t\t$data['first'] = System::baseUrl() . '/following/' . $owner['nickname'] . '?page=1';\n+\t\t} else {\n+\t\t\t$list = [];\n+\n+\t\t\t$contacts = DBA::select('contact', ['url'], $condition, ['limit' => [($page - 1) * 100, 100]]);\n+\t\t\twhile ($contact = DBA::fetch($contacts)) {\n+\t\t\t\t$list[] = $contact['url'];\n+\t\t\t}\n+\n+\t\t\tif (!empty($list)) {\n+\t\t\t\t$data['next'] = System::baseUrl() . '/following/' . $owner['nickname'] . '?page=' . ($page + 1);\n+\t\t\t}\n+\n+\t\t\t$data['partOf'] = System::baseUrl() . '/following/' . $owner['nickname'];\n+\n+\t\t\t$data['orderedItems'] = $list;\n+\t\t}\n+\n+\t\treturn $data;\n+\t}\n+\n+\t/**\n+\t * @brief Public posts for the given owner\n+\t *\n+\t * @param array $owner Owner array\n+\t * @param integer $page Page numbe\n+\t *\n+\t * @return array of posts\n+\t */\n+\tpublic static function getOutbox($owner, $page = null)\n+\t{\n+\t\t$public_contact = Contact::getIdForURL($owner['url'], 0, true);\n+\n+\t\t$condition = ['uid' => $owner['uid'], 'contact-id' => $owner['id'], 'author-id' => $public_contact,\n+\t\t\t'wall' => true, 'private' => false, 'gravity' => [GRAVITY_PARENT, GRAVITY_COMMENT],\n+\t\t\t'deleted' => false, 'visible' => true];\n+\t\t$count = DBA::count('item', $condition);\n+\n+\t\t$data = ['@context' => ActivityPub::CONTEXT];\n+\t\t$data['id'] = System::baseUrl() . '/outbox/' . $owner['nickname'];\n+\t\t$data['type'] = 'OrderedCollection';\n+\t\t$data['totalItems'] = $count;\n+\n+\t\tif (empty($page)) {\n+\t\t\t$data['first'] = System::baseUrl() . '/outbox/' . $owner['nickname'] . '?page=1';\n+\t\t} else {\n+\t\t\t$list = [];\n+\n+\t\t\t$condition['parent-network'] = Protocol::NATIVE_SUPPORT;\n+\n+\t\t\t$items = Item::select(['id'], $condition, ['limit' => [($page - 1) * 20, 20], 'order' => ['created' => true]]);\n+\t\t\twhile ($item = Item::fetch($items)) {\n+\t\t\t\t$object = self::createObjectFromItemID($item['id']);\n+\t\t\t\tunset($object['@context']);\n+\t\t\t\t$list[] = $object;\n+\t\t\t}\n+\n+\t\t\tif (!empty($list)) {\n+\t\t\t\t$data['next'] = System::baseUrl() . '/outbox/' . $owner['nickname'] . '?page=' . ($page + 1);\n+\t\t\t}\n+\n+\t\t\t$data['partOf'] = System::baseUrl() . '/outbox/' . $owner['nickname'];\n+\n+\t\t\t$data['orderedItems'] = $list;\n+\t\t}\n+\n+\t\treturn $data;\n+\t}\n+\n+\t/**\n+\t * Return the ActivityPub profile of the given user\n+\t *\n+\t * @param integer $uid User ID\n+\t * @return profile array\n+\t */\n+\tpublic static function profile($uid)\n+\t{\n+\t\t$condition = ['uid' => $uid, 'blocked' => false, 'account_expired' => false,\n+\t\t\t'account_removed' => false, 'verified' => true];\n+\t\t$fields = ['guid', 'nickname', 'pubkey', 'account-type', 'page-flags'];\n+\t\t$user = DBA::selectFirst('user', $fields, $condition);\n+\t\tif (!DBA::isResult($user)) {\n+\t\t\treturn [];\n+\t\t}\n+\n+\t\t$fields = ['locality', 'region', 'country-name'];\n+\t\t$profile = DBA::selectFirst('profile', $fields, ['uid' => $uid, 'is-default' => true]);\n+\t\tif (!DBA::isResult($profile)) {\n+\t\t\treturn [];\n+\t\t}\n+\n+\t\t$fields = ['name', 'url', 'location', 'about', 'avatar'];\n+\t\t$contact = DBA::selectFirst('contact', $fields, ['uid' => $uid, 'self' => true]);\n+\t\tif (!DBA::isResult($contact)) {\n+\t\t\treturn [];\n+\t\t}\n+\n+\t\t$data = ['@context' => ActivityPub::CONTEXT];\n+\t\t$data['id'] = $contact['url'];\n+\t\t$data['diaspora:guid'] = $user['guid'];\n+\t\t$data['type'] = ActivityPub::ACCOUNT_TYPES[$user['account-type']];\n+\t\t$data['following'] = System::baseUrl() . '/following/' . $user['nickname'];\n+\t\t$data['followers'] = System::baseUrl() . '/followers/' . $user['nickname'];\n+\t\t$data['inbox'] = System::baseUrl() . '/inbox/' . $user['nickname'];\n+\t\t$data['outbox'] = System::baseUrl() . '/outbox/' . $user['nickname'];\n+\t\t$data['preferredUsername'] = $user['nickname'];\n+\t\t$data['name'] = $contact['name'];\n+\t\t$data['vcard:hasAddress'] = ['@type' => 'vcard:Home', 'vcard:country-name' => $profile['country-name'],\n+\t\t\t'vcard:region' => $profile['region'], 'vcard:locality' => $profile['locality']];\n+\t\t$data['summary'] = $contact['about'];\n+\t\t$data['url'] = $contact['url'];\n+\t\t$data['manuallyApprovesFollowers'] = in_array($user['page-flags'], [Contact::PAGE_NORMAL, Contact::PAGE_PRVGROUP]);\n+\t\t$data['publicKey'] = ['id' => $contact['url'] . '#main-key',\n+\t\t\t'owner' => $contact['url'],\n+\t\t\t'publicKeyPem' => $user['pubkey']];\n+\t\t$data['endpoints'] = ['sharedInbox' => System::baseUrl() . '/inbox'];\n+\t\t$data['icon'] = ['type' => 'Image',\n+\t\t\t'url' => $contact['avatar']];\n+\n+\t\t// tags: https://kitty.town/@inmysocks/100656097926961126.json\n+\t\treturn $data;\n+\t}\n+\n+\t/**\n+\t * @brief Returns an array with permissions of a given item array\n+\t *\n+\t * @param array $item\n+\t *\n+\t * @return array with permissions\n+\t */\n+\tprivate static function fetchPermissionBlockFromConversation($item)\n+\t{\n+\t\tif (empty($item['thr-parent'])) {\n+\t\t\treturn [];\n+\t\t}\n+\n+\t\t$condition = ['item-uri' => $item['thr-parent'], 'protocol' => Conversation::PARCEL_ACTIVITYPUB];\n+\t\t$conversation = DBA::selectFirst('conversation', ['source'], $condition);\n+\t\tif (!DBA::isResult($conversation)) {\n+\t\t\treturn [];\n+\t\t}\n+\n+\t\t$activity = json_decode($conversation['source'], true);\n+\n+\t\t$actor = JsonLD::fetchElement($activity, 'actor', 'id');\n+\t\t$profile = APContact::getByURL($actor);\n+\n+\t\t$item_profile = APContact::getByURL($item['author-link']);\n+\t\t$exclude[] = $item['author-link'];\n+\n+\t\tif ($item['gravity'] == GRAVITY_PARENT) {\n+\t\t\t$exclude[] = $item['owner-link'];\n+\t\t}\n+\n+\t\t$permissions['to'][] = $actor;\n+\n+\t\tforeach (['to', 'cc', 'bto', 'bcc'] as $element) {\n+\t\t\tif (empty($activity[$element])) {\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t\tif (is_string($activity[$element])) {\n+\t\t\t\t$activity[$element] = [$activity[$element]];\n+\t\t\t}\n+\n+\t\t\tforeach ($activity[$element] as $receiver) {\n+\t\t\t\tif ($receiver == $profile['followers'] && !empty($item_profile['followers'])) {\n+\t\t\t\t\t$receiver = $item_profile['followers'];\n+\t\t\t\t}\n+\t\t\t\tif (!in_array($receiver, $exclude)) {\n+\t\t\t\t\t$permissions[$element][] = $receiver;\n+\t\t\t\t}\n+\t\t\t}\n+\t\t}\n+\t\treturn $permissions;\n+\t}\n+\n+\t/**\n+\t * @brief Creates an array of permissions from an item thread\n+\t *\n+\t * @param array $item\n+\t *\n+\t * @return permission array\n+\t */\n+\tprivate static function createPermissionBlockForItem($item)\n+\t{\n+\t\t$data = ['to' => [], 'cc' => []];\n+\n+\t\t$data = array_merge($data, self::fetchPermissionBlockFromConversation($item));\n+\n+\t\t$actor_profile = APContact::getByURL($item['author-link']);\n+\n+\t\t$terms = Term::tagArrayFromItemId($item['id'], TERM_MENTION);\n+\n+\t\t$contacts[$item['author-link']] = $item['author-link'];\n+\n+\t\tif (!$item['private']) {\n+\t\t\t$data['to'][] = ActivityPub::PUBLIC_COLLECTION;\n+\t\t\tif (!empty($actor_profile['followers'])) {\n+\t\t\t\t$data['cc'][] = $actor_profile['followers'];\n+\t\t\t}\n+\n+\t\t\tforeach ($terms as $term) {\n+\t\t\t\t$profile = APContact::getByURL($term['url'], false);\n+\t\t\t\tif (!empty($profile) && empty($contacts[$profile['url']])) {\n+\t\t\t\t\t$data['cc'][] = $profile['url'];\n+\t\t\t\t\t$contacts[$profile['url']] = $profile['url'];\n+\t\t\t\t}\n+\t\t\t}\n+\t\t} else {\n+\t\t\t$receiver_list = Item::enumeratePermissions($item);\n+\n+\t\t\t$mentioned = [];\n+\n+\t\t\tforeach ($terms as $term) {\n+\t\t\t\t$cid = Contact::getIdForURL($term['url'], $item['uid']);\n+\t\t\t\tif (!empty($cid) && in_array($cid, $receiver_list)) {\n+\t\t\t\t\t$contact = DBA::selectFirst('contact', ['url'], ['id' => $cid, 'network' => Protocol::ACTIVITYPUB]);\n+\t\t\t\t\t$data['to'][] = $contact['url'];\n+\t\t\t\t\t$contacts[$contact['url']] = $contact['url'];\n+\t\t\t\t}\n+\t\t\t}\n+\n+\t\t\tforeach ($receiver_list as $receiver) {\n+\t\t\t\t$contact = DBA::selectFirst('contact', ['url'], ['id' => $receiver, 'network' => Protocol::ACTIVITYPUB]);\n+\t\t\t\tif (empty($contacts[$contact['url']])) {\n+\t\t\t\t\t$data['cc'][] = $contact['url'];\n+\t\t\t\t\t$contacts[$contact['url']] = $contact['url'];\n+\t\t\t\t}\n+\t\t\t}\n+\t\t}\n+\n+\t\t$parents = Item::select(['id', 'author-link', 'owner-link', 'gravity'], ['parent' => $item['parent']]);\n+\t\twhile ($parent = Item::fetch($parents)) {\n+\t\t\t// Don't include data from future posts\n+\t\t\tif ($parent['id'] >= $item['id']) {\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\n+\t\t\t$profile = APContact::getByURL($parent['author-link'], false);\n+\t\t\tif (!empty($profile) && empty($contacts[$profile['url']])) {\n+\t\t\t\t$data['cc'][] = $profile['url'];\n+\t\t\t\t$contacts[$profile['url']] = $profile['url'];\n+\t\t\t}\n+\n+\t\t\tif ($item['gravity'] != GRAVITY_PARENT) {\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\n+\t\t\t$profile = APContact::getByURL($parent['owner-link'], false);\n+\t\t\tif (!empty($profile) && empty($contacts[$profile['url']])) {\n+\t\t\t\t$data['cc'][] = $profile['url'];\n+\t\t\t\t$contacts[$profile['url']] = $profile['url'];\n+\t\t\t}\n+\t\t}\n+\t\tDBA::close($parents);\n+\n+\t\tif (empty($data['to'])) {\n+\t\t\t$data['to'] = $data['cc'];\n+\t\t\t$data['cc'] = [];\n+\t\t}\n+\n+\t\treturn $data;\n+\t}\n+\n+\t/**\n+\t * @brief Fetches a list of inboxes of followers of a given user\n+\t *\n+\t * @param integer $uid User ID\n+\t *\n+\t * @return array of follower inboxes\n+\t */\n+\tpublic static function fetchTargetInboxesforUser($uid)\n+\t{\n+\t\t$inboxes = [];\n+\n+\t\t$condition = ['uid' => $uid, 'network' => Protocol::ACTIVITYPUB, 'archive' => false, 'pending' => false];\n+\n+\t\tif (!empty($uid)) {\n+\t\t\t$condition['rel'] = [Contact::FOLLOWER, Contact::FRIEND];\n+\t\t}\n+\n+\t\t$contacts = DBA::select('contact', ['notify', 'batch'], $condition);\n+\t\twhile ($contact = DBA::fetch($contacts)) {\n+\t\t\t$contact = defaults($contact, 'batch', $contact['notify']);\n+\t\t\t$inboxes[$contact] = $contact;\n+\t\t}\n+\t\tDBA::close($contacts);\n+\n+\t\treturn $inboxes;\n+\t}\n+\n+\t/**\n+\t * @brief Fetches an array of inboxes for the given item and user\n+\t *\n+\t * @param array $item\n+\t * @param integer $uid User ID\n+\t *\n+\t * @return array with inboxes\n+\t */\n+\tpublic static function fetchTargetInboxes($item, $uid)\n+\t{\n+\t\t$permissions = self::createPermissionBlockForItem($item);\n+\t\tif (empty($permissions)) {\n+\t\t\treturn [];\n+\t\t}\n+\n+\t\t$inboxes = [];\n+\n+\t\tif ($item['gravity'] == GRAVITY_ACTIVITY) {\n+\t\t\t$item_profile = APContact::getByURL($item['author-link']);\n+\t\t} else {\n+\t\t\t$item_profile = APContact::getByURL($item['owner-link']);\n+\t\t}\n+\n+\t\tforeach (['to', 'cc', 'bto', 'bcc'] as $element) {\n+\t\t\tif (empty($permissions[$element])) {\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\n+\t\t\tforeach ($permissions[$element] as $receiver) {\n+\t\t\t\tif ($receiver == $item_profile['followers']) {\n+\t\t\t\t\t$inboxes = self::fetchTargetInboxesforUser($uid);\n+\t\t\t\t} else {\n+\t\t\t\t\t$profile = APContact::getByURL($receiver);\n+\t\t\t\t\tif (!empty($profile)) {\n+\t\t\t\t\t\t$target = defaults($profile, 'sharedinbox', $profile['inbox']);\n+\t\t\t\t\t\t$inboxes[$target] = $target;\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\t\t\t}\n+\t\t}\n+\n+\t\treturn $inboxes;\n+\t}\n+\n+\t/**\n+\t * @brief Returns the activity type of a given item\n+\t *\n+\t * @param array $item\n+\t *\n+\t * @return activity type\n+\t */\n+\tprivate static function getTypeOfItem($item)\n+\t{\n+\t\tif ($item['verb'] == ACTIVITY_POST) {\n+\t\t\tif ($item['created'] == $item['edited']) {\n+\t\t\t\t$type = 'Create';\n+\t\t\t} else {\n+\t\t\t\t$type = 'Update';\n+\t\t\t}\n+\t\t} elseif ($item['verb'] == ACTIVITY_LIKE) {\n+\t\t\t$type = 'Like';\n+\t\t} elseif ($item['verb'] == ACTIVITY_DISLIKE) {\n+\t\t\t$type = 'Dislike';\n+\t\t} elseif ($item['verb'] == ACTIVITY_ATTEND) {\n+\t\t\t$type = 'Accept';\n+\t\t} elseif ($item['verb'] == ACTIVITY_ATTENDNO) {\n+\t\t\t$type = 'Reject';\n+\t\t} elseif ($item['verb'] == ACTIVITY_ATTENDMAYBE) {\n+\t\t\t$type = 'TentativeAccept';\n+\t\t} else {\n+\t\t\t$type = '';\n+\t\t}\n+\n+\t\treturn $type;\n+\t}\n+\n+\t/**\n+\t * @brief Creates an activity array for a given item id\n+\t *\n+\t * @param integer $item_id\n+\t * @param boolean $object_mode Is the activity item is used inside another object?\n+\t *\n+\t * @return array of activity\n+\t */\n+\tpublic static function createActivityFromItem($item_id, $object_mode = false)\n+\t{\n+\t\t$item = Item::selectFirst([], ['id' => $item_id, 'parent-network' => Protocol::NATIVE_SUPPORT]);\n+\n+\t\tif (!DBA::isResult($item)) {\n+\t\t\treturn false;\n+\t\t}\n+\n+\t\t$condition = ['item-uri' => $item['uri'], 'protocol' => Conversation::PARCEL_ACTIVITYPUB];\n+\t\t$conversation = DBA::selectFirst('conversation', ['source'], $condition);\n+\t\tif (DBA::isResult($conversation)) {\n+\t\t\t$data = json_decode($conversation['source']);\n+\t\t\tif (!empty($data)) {\n+\t\t\t\treturn $data;\n+\t\t\t}\n+\t\t}\n+\n+\t\t$type = self::getTypeOfItem($item);\n+\n+\t\tif (!$object_mode) {\n+\t\t\t$data = ['@context' => ActivityPub::CONTEXT];\n+\n+\t\t\tif ($item['deleted'] && ($item['gravity'] == GRAVITY_ACTIVITY)) {\n+\t\t\t\t$type = 'Undo';\n+\t\t\t} elseif ($item['deleted']) {\n+\t\t\t\t$type = 'Delete';\n+\t\t\t}\n+\t\t} else {\n+\t\t\t$data = [];\n+\t\t}\n+\n+\t\t$data['id'] = $item['uri'] . '#' . $type;\n+\t\t$data['type'] = $type;\n+\t\t$data['actor'] = $item['author-link'];\n+\n+\t\t$data['published'] = DateTimeFormat::utc($item[\"created\"].\"+00:00\", DateTimeFormat::ATOM);\n+\n+\t\tif ($item[\"created\"] != $item[\"edited\"]) {\n+\t\t\t$data['updated'] = DateTimeFormat::utc($item[\"edited\"].\"+00:00\", DateTimeFormat::ATOM);\n+\t\t}\n+\n+\t\t$data['context'] = self::fetchContextURLForItem($item);\n+\n+\t\t$data = array_merge($data, self::createPermissionBlockForItem($item));\n+\n+\t\tif (in_array($data['type'], ['Create', 'Update', 'Announce', 'Delete'])) {\n+\t\t\t$data['object'] = self::createNote($item);\n+\t\t} elseif ($data['type'] == 'Undo') {\n+\t\t\t$data['object'] = self::createActivityFromItem($item_id, true);\n+\t\t} else {\n+\t\t\t$data['object'] = $item['thr-parent'];\n+\t\t}\n+\n+\t\t$owner = User::getOwnerDataById($item['uid']);\n+\n+\t\tif (!$object_mode) {\n+\t\t\treturn LDSignature::sign($data, $owner);\n+\t\t} else {\n+\t\t\treturn $data;\n+\t\t}\n+\t}\n+\n+\t/**\n+\t * @brief Creates an object array for a given item id\n+\t *\n+\t * @param integer $item_id\n+\t *\n+\t * @return object array\n+\t */\n+\tpublic static function createObjectFromItemID($item_id)\n+\t{\n+\t\t$item = Item::selectFirst([], ['id' => $item_id, 'parent-network' => Protocol::NATIVE_SUPPORT]);\n+\n+\t\tif (!DBA::isResult($item)) {\n+\t\t\treturn false;\n+\t\t}\n+\n+\t\t$data = ['@context' => ActivityPub::CONTEXT];\n+\t\t$data = array_merge($data, self::createNote($item));\n+\n+\t\treturn $data;\n+\t}\n+\n+\t/**\n+\t * @brief Returns a tag array for a given item array\n+\t *\n+\t * @param array $item\n+\t *\n+\t * @return array of tags\n+\t */\n+\tprivate static function createTagList($item)\n+\t{\n+\t\t$tags = [];\n+\n+\t\t$terms = Term::tagArrayFromItemId($item['id'], TERM_MENTION);\n+\t\tforeach ($terms as $term) {\n+\t\t\t$contact = Contact::getDetailsByURL($term['url']);\n+\t\t\tif (!empty($contact['addr'])) {\n+\t\t\t\t$mention = '@' . $contact['addr'];\n+\t\t\t} else {\n+\t\t\t\t$mention = '@' . $term['url'];\n+\t\t\t}\n+\n+\t\t\t$tags[] = ['type' => 'Mention', 'href' => $term['url'], 'name' => $mention];\n+\t\t}\n+\t\treturn $tags;\n+\t}\n+\n+\t/**\n+\t * @brief Fetches the \"context\" value for a givem item array from the \"conversation\" table\n+\t *\n+\t * @param array $item\n+\t *\n+\t * @return string with context url\n+\t */\n+\tprivate static function fetchContextURLForItem($item)\n+\t{\n+\t\t$conversation = DBA::selectFirst('conversation', ['conversation-href', 'conversation-uri'], ['item-uri' => $item['parent-uri']]);\n+\t\tif (DBA::isResult($conversation) && !empty($conversation['conversation-href'])) {\n+\t\t\t$context_uri = $conversation['conversation-href'];\n+\t\t} elseif (DBA::isResult($conversation) && !empty($conversation['conversation-uri'])) {\n+\t\t\t$context_uri = $conversation['conversation-uri'];\n+\t\t} else {\n+\t\t\t$context_uri = str_replace('/objects/', '/context/', $item['parent-uri']);\n+\t\t}\n+\t\treturn $context_uri;\n+\t}\n+\n+\t/**\n+\t * @brief Creates a note/article object array\n+\t *\n+\t * @param array $item\n+\t *\n+\t * @return object array\n+\t */\n+\tprivate static function createNote($item)\n+\t{\n+\t\tif (!empty($item['title'])) {\n+\t\t\t$type = 'Article';\n+\t\t} else {\n+\t\t\t$type = 'Note';\n+\t\t}\n+\n+\t\tif ($item['deleted']) {\n+\t\t\t$type = 'Tombstone';\n+\t\t}\n+\n+\t\t$data = [];\n+\t\t$data['id'] = $item['uri'];\n+\t\t$data['type'] = $type;\n+\n+\t\tif ($item['deleted']) {\n+\t\t\treturn $data;\n+\t\t}\n+\n+\t\t$data['summary'] = null; // Ignore by now\n+\n+\t\tif ($item['uri'] != $item['thr-parent']) {\n+\t\t\t$data['inReplyTo'] = $item['thr-parent'];\n+\t\t} else {\n+\t\t\t$data['inReplyTo'] = null;\n+\t\t}\n+\n+\t\t$data['diaspora:guid'] = $item['guid'];\n+\t\t$data['published'] = DateTimeFormat::utc($item[\"created\"].\"+00:00\", DateTimeFormat::ATOM);\n+\n+\t\tif ($item[\"created\"] != $item[\"edited\"]) {\n+\t\t\t$data['updated'] = DateTimeFormat::utc($item[\"edited\"].\"+00:00\", DateTimeFormat::ATOM);\n+\t\t}\n+\n+\t\t$data['url'] = $item['plink'];\n+\t\t$data['attributedTo'] = $item['author-link'];\n+\t\t$data['actor'] = $item['author-link'];\n+\t\t$data['sensitive'] = false; // - Query NSFW\n+\t\t$data['context'] = self::fetchContextURLForItem($item);\n+\n+\t\tif (!empty($item['title'])) {\n+\t\t\t$data['name'] = BBCode::convert($item['title'], false, 7);\n+\t\t}\n+\n+\t\t$data['content'] = BBCode::convert($item['body'], false, 7);\n+\t\t$data['source'] = ['content' => $item['body'], 'mediaType' => \"text/bbcode\"];\n+\n+\t\tif (!empty($item['signed_text']) && ($item['uri'] != $item['thr-parent'])) {\n+\t\t\t$data['diaspora:comment'] = $item['signed_text'];\n+\t\t}\n+\n+\t\t$data['attachment'] = []; // @ToDo\n+\t\t$data['tag'] = self::createTagList($item);\n+\t\t$data = array_merge($data, self::createPermissionBlockForItem($item));\n+\n+\t\treturn $data;\n+\t}\n+\n+\t/**\n+\t * @brief Transmits a profile deletion to a given inbox\n+\t *\n+\t * @param integer $uid User ID\n+\t * @param string $inbox Target inbox\n+\t */\n+\tpublic static function transmitProfileDeletion($uid, $inbox)\n+\t{\n+\t\t$owner = User::getOwnerDataById($uid);\n+\t\t$profile = APContact::getByURL($owner['url']);\n+\n+\t\t$data = ['@context' => 'https://www.w3.org/ns/activitystreams',\n+\t\t\t'id' => System::baseUrl() . '/activity/' . System::createGUID(),\n+\t\t\t'type' => 'Delete',\n+\t\t\t'actor' => $owner['url'],\n+\t\t\t'object' => $owner['url'],\n+\t\t\t'published' => DateTimeFormat::utcNow(DateTimeFormat::ATOM),\n+\t\t\t'to' => [ActivityPub::PUBLIC_COLLECTION],\n+\t\t\t'cc' => []];\n+\n+\t\t$signed = LDSignature::sign($data, $owner);\n+\n+\t\tlogger('Deliver profile deletion for user ' . $uid . ' to ' . $inbox .' via ActivityPub', LOGGER_DEBUG);\n+\t\tHTTPSignature::transmit($signed, $inbox, $uid);\n+\t}\n+\n+\t/**\n+\t * @brief Transmits a profile change to a given inbox\n+\t *\n+\t * @param integer $uid User ID\n+\t * @param string $inbox Target inbox\n+\t */\n+\tpublic static function transmitProfileUpdate($uid, $inbox)\n+\t{\n+\t\t$owner = User::getOwnerDataById($uid);\n+\t\t$profile = APContact::getByURL($owner['url']);\n+\n+\t\t$data = ['@context' => 'https://www.w3.org/ns/activitystreams',\n+\t\t\t'id' => System::baseUrl() . '/activity/' . System::createGUID(),\n+\t\t\t'type' => 'Update',\n+\t\t\t'actor' => $owner['url'],\n+\t\t\t'object' => self::profile($uid),\n+\t\t\t'published' => DateTimeFormat::utcNow(DateTimeFormat::ATOM),\n+\t\t\t'to' => [$profile['followers']],\n+\t\t\t'cc' => []];\n+\n+\t\t$signed = LDSignature::sign($data, $owner);\n+\n+\t\tlogger('Deliver profile update for user ' . $uid . ' to ' . $inbox .' via ActivityPub', LOGGER_DEBUG);\n+\t\tHTTPSignature::transmit($signed, $inbox, $uid);\n+\t}\n+\n+\t/**\n+\t * @brief Transmits a given activity to a target\n+\t *\n+\t * @param array $activity\n+\t * @param string $target Target profile\n+\t * @param integer $uid User ID\n+\t */\n+\tpublic static function transmitActivity($activity, $target, $uid)\n+\t{\n+\t\t$profile = APContact::getByURL($target);\n+\n+\t\t$owner = User::getOwnerDataById($uid);\n+\n+\t\t$data = ['@context' => 'https://www.w3.org/ns/activitystreams',\n+\t\t\t'id' => System::baseUrl() . '/activity/' . System::createGUID(),\n+\t\t\t'type' => $activity,\n+\t\t\t'actor' => $owner['url'],\n+\t\t\t'object' => $profile['url'],\n+\t\t\t'to' => $profile['url']];\n+\n+\t\tlogger('Sending activity ' . $activity . ' to ' . $target . ' for user ' . $uid, LOGGER_DEBUG);\n+\n+\t\t$signed = LDSignature::sign($data, $owner);\n+\t\tHTTPSignature::transmit($signed, $profile['inbox'], $uid);\n+\t}\n+\n+\t/**\n+\t * @brief Transmit a message that the contact request had been accepted\n+\t *\n+\t * @param string $target Target profile\n+\t * @param $id\n+\t * @param integer $uid User ID\n+\t */\n+\tpublic static function transmitContactAccept($target, $id, $uid)\n+\t{\n+\t\t$profile = APContact::getByURL($target);\n+\n+\t\t$owner = User::getOwnerDataById($uid);\n+\t\t$data = ['@context' => 'https://www.w3.org/ns/activitystreams',\n+\t\t\t'id' => System::baseUrl() . '/activity/' . System::createGUID(),\n+\t\t\t'type' => 'Accept',\n+\t\t\t'actor' => $owner['url'],\n+\t\t\t'object' => ['id' => $id, 'type' => 'Follow',\n+\t\t\t\t'actor' => $profile['url'],\n+\t\t\t\t'object' => $owner['url']],\n+\t\t\t'to' => $profile['url']];\n+\n+\t\tlogger('Sending accept to ' . $target . ' for user ' . $uid . ' with id ' . $id, LOGGER_DEBUG);\n+\n+\t\t$signed = LDSignature::sign($data, $owner);\n+\t\tHTTPSignature::transmit($signed, $profile['inbox'], $uid);\n+\t}\n+\n+\t/**\n+\t * @brief \n+\t *\n+\t * @param string $target Target profile\n+\t * @param $id\n+\t * @param integer $uid User ID\n+\t */\n+\tpublic static function transmitContactReject($target, $id, $uid)","path":"src/Protocol/ActivityPub/Transmitter.php","position":796,"original_position":796,"commit_id":"1fe55679fc06af36fabfee4758adb6c2dc3241b9","original_commit_id":"1fe55679fc06af36fabfee4758adb6c2dc3241b9","user":{"login":"MrPetovan","id":925415,"node_id":"MDQ6VXNlcjkyNTQxNQ==","avatar_url":"https://avatars1.githubusercontent.com/u/925415?v=4","gravatar_id":"","url":"https://api.github.com/users/MrPetovan","html_url":"https://github.com/MrPetovan","followers_url":"https://api.github.com/users/MrPetovan/followers","following_url":"https://api.github.com/users/MrPetovan/following{/other_user}","gists_url":"https://api.github.com/users/MrPetovan/gists{/gist_id}","starred_url":"https://api.github.com/users/MrPetovan/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/MrPetovan/subscriptions","organizations_url":"https://api.github.com/users/MrPetovan/orgs","repos_url":"https://api.github.com/users/MrPetovan/repos","events_url":"https://api.github.com/users/MrPetovan/events{/privacy}","received_events_url":"https://api.github.com/users/MrPetovan/received_events","type":"User","site_admin":false},"body":"Please rename to `sendContactReject` to avoid repeating \"transmit\" when calling the method.","created_at":"2018-10-03T12:16:53Z","updated_at":"2018-10-03T12:17:14Z","html_url":"https://github.com/friendica/friendica/pull/5815#discussion_r222285454","pull_request_url":"https://api.github.com/repos/friendica/friendica/pulls/5815","author_association":"COLLABORATOR","_links":{"self":{"href":"https://api.github.com/repos/friendica/friendica/pulls/comments/222285454"},"html":{"href":"https://github.com/friendica/friendica/pull/5815#discussion_r222285454"},"pull_request":{"href":"https://api.github.com/repos/friendica/friendica/pulls/5815"}}},"pull_request":{"url":"https://api.github.com/repos/friendica/friendica/pulls/5815","id":219979281,"node_id":"MDExOlB1bGxSZXF1ZXN0MjE5OTc5Mjgx","html_url":"https://github.com/friendica/friendica/pull/5815","diff_url":"https://github.com/friendica/friendica/pull/5815.diff","patch_url":"https://github.com/friendica/friendica/pull/5815.patch","issue_url":"https://api.github.com/repos/friendica/friendica/issues/5815","number":5815,"state":"open","locked":false,"title":"The ActivityPub functionality is now split into 4 files","user":{"login":"annando","id":844208,"node_id":"MDQ6VXNlcjg0NDIwOA==","avatar_url":"https://avatars1.githubusercontent.com/u/844208?v=4","gravatar_id":"","url":"https://api.github.com/users/annando","html_url":"https://github.com/annando","followers_url":"https://api.github.com/users/annando/followers","following_url":"https://api.github.com/users/annando/following{/other_user}","gists_url":"https://api.github.com/users/annando/gists{/gist_id}","starred_url":"https://api.github.com/users/annando/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/annando/subscriptions","organizations_url":"https://api.github.com/users/annando/orgs","repos_url":"https://api.github.com/users/annando/repos","events_url":"https://api.github.com/users/annando/events{/privacy}","received_events_url":"https://api.github.com/users/annando/received_events","type":"User","site_admin":false},"body":"There are now 4 different files for the ActivityPub functionality:\r\n- ActivityPub.php: General functions that are used from several AP clas.ses\r\n- ActivityPub/Transmitter.php: Everything that is used to generate and transmit AP content.\r\n- ActivityPub/Receiver.php: Receives and checks incoming AP content, dispatches the actions to the processor.\r\n- ActivityPub/Processor.php: Is called from the receiver and does the whole job.\r\n\r\nBackground for this is to keep the files small and to separate functionality.\r\n\r\nThe next steps (in future PR) will be the perfection of the transmitter (means attachments and the \"sensitive\" flag) as well as hardening the receiver by using the JSON-LD parser to compact the JSON object to a more general form. This should improve the compatibility with several implementations.","created_at":"2018-10-03T10:01:21Z","updated_at":"2018-10-03T12:17:14Z","closed_at":null,"merged_at":null,"merge_commit_sha":"c13ac2ba37b825082a61bfbe6d323ae9e4c34d31","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/friendica/friendica/pulls/5815/commits","review_comments_url":"https://api.github.com/repos/friendica/friendica/pulls/5815/comments","review_comment_url":"https://api.github.com/repos/friendica/friendica/pulls/comments{/number}","comments_url":"https://api.github.com/repos/friendica/friendica/issues/5815/comments","statuses_url":"https://api.github.com/repos/friendica/friendica/statuses/1fe55679fc06af36fabfee4758adb6c2dc3241b9","head":{"label":"annando:ap2","ref":"ap2","sha":"1fe55679fc06af36fabfee4758adb6c2dc3241b9","user":{"login":"annando","id":844208,"node_id":"MDQ6VXNlcjg0NDIwOA==","avatar_url":"https://avatars1.githubusercontent.com/u/844208?v=4","gravatar_id":"","url":"https://api.github.com/users/annando","html_url":"https://github.com/annando","followers_url":"https://api.github.com/users/annando/followers","following_url":"https://api.github.com/users/annando/following{/other_user}","gists_url":"https://api.github.com/users/annando/gists{/gist_id}","starred_url":"https://api.github.com/users/annando/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/annando/subscriptions","organizations_url":"https://api.github.com/users/annando/orgs","repos_url":"https://api.github.com/users/annando/repos","events_url":"https://api.github.com/users/annando/events{/privacy}","received_events_url":"https://api.github.com/users/annando/received_events","type":"User","site_admin":false},"repo":{"id":3302872,"node_id":"MDEwOlJlcG9zaXRvcnkzMzAyODcy","name":"friendica","full_name":"annando/friendica","private":false,"owner":{"login":"annando","id":844208,"node_id":"MDQ6VXNlcjg0NDIwOA==","avatar_url":"https://avatars1.githubusercontent.com/u/844208?v=4","gravatar_id":"","url":"https://api.github.com/users/annando","html_url":"https://github.com/annando","followers_url":"https://api.github.com/users/annando/followers","following_url":"https://api.github.com/users/annando/following{/other_user}","gists_url":"https://api.github.com/users/annando/gists{/gist_id}","starred_url":"https://api.github.com/users/annando/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/annando/subscriptions","organizations_url":"https://api.github.com/users/annando/orgs","repos_url":"https://api.github.com/users/annando/repos","events_url":"https://api.github.com/users/annando/events{/privacy}","received_events_url":"https://api.github.com/users/annando/received_events","type":"User","site_admin":false},"html_url":"https://github.com/annando/friendica","description":"Friendica Communications Platform - MIT license","fork":true,"url":"https://api.github.com/repos/annando/friendica","forks_url":"https://api.github.com/repos/annando/friendica/forks","keys_url":"https://api.github.com/repos/annando/friendica/keys{/key_id}","collaborators_url":"https://api.github.com/repos/annando/friendica/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/annando/friendica/teams","hooks_url":"https://api.github.com/repos/annando/friendica/hooks","issue_events_url":"https://api.github.com/repos/annando/friendica/issues/events{/number}","events_url":"https://api.github.com/repos/annando/friendica/events","assignees_url":"https://api.github.com/repos/annando/friendica/assignees{/user}","branches_url":"https://api.github.com/repos/annando/friendica/branches{/branch}","tags_url":"https://api.github.com/repos/annando/friendica/tags","blobs_url":"https://api.github.com/repos/annando/friendica/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/annando/friendica/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/annando/friendica/git/refs{/sha}","trees_url":"https://api.github.com/repos/annando/friendica/git/trees{/sha}","statuses_url":"https://api.github.com/repos/annando/friendica/statuses/{sha}","languages_url":"https://api.github.com/repos/annando/friendica/languages","stargazers_url":"https://api.github.com/repos/annando/friendica/stargazers","contributors_url":"https://api.github.com/repos/annando/friendica/contributors","subscribers_url":"https://api.github.com/repos/annando/friendica/subscribers","subscription_url":"https://api.github.com/repos/annando/friendica/subscription","commits_url":"https://api.github.com/repos/annando/friendica/commits{/sha}","git_commits_url":"https://api.github.com/repos/annando/friendica/git/commits{/sha}","comments_url":"https://api.github.com/repos/annando/friendica/comments{/number}","issue_comment_url":"https://api.github.com/repos/annando/friendica/issues/comments{/number}","contents_url":"https://api.github.com/repos/annando/friendica/contents/{+path}","compare_url":"https://api.github.com/repos/annando/friendica/compare/{base}...{head}","merges_url":"https://api.github.com/repos/annando/friendica/merges","archive_url":"https://api.github.com/repos/annando/friendica/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/annando/friendica/downloads","issues_url":"https://api.github.com/repos/annando/friendica/issues{/number}","pulls_url":"https://api.github.com/repos/annando/friendica/pulls{/number}","milestones_url":"https://api.github.com/repos/annando/friendica/milestones{/number}","notifications_url":"https://api.github.com/repos/annando/friendica/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/annando/friendica/labels{/name}","releases_url":"https://api.github.com/repos/annando/friendica/releases{/id}","deployments_url":"https://api.github.com/repos/annando/friendica/deployments","created_at":"2012-01-30T06:45:26Z","updated_at":"2018-10-03T05:59:42Z","pushed_at":"2018-10-03T09:53:26Z","git_url":"git://github.com/annando/friendica.git","ssh_url":"git@github.com:annando/friendica.git","clone_url":"https://github.com/annando/friendica.git","svn_url":"https://github.com/annando/friendica","homepage":"http://friendica.com","size":106531,"stargazers_count":3,"watchers_count":3,"language":"PHP","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":0,"mirror_url":null,"archived":false,"open_issues_count":0,"license":{"key":"other","name":"NOASSERTION","spdx_id":"NOASSERTION","url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":0,"open_issues":0,"watchers":3,"default_branch":"develop"}},"base":{"label":"friendica:develop","ref":"develop","sha":"ec898ecd2a6a79e1e8a6a9dda94e90393eb08487","user":{"login":"friendica","id":1176581,"node_id":"MDQ6VXNlcjExNzY1ODE=","avatar_url":"https://avatars1.githubusercontent.com/u/1176581?v=4","gravatar_id":"","url":"https://api.github.com/users/friendica","html_url":"https://github.com/friendica","followers_url":"https://api.github.com/users/friendica/followers","following_url":"https://api.github.com/users/friendica/following{/other_user}","gists_url":"https://api.github.com/users/friendica/gists{/gist_id}","starred_url":"https://api.github.com/users/friendica/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/friendica/subscriptions","organizations_url":"https://api.github.com/users/friendica/orgs","repos_url":"https://api.github.com/users/friendica/repos","events_url":"https://api.github.com/users/friendica/events{/privacy}","received_events_url":"https://api.github.com/users/friendica/received_events","type":"User","site_admin":false},"repo":{"id":2722709,"node_id":"MDEwOlJlcG9zaXRvcnkyNzIyNzA5","name":"friendica","full_name":"friendica/friendica","private":false,"owner":{"login":"friendica","id":1176581,"node_id":"MDQ6VXNlcjExNzY1ODE=","avatar_url":"https://avatars1.githubusercontent.com/u/1176581?v=4","gravatar_id":"","url":"https://api.github.com/users/friendica","html_url":"https://github.com/friendica","followers_url":"https://api.github.com/users/friendica/followers","following_url":"https://api.github.com/users/friendica/following{/other_user}","gists_url":"https://api.github.com/users/friendica/gists{/gist_id}","starred_url":"https://api.github.com/users/friendica/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/friendica/subscriptions","organizations_url":"https://api.github.com/users/friendica/orgs","repos_url":"https://api.github.com/users/friendica/repos","events_url":"https://api.github.com/users/friendica/events{/privacy}","received_events_url":"https://api.github.com/users/friendica/received_events","type":"User","site_admin":false},"html_url":"https://github.com/friendica/friendica","description":"Friendica Communications Platform","fork":true,"url":"https://api.github.com/repos/friendica/friendica","forks_url":"https://api.github.com/repos/friendica/friendica/forks","keys_url":"https://api.github.com/repos/friendica/friendica/keys{/key_id}","collaborators_url":"https://api.github.com/repos/friendica/friendica/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/friendica/friendica/teams","hooks_url":"https://api.github.com/repos/friendica/friendica/hooks","issue_events_url":"https://api.github.com/repos/friendica/friendica/issues/events{/number}","events_url":"https://api.github.com/repos/friendica/friendica/events","assignees_url":"https://api.github.com/repos/friendica/friendica/assignees{/user}","branches_url":"https://api.github.com/repos/friendica/friendica/branches{/branch}","tags_url":"https://api.github.com/repos/friendica/friendica/tags","blobs_url":"https://api.github.com/repos/friendica/friendica/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/friendica/friendica/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/friendica/friendica/git/refs{/sha}","trees_url":"https://api.github.com/repos/friendica/friendica/git/trees{/sha}","statuses_url":"https://api.github.com/repos/friendica/friendica/statuses/{sha}","languages_url":"https://api.github.com/repos/friendica/friendica/languages","stargazers_url":"https://api.github.com/repos/friendica/friendica/stargazers","contributors_url":"https://api.github.com/repos/friendica/friendica/contributors","subscribers_url":"https://api.github.com/repos/friendica/friendica/subscribers","subscription_url":"https://api.github.com/repos/friendica/friendica/subscription","commits_url":"https://api.github.com/repos/friendica/friendica/commits{/sha}","git_commits_url":"https://api.github.com/repos/friendica/friendica/git/commits{/sha}","comments_url":"https://api.github.com/repos/friendica/friendica/comments{/number}","issue_comment_url":"https://api.github.com/repos/friendica/friendica/issues/comments{/number}","contents_url":"https://api.github.com/repos/friendica/friendica/contents/{+path}","compare_url":"https://api.github.com/repos/friendica/friendica/compare/{base}...{head}","merges_url":"https://api.github.com/repos/friendica/friendica/merges","archive_url":"https://api.github.com/repos/friendica/friendica/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/friendica/friendica/downloads","issues_url":"https://api.github.com/repos/friendica/friendica/issues{/number}","pulls_url":"https://api.github.com/repos/friendica/friendica/pulls{/number}","milestones_url":"https://api.github.com/repos/friendica/friendica/milestones{/number}","notifications_url":"https://api.github.com/repos/friendica/friendica/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/friendica/friendica/labels{/name}","releases_url":"https://api.github.com/repos/friendica/friendica/releases{/id}","deployments_url":"https://api.github.com/repos/friendica/friendica/deployments","created_at":"2011-11-06T22:19:41Z","updated_at":"2018-10-03T10:58:18Z","pushed_at":"2018-10-03T11:12:34Z","git_url":"git://github.com/friendica/friendica.git","ssh_url":"git@github.com:friendica/friendica.git","clone_url":"https://github.com/friendica/friendica.git","svn_url":"https://github.com/friendica/friendica","homepage":"https://friendi.ca","size":105856,"stargazers_count":639,"watchers_count":639,"language":"PHP","has_issues":true,"has_projects":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":210,"mirror_url":null,"archived":false,"open_issues_count":404,"license":{"key":"other","name":"NOASSERTION","spdx_id":"NOASSERTION","url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":210,"open_issues":404,"watchers":639,"default_branch":"develop"}},"_links":{"self":{"href":"https://api.github.com/repos/friendica/friendica/pulls/5815"},"html":{"href":"https://github.com/friendica/friendica/pull/5815"},"issue":{"href":"https://api.github.com/repos/friendica/friendica/issues/5815"},"comments":{"href":"https://api.github.com/repos/friendica/friendica/issues/5815/comments"},"review_comments":{"href":"https://api.github.com/repos/friendica/friendica/pulls/5815/comments"},"review_comment":{"href":"https://api.github.com/repos/friendica/friendica/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/friendica/friendica/pulls/5815/commits"},"statuses":{"href":"https://api.github.com/repos/friendica/friendica/statuses/1fe55679fc06af36fabfee4758adb6c2dc3241b9"}},"author_association":"COLLABORATOR"}}
{"actor":{"display_login":"MrPetovan"}}