X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=src%2FProtocol%2FDiaspora.php;h=9e074e8a9a1c3a0b820938151fe7d18ac9ce6dae;hb=775324bd07cb0f0a7f0994ee5ec25bd691ca929f;hp=3a7e73cee56b56bf49d841e0e1e34a0183632331;hpb=41f781c52af32b8748368b5c51f68b31fe0b7164;p=friendica.git diff --git a/src/Protocol/Diaspora.php b/src/Protocol/Diaspora.php index 3a7e73cee5..9e074e8a9a 100644 --- a/src/Protocol/Diaspora.php +++ b/src/Protocol/Diaspora.php @@ -27,6 +27,8 @@ use Friendica\Model\Conversation; use Friendica\Model\GContact; use Friendica\Model\Group; use Friendica\Model\Item; +use Friendica\Model\ItemDeliveryData; +use Friendica\Model\Mail; use Friendica\Model\Profile; use Friendica\Model\User; use Friendica\Network\Probe; @@ -36,6 +38,7 @@ use Friendica\Util\Map; use Friendica\Util\Network; use Friendica\Util\Strings; use Friendica\Util\XML; +use Friendica\Worker\Delivery; use SimpleXMLElement; /** @@ -44,6 +47,36 @@ use SimpleXMLElement; */ class Diaspora { + /** + * Mark the relay contact of the given contact for archival + * This is called whenever there is a communication issue with the server. + * It avoids sending stuff to servers who don't exist anymore. + * The relay contact is a technical contact entry that exists once per server. + * + * @param array $contact of the relay contact + */ + public static function markRelayForArchival(array $contact) + { + if (!empty($contact['contact-type']) && ($contact['contact-type'] == Contact::TYPE_RELAY)) { + // This is already the relay contact, we don't need to fetch it + $relay_contact = $contact; + } elseif (empty($contact['baseurl'])) { + if (!empty($contact['batch'])) { + $condition = ['uid' => 0, 'network' => Protocol::FEDERATED, 'batch' => $contact['batch'], 'contact-type' => Contact::TYPE_RELAY]; + $relay_contact = DBA::selectFirst('contact', [], $condition); + } else { + return; + } + } else { + $relay_contact = self::getRelayContact($contact['baseurl'], []); + } + + if (!empty($relay_contact)) { + Logger::info('Relay contact will be marked for archival', ['id' => $relay_contact['id'], 'url' => $relay_contact['url']]); + Contact::markForArchival($relay_contact); + } + } + /** * @brief Return a list of relay servers * @@ -138,13 +171,12 @@ class Diaspora * @brief Return a contact for a given server address or creates a dummy entry * * @param string $server_url The url of the server + * @param array $fields Fieldlist * @return array with the contact * @throws \Exception */ - private static function getRelayContact($server_url) + private static function getRelayContact(string $server_url, array $fields = ['batch', 'id', 'name', 'network', 'protocol', 'archive', 'blocked']) { - $fields = ['batch', 'id', 'name', 'network', 'archive', 'blocked']; - // Fetch the relay contact $condition = ['uid' => 0, 'nurl' => Strings::normaliseLink($server_url), 'contact-type' => Contact::TYPE_RELAY]; @@ -178,22 +210,28 @@ class Diaspora public static function setRelayContact($server_url, array $network_fields = []) { $fields = ['created' => DateTimeFormat::utcNow(), - 'name' => 'relay', 'nick' => 'relay', - 'url' => $server_url, 'network' => Protocol::DIASPORA, + 'name' => 'relay', 'nick' => 'relay', 'url' => $server_url, + 'nurl' => Strings::normaliseLink($server_url), + 'network' => Protocol::DIASPORA, 'uid' => 0, 'batch' => $server_url . '/receive/public', 'rel' => Contact::FOLLOWER, 'blocked' => false, - 'pending' => false, 'writable' => true]; + 'pending' => false, 'writable' => true, + 'baseurl' => $server_url, 'contact-type' => Contact::TYPE_RELAY]; $fields = array_merge($fields, $network_fields); - $condition = ['uid' => 0, 'nurl' => Strings::normaliseLink($server_url), - 'contact-type' => Contact::TYPE_RELAY]; - - if (DBA::exists('contact', $condition)) { + $condition = ['uid' => 0, 'nurl' => Strings::normaliseLink($server_url)]; + $old = DBA::selectFirst('contact', [], $condition); + if (DBA::isResult($old)) { unset($fields['created']); - } + $condition = ['id' => $old['id']]; - DBA::update('contact', $fields, $condition, true); + Logger::info('Update relay contact', ['fields' => $fields, 'condition' => $condition]); + DBA::update('contact', $fields, $condition, $old); + } else { + Logger::info('Create relay contact', ['fields' => $fields]); + Contact::insert($fields); + } } /** @@ -211,11 +249,11 @@ class Diaspora */ public static function participantsForThread($thread, array $contacts) { - $r = DBA::p("SELECT `contact`.`batch`, `contact`.`id`, `contact`.`name`, `contact`.`network`, + $r = DBA::p("SELECT `contact`.`batch`, `contact`.`id`, `contact`.`name`, `contact`.`network`, `contact`.`protocol`, `fcontact`.`batch` AS `fbatch`, `fcontact`.`network` AS `fnetwork` FROM `participation` INNER JOIN `contact` ON `contact`.`id` = `participation`.`cid` INNER JOIN `fcontact` ON `fcontact`.`id` = `participation`.`fid` - WHERE `participation`.`iid` = ?", $thread); + WHERE `participation`.`iid` = ? AND NOT `contact`.`archive`", $thread); while ($contact = DBA::fetch($r)) { if (!empty($contact['fnetwork'])) { @@ -223,6 +261,10 @@ class Diaspora } unset($contact['fnetwork']); + if (empty($contact['protocol'])) { + $contact['protocol'] = $contact['network']; + } + if (empty($contact['batch']) && !empty($contact['fbatch'])) { $contact['batch'] = $contact['fbatch']; } @@ -930,31 +972,41 @@ class Diaspora * @brief Fetches data for a given handle * * @param string $handle The handle + * @param boolean $update true = always update, false = never update, null = update when not found or outdated * * @return array the queried data * @throws \Friendica\Network\HTTPException\InternalServerErrorException * @throws \ImagickException */ - public static function personByHandle($handle) + public static function personByHandle($handle, $update = null) { - $update = false; - $person = DBA::selectFirst('fcontact', [], ['network' => Protocol::DIASPORA, 'addr' => $handle]); + if (!DBA::isResult($person)) { + $urls = [$handle, str_replace('http://', 'https://', $handle), Strings::normaliseLink($handle)]; + $person = DBA::selectFirst('fcontact', [], ['network' => Protocol::DIASPORA, 'url' => $urls]); + } + if (DBA::isResult($person)) { Logger::debug("In cache " . print_r($person, true)); - // update record occasionally so it doesn't get stale - $d = strtotime($person["updated"]." +00:00"); - if ($d < strtotime("now - 14 days")) { - $update = true; - } + if (is_null($update)) { + // update record occasionally so it doesn't get stale + $d = strtotime($person["updated"]." +00:00"); + if ($d < strtotime("now - 14 days")) { + $update = true; + } - if ($person["guid"] == "") { - $update = true; + if ($person["guid"] == "") { + $update = true; + } } + } elseif (is_null($update)) { + $update = !DBA::isResult($person); + } else { + $person = []; } - if (!DBA::isResult($person) || $update) { + if ($update) { Logger::log("create or refresh", Logger::DEBUG); $r = Probe::uri($handle, Protocol::DIASPORA); @@ -963,12 +1015,7 @@ class Diaspora if ($r && ($r["network"] === Protocol::DIASPORA)) { self::updateFContact($r); - // Fetch the updated or added contact - $person = DBA::selectFirst('fcontact', [], ['network' => Protocol::DIASPORA, 'addr' => $handle]); - if (!DBA::isResult($person)) { - $person = $r; - $person['id'] = 0; - } + $person = self::personByHandle($handle, false); } } @@ -1105,6 +1152,20 @@ class Diaspora return $contact; } + /** + * Checks if the given contact url does support ActivityPub + * + * @param string $url profile url + * @param boolean $update true = always update, false = never update, null = update when not found or outdated + * @return boolean + * @throws \Friendica\Network\HTTPException\InternalServerErrorException + * @throws \ImagickException + */ + public static function isSupportedByContactUrl($url, $update = null) + { + return !empty(self::personByHandle($url, $update)); + } + /** * @brief Check if posting is allowed for this contact * @@ -1402,6 +1463,39 @@ class Diaspora return $msg; } + /** + * @brief Fetches an item with a given URL + * + * @param string $url the message url + * + * @return int the message id of the stored message or false + * @throws \Friendica\Network\HTTPException\InternalServerErrorException + * @throws \ImagickException + */ + public static function fetchByURL($url, $uid = 0) + { + // Check for Diaspora (and Friendica) typical paths + if (!preg_match("=(https?://.+)/(?:posts|display)/([a-zA-Z0-9-_@.:%]+[a-zA-Z0-9])=i", $url, $matches)) { + return false; + } + + $guid = urldecode($matches[2]); + + $item = Item::selectFirst(['id'], ['guid' => $guid, 'uid' => $uid]); + if (DBA::isResult($item)) { + return $item['id']; + } + + self::storeByGuid($guid, $matches[1], $uid); + + $item = Item::selectFirst(['id'], ['guid' => $guid, 'uid' => $uid]); + if (DBA::isResult($item)) { + return $item['id']; + } else { + return false; + } + } + /** * @brief Fetches the item record of a given guid * @@ -1841,14 +1935,7 @@ class Diaspora $person = self::personByHandle($msg_author); - DBA::lock('mail'); - - if (DBA::exists('mail', ['guid' => $msg_guid, 'uid' => $importer["uid"]])) { - Logger::log("duplicate message already delivered.", Logger::DEBUG); - return false; - } - - DBA::insert('mail', [ + return Mail::insert([ 'uid' => $importer['uid'], 'guid' => $msg_guid, 'convid' => $conversation['id'], @@ -1858,36 +1945,10 @@ class Diaspora 'contact-id' => $contact['id'], 'title' => $subject, 'body' => $body, - 'seen' => 0, - 'reply' => 0, 'uri' => $message_uri, 'parent-uri' => $author . ':' . $guid, 'created' => $msg_created_at ]); - - $message_id = DBA::lastInsertId(); - - DBA::unlock(); - - DBA::update('conv', ['updated' => DateTimeFormat::utcNow()], ['id' => $conversation["id"]]); - - notification([ - "type" => NOTIFY_MAIL, - "notify_flags" => $importer["notify-flags"], - "language" => $importer["language"], - "to_name" => $importer["username"], - "to_email" => $importer["email"], - "uid" => $importer["uid"], - "item" => ["id" => $message_id, "title" => $subject, "subject" => $subject, "body" => $body], - "parent" => $conversation["id"], - "source_name" => $person["name"], - "source_link" => $person["url"], - "source_photo" => $person["photo"], - "verb" => ACTIVITY_POST, - "otype" => "mail" - ]); - - return true; } /** @@ -2105,14 +2166,7 @@ class Diaspora $body = self::replacePeopleGuid($body, $person["url"]); - DBA::lock('mail'); - - if (DBA::exists('mail', ['guid' => $guid, 'uid' => $importer["uid"]])) { - Logger::log("duplicate message already delivered.", Logger::DEBUG); - return false; - } - - DBA::insert('mail', [ + return Mail::insert([ 'uid' => $importer['uid'], 'guid' => $guid, 'convid' => $conversation['id'], @@ -2122,36 +2176,11 @@ class Diaspora 'contact-id' => $contact['id'], 'title' => $conversation['subject'], 'body' => $body, - 'seen' => 0, 'reply' => 1, 'uri' => $message_uri, 'parent-uri' => $author.":".$conversation['guid'], 'created' => $created_at ]); - - $message_id = DBA::lastInsertId(); - - DBA::unlock(); - - DBA::update('conv', ['updated' => DateTimeFormat::utcNow()], ['id' => $conversation["id"]]); - - notification([ - "type" => NOTIFY_MAIL, - "notify_flags" => $importer["notify-flags"], - "language" => $importer["language"], - "to_name" => $importer["username"], - "to_email" => $importer["email"], - "uid" => $importer["uid"], - "item" => ["id" => $message_id, "title" => $conversation["subject"], "subject" => $conversation["subject"], "body" => $body], - "parent" => $conversation["id"], - "source_name" => $person["name"], - "source_link" => $person["url"], - "source_photo" => $person["photo"], - "verb" => ACTIVITY_POST, - "otype" => "mail" - ]); - - return true; } /** @@ -2207,13 +2236,11 @@ class Diaspora if ($comment['id'] == $comment['parent']) { continue; } - if ($comment['verb'] == ACTIVITY_POST) { - $cmd = $comment['self'] ? 'comment-new' : 'comment-import'; - } else { - $cmd = $comment['self'] ? 'like' : 'comment-import'; + + Logger::info('Deliver participation', ['item' => $comment['id'], 'contact' => $contact_id]); + if (Worker::add(PRIORITY_HIGH, 'Delivery', Delivery::POST, $comment['id'], $contact_id)) { + ItemDeliveryData::incrementQueueCount($comment['id'], 1); } - Logger::log("Send ".$cmd." for item ".$comment['id']." to contact ".$contact_id, Logger::DEBUG); - Worker::add(PRIORITY_HIGH, 'Delivery', $cmd, $comment['id'], $contact_id); } DBA::close($comments); @@ -2321,8 +2348,8 @@ class Diaspora $fields = ['name' => $name, 'location' => $location, 'name-date' => DateTimeFormat::utcNow(), 'about' => $about, 'gender' => $gender, - 'addr' => $author, 'nick' => $nick, - 'keywords' => $keywords]; + 'addr' => $author, 'nick' => $nick, 'keywords' => $keywords, + 'unsearchable' => !$searchable, 'sensitive' => $nsfw]; if (!empty($birthday)) { $fields['bd'] = $birthday; @@ -2330,6 +2357,8 @@ class Diaspora DBA::update('contact', $fields, ['id' => $contact['id']]); + // @todo Update the public contact, then update the gcontact from that + $gcontact = ["url" => $contact["url"], "network" => Protocol::DIASPORA, "generation" => 2, "photo" => $image_url, "name" => $name, "location" => $location, "about" => $about, "birthday" => $birthday, "gender" => $gender, @@ -3170,13 +3199,12 @@ class Diaspora * @param string $envelope The message that is to be transmitted * @param bool $public_batch Is it a public post? * @param string $guid message guid - * @param bool $no_defer Don't defer a failing delivery * * @return int Result of the transmission * @throws \Friendica\Network\HTTPException\InternalServerErrorException * @throws \ImagickException */ - public static function transmit(array $owner, array $contact, $envelope, $public_batch, $guid = "", $no_defer = false) + private static function transmit(array $owner, array $contact, $envelope, $public_batch, $guid = "") { $enabled = intval(Config::get("system", "diaspora_enabled")); if (!$enabled) { @@ -3185,8 +3213,6 @@ class Diaspora $logid = Strings::getRandomHex(4); - $dest_url = ($public_batch ? $contact["batch"] : $contact["notify"]); - // We always try to use the data from the fcontact table. // This is important for transmitting data to Friendica servers. if (!empty($contact['addr'])) { @@ -3196,6 +3222,10 @@ class Diaspora } } + if (empty($dest_url)) { + $dest_url = ($public_batch ? $contact["batch"] : $contact["notify"]); + } + if (!$dest_url) { Logger::log("no url for contact: ".$contact["id"]." batch mode =".$public_batch); return 0; @@ -3215,20 +3245,6 @@ class Diaspora Logger::log("transmit: ".$logid."-".$guid." to ".$dest_url." returns: ".$return_code); - if (!$return_code || (($return_code == 503) && (stristr($postResult->getHeader(), "retry-after")))) { - if (!$no_defer && !empty($contact['contact-type']) && ($contact['contact-type'] != Contact::TYPE_RELAY)) { - Logger::info('defer message', ['log' => $logid, 'guid' => $guid, 'destination' => $dest_url]); - // defer message for redelivery - Worker::defer(); - } - - // The message could not be delivered. We mark the contact as "dead" - Contact::markForArchival($contact); - } elseif (($return_code >= 200) && ($return_code <= 299)) { - // We successfully delivered a message, the contact is alive - Contact::unmarkForArchival($contact); - } - return $return_code ? $return_code : -1; } @@ -3257,13 +3273,12 @@ class Diaspora * @param array $message The message data * @param bool $public_batch Is it a public post? * @param string $guid message guid - * @param bool $no_defer Don't defer a failing delivery * * @return int Result of the transmission * @throws \Friendica\Network\HTTPException\InternalServerErrorException * @throws \ImagickException */ - private static function buildAndTransmit(array $owner, array $contact, $type, $message, $public_batch = false, $guid = "", $no_defer = false) + private static function buildAndTransmit(array $owner, array $contact, $type, $message, $public_batch = false, $guid = "") { $msg = self::buildPostXml($type, $message); @@ -3277,7 +3292,7 @@ class Diaspora $envelope = self::buildMessage($msg, $owner, $contact, $owner['uprvkey'], $contact['pubkey'], $public_batch); - $return_code = self::transmit($owner, $contact, $envelope, $public_batch, $guid, $no_defer); + $return_code = self::transmit($owner, $contact, $envelope, $public_batch, $guid); Logger::log("guid: ".$guid." result ".$return_code, Logger::DEBUG); @@ -3622,6 +3637,7 @@ class Diaspora $public = ($item["private"] ? "false" : "true"); $created = DateTimeFormat::utc($item["created"], DateTimeFormat::ATOM); + $edited = DateTimeFormat::utc($item["edited"] ?? $item["created"], DateTimeFormat::ATOM); // Detect a share element and do a reshare if (!$item['private'] && ($ret = self::isReshare($item["body"]))) { @@ -3676,6 +3692,7 @@ class Diaspora $message = ["author" => $myaddr, "guid" => $item["guid"], "created_at" => $created, + "edited_at" => $edited, "public" => $public, "text" => $body, "provider_display_name" => $item["app"], @@ -3833,7 +3850,7 @@ class Diaspora return $result; } - $toplevel_item = Item::selectFirst(['guid', 'author-link'], ['id' => $item["parent"], 'parent' => $item["parent"]]); + $toplevel_item = Item::selectFirst(['guid', 'author-id', 'author-link'], ['id' => $item["parent"], 'parent' => $item["parent"]]); if (!DBA::isResult($toplevel_item)) { Logger::error('Missing parent conversation item', ['parent' => $item["parent"]]); return false; @@ -3841,12 +3858,18 @@ class Diaspora $thread_parent_item = $toplevel_item; if ($item['thr-parent'] != $item['parent-uri']) { - $thread_parent_item = Item::selectFirst(['guid', 'author-link'], ['uri' => $item['thr-parent'], 'uid' => $item['uid']]); + $thread_parent_item = Item::selectFirst(['guid', 'author-id', 'author-link'], ['uri' => $item['thr-parent'], 'uid' => $item['uid']]); } $body = $item["body"]; - if ((empty($item['uid']) || !Feature::isEnabled($item['uid'], 'explicit_mentions')) + // The replied to autor mention is prepended for clarity if: + // - Item replied isn't yours + // - Item is public or explicit mentions are disabled + // - Implicit mentions are enabled + if ( + $item['author-id'] != $thread_parent_item['author-id'] + && (empty($item['uid']) || !Feature::isEnabled($item['uid'], 'explicit_mentions')) && !Config::get('system', 'disable_implicit_mentions') ) { $body = self::prependParentAuthorMention($body, $thread_parent_item['author-link']); @@ -3854,11 +3877,13 @@ class Diaspora $text = html_entity_decode(BBCode::toMarkdown($body)); $created = DateTimeFormat::utc($item["created"], DateTimeFormat::ATOM); + $edited = DateTimeFormat::utc($item["edited"], DateTimeFormat::ATOM); $comment = [ "author" => self::myHandle($owner), "guid" => $item["guid"], "created_at" => $created, + "edited_at" => $edited, "parent_guid" => $toplevel_item["guid"], "text" => $text, "author_signature" => "" @@ -3894,7 +3919,7 @@ class Diaspora } elseif (in_array($item["verb"], [ACTIVITY_LIKE, ACTIVITY_DISLIKE])) { $message = self::constructLike($item, $owner); $type = "like"; - } elseif (!in_array($item["verb"], [ACTIVITY_FOLLOW])) { + } elseif (!in_array($item["verb"], [ACTIVITY_FOLLOW, ACTIVITY_TAG])) { $message = self::constructComment($item, $owner); $type = "comment"; } @@ -4271,9 +4296,10 @@ class Diaspora $message = self::createProfileData($uid); + // @ToDo Split this into single worker jobs foreach ($recips as $recip) { Logger::log("Send updated profile data for user ".$uid." to contact ".$recip["id"], Logger::DEBUG); - self::buildAndTransmit($owner, $recip, "profile", $message, false, '', true); + self::buildAndTransmit($owner, $recip, "profile", $message); } }