diff --git a/include/Scrape.php b/include/Scrape.php index ce18bb103..90fb1a9e3 100644 --- a/include/Scrape.php +++ b/include/Scrape.php @@ -335,7 +335,7 @@ function scrape_feed($url) { define ( 'PROBE_NORMAL', 0); define ( 'PROBE_DIASPORA', 1); -function probe_url($url, $mode = PROBE_NORMAL) { +function probe_url($url, $mode = PROBE_NORMAL, $level = 1) { require_once('include/email.php'); $result = array(); @@ -670,6 +670,7 @@ function probe_url($url, $mode = PROBE_NORMAL) { $vcard['fn'] = trim(unxmlify($author->get_email())); if(strpos($vcard['fn'],'@') !== false) $vcard['fn'] = substr($vcard['fn'],0,strpos($vcard['fn'],'@')); + $email = unxmlify($author->get_email()); if(! $profile && $author->get_link()) $profile = trim(unxmlify($author->get_link())); @@ -681,6 +682,15 @@ function probe_url($url, $mode = PROBE_NORMAL) { $vcard['photo'] = $elems['link'][0]['attribs']['']['href']; } } + // Fetch fullname via poco:displayName + $pocotags = $feed->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'author'); + if ($pocotags) { + $elems = $pocotags[0]['child']['http://portablecontacts.net/spec/1.0']; + if (isset($elems["displayName"])) + $vcard['fn'] = $elems["displayName"][0]["data"]; + if (isset($elems["preferredUsername"])) + $vcard['nick'] = $elems["preferredUsername"][0]["data"]; + } } else { $item = $feed->get_item(0); @@ -757,18 +767,18 @@ function probe_url($url, $mode = PROBE_NORMAL) { $vcard['fn'] = $url; if (($notify != "") AND ($poll != "")) { - $baseurl = matching($notify, $poll); + $baseurl = matching(normalise_link($notify), normalise_link($poll)); - $baseurl2 = matching($baseurl, $profile); + $baseurl2 = matching($baseurl, normalise_link($profile)); if ($baseurl2 != "") $baseurl = $baseurl2; } if (($baseurl == "") AND ($notify != "")) - $baseurl = matching($profile, $notify); + $baseurl = matching(normalise_link($profile), normalise_link($notify)); if (($baseurl == "") AND ($poll != "")) - $baseurl = matching($profile, $poll); + $baseurl = matching(normalise_link($profile), normalise_link($poll)); $baseurl = rtrim($baseurl, "/"); @@ -794,13 +804,23 @@ function probe_url($url, $mode = PROBE_NORMAL) { logger('probe_url: ' . print_r($result,true), LOGGER_DEBUG); - // Trying if it maybe a diaspora account - if (($result['network'] == NETWORK_FEED) OR ($result['addr'] == "")) { - require_once('include/bbcode.php'); - $address = GetProfileUsername($url, "", true); - $result2 = probe_url($address, $mode); - if ($result2['network'] != "") - $result = $result2; + if ($level == 1) { + // Trying if it maybe a diaspora account + if (($result['network'] == NETWORK_FEED) OR ($result['addr'] == "")) { + require_once('include/bbcode.php'); + $address = GetProfileUsername($url, "", true); + $result2 = probe_url($address, $mode, ++$level); + if ($result2['network'] != "") + $result = $result2; + } + + // Maybe it's some non standard GNU Social installation (Single user, subfolder or no uri rewrite) + if (($result['network'] == NETWORK_FEED) AND ($result['baseurl'] != "") AND ($result['nick'] != "")) { + $addr = $result['nick'].'@'.str_replace("http://", "", $result['baseurl']); + $result2 = probe_url($addr, $mode, ++$level); + if (($result2['network'] != "") AND ($result2['network'] != NETWORK_FEED)) + $result = $result2; + } } Cache::set("probe_url:".$mode.":".$url,serialize($result)); diff --git a/include/items.php b/include/items.php index 0a52d49d8..fa370c82a 100644 --- a/include/items.php +++ b/include/items.php @@ -1189,6 +1189,18 @@ function item_store($arr,$force_parent = false, $notify = false, $dontcache = fa } } + // If there is no guid then take the same guid that was taken before for the same plink + if ((trim($arr['guid']) == "") AND (trim($arr['plink']) != "") AND (trim($arr['network']) != "")) { + logger('item_store: checking for an existing guid for plink '.$arr['plink'], LOGGER_DEBUG); + $r = q("SELECT `guid` FROM `guid` WHERE `plink` = '%s' AND `network` = '%s' LIMIT 1", + dbesc(trim($arr['plink'])), dbesc(trim($arr['network']))); + + if(count($r)) { + $arr['guid'] = $r[0]["guid"]; + logger('item_store: found guid '.$arr['guid'].' for plink '.$arr['plink'], LOGGER_DEBUG); + } + } + // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin. // Deactivated, since the bbcode parser can handle with it - and it destroys posts with some smileys that contain "<" //if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false)) @@ -2227,6 +2239,10 @@ function edited_timestamp_is_newer($existing, $update) { function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) { if ($contact['network'] === NETWORK_OSTATUS) { if ($pass < 2) { + // Test - remove before flight + //$tempfile = tempnam(get_temppath(), "ostatus"); + //file_put_contents($tempfile, $xml); + logger("Consume OStatus messages ", LOGGER_DEBUG); ostatus_import($xml,$importer,$contact, $hub); } @@ -2241,12 +2257,6 @@ function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) return; } - // Test - remove before flight -// if ($contact['network'] === NETWORK_OSTATUS) { -// $tempfile = tempnam(get_temppath(), "ostatus"); -// file_put_contents($tempfile, $xml); -// } - $feed = new SimplePie(); $feed->set_raw_data($xml); if($datedir) @@ -4306,10 +4316,48 @@ function atom_author($tag,$name,$uri,$h,$w,$photo) { $o .= "<$tag>\r\n"; - $o .= "$name\r\n"; - $o .= "$uri\r\n"; - $o .= '' . "\r\n"; - $o .= '' . "\r\n"; + $o .= "\t$name\r\n"; + $o .= "\t$uri\r\n"; + $o .= "\t".'' . "\r\n"; + $o .= "\t".'' . "\r\n"; + + if ($tag == "author") { + $r = q("SELECT `profile`.`locality`, `profile`.`region`, `profile`.`country-name`, + `profile`.`name`, `profile`.`pub_keywords`, `profile`.`about`, + `profile`.`homepage`,`contact`.`nick` FROM `profile` + INNER JOIN `contact` ON `contact`.`uid` = `profile`.`uid` + INNER JOIN `user` ON `user`.`uid` = `profile`.`uid` + WHERE `profile`.`is-default` AND `contact`.`self` AND + NOT `user`.`hidewall` AND `contact`.`nurl`='%s'", + dbesc(normalise_link($uri))); + if ($r) { + $location = ''; + if($r[0]['locality']) + $location .= $r[0]['locality']; + if($r[0]['region']) { + if($location) + $location .= ', '; + $location .= $r[0]['region']; + } + if($r[0]['country-name']) { + if($location) + $location .= ', '; + $location .= $r[0]['country-name']; + } + + $o .= "\t".xmlify($r[0]["nick"])."\r\n"; + $o .= "\t".xmlify($r[0]["name"])."\r\n"; + $o .= "\t".xmlify($r[0]["about"])."\r\n"; + $o .= "\t\r\n"; + $o .= "\t\t".xmlify($location)."\r\n"; + $o .= "\t\r\n"; + $o .= "\t\r\n"; + $o .= "\thomepage\r\n"; + $o .= "\t\t".xmlify($r[0]["homepage"])."\r\n"; + $o .= "\t\ttrue\r\n"; + $o .= "\t\r\n"; + } + } call_hooks('atom_author', $o); @@ -4365,6 +4413,8 @@ function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) { $o .= '' . "\r\n"; + $o .= ''."\r\n"; + if($comment) $o .= '' . intval($item['last-child']) . '' . "\r\n"; @@ -4411,9 +4461,11 @@ function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) { $o .= '' . "\r\n"; } + // To-Do: + // To support these elements, the API needs to be enhanced //$o .= ''."\r\n"; - //$o .= ''."\r\n"; - //$o .= ''."\r\n"; + //$o .= "\t".''."\r\n"; + //$o .= "\t".''."\r\n"; $o .= item_get_attachment($item); diff --git a/include/notifier.php b/include/notifier.php index a0fd713c2..19f013472 100644 --- a/include/notifier.php +++ b/include/notifier.php @@ -301,26 +301,43 @@ function notifier_run(&$argv, &$argc){ $target_item['deny_cid'].$target_item['deny_gid']) == 0)) $push_notify = true; - if ($parent['network'] == NETWORK_OSTATUS) { - logger('Parent is OStatus', LOGGER_DEBUG); + $thr_parent = q("SELECT `network` FROM `item` WHERE `uri` = '%s' AND `uid` = %d", + dbesc($target_item["thr-parent"]), intval($target_item["uid"])); + + // If the thread parent is OStatus then do some magic to distribute the messages. + // We have not only to look at the parent, since it could be a Friendica thread. + if (($thr_parent AND ($thr_parent[0]['network'] == NETWORK_OSTATUS)) OR ($parent['network'] == NETWORK_OSTATUS)) { + logger('Parent is '.$parent['network'].'. Thread parent is '.$thr_parent[0]['network'], LOGGER_DEBUG); $push_notify = true; // Send a salmon notification to every person we mentioned in the post - // To-Do: Send a Salmon to every Friendica user in that thread $arr = explode(',',$target_item['tag']); foreach($arr as $x) { - logger('Checking tag '.$x, LOGGER_DEBUG); + //logger('Checking tag '.$x, LOGGER_DEBUG); $matches = null; if(preg_match('/@\[url=([^\]]*)\]/',$x,$matches)) { $probed_contact = probe_url($matches[1]); if ($probed_contact["notify"] != "") { - logger('scrape data for slapper: '.print_r($probed_contact, true)); + logger('Notify mentioned user '.$probed_contact["url"].': '.$probed_contact["notify"]); $url_recipients[$probed_contact["notify"]] = $probed_contact["notify"]; } } } + // We notify Friendica users in the thread when it is an OStatus thread. + // Hopefully this transfers the messages to the other Friendica servers. (Untested) + if ($parent["network"] == NETWORK_OSTATUS) { + $r = q("SELECT `author-link` FROM `item` WHERE `parent` = %d AND `author-link` != '%s'", + intval($target_item["parent"]), dbesc($owner['url'])); + foreach($r as $parent_item) { + $probed_contact = probe_url($parent_item["author-link"]); + if (($probed_contact["notify"] != "") AND ($probed_contact["network"] == NETWORK_DFRN)) { + logger('Notify Friendica user '.$probed_contact["url"].': '.$probed_contact["notify"]); + $url_recipients[$probed_contact["notify"]] = $probed_contact["notify"]; + } + } + } /* // Check if the recipient isn't in your contact list, try to slap it // Not sure if it is working or not. diff --git a/include/ostatus.php b/include/ostatus.php index f57b14817..86d0e36db 100644 --- a/include/ostatus.php +++ b/include/ostatus.php @@ -4,6 +4,8 @@ require_once('include/html2bbcode.php'); require_once('include/enotify.php'); require_once('include/items.php'); require_once('include/ostatus_conversation.php'); +require_once('include/socgraph.php'); +require_once('include/Photo.php'); function ostatus_fetchauthor($xpath, $context, $importer, &$contact) { @@ -56,14 +58,54 @@ function ostatus_fetchauthor($xpath, $context, $importer, &$contact) { $author["owner-link"] = $author["author-link"]; $author["owner-avatar"] = $author["author-avatar"]; + if ($r) { + // Update contact data + $update_contact = ($r[0]['name-date'] < datetime_convert('','','now -12 hours')); + if ($update_contact) { + logger("Update contact data for contact ".$contact["id"], LOGGER_DEBUG); + + $value = $xpath->evaluate('atom:author/poco:displayName/text()', $context)->item(0)->nodeValue; + if ($value != "") + $contact["name"] = $value; + + $value = $xpath->evaluate('atom:author/poco:preferredUsername/text()', $context)->item(0)->nodeValue; + if ($value != "") + $contact["nick"] = $value; + + $value = $xpath->evaluate('atom:author/poco:note/text()', $context)->item(0)->nodeValue; + if ($value != "") + $contact["about"] = $value; + + $value = $xpath->evaluate('atom:author/poco:address/poco:formatted/text()', $context)->item(0)->nodeValue; + if ($value != "") + $contact["location"] = $value; + + q("UPDATE `contact` SET `name` = '%s', `nick` = '%s', `about` = '%s', `location` = '%s', `name-date` = '%s' WHERE `id` = %d", + dbesc($contact["name"]), dbesc($contact["nick"]), dbesc($contact["about"]), dbesc($contact["location"]), + dbesc(datetime_convert()), intval($contact["id"])); + + poco_check($contact["url"], $contact["name"], $contact["network"], $author["author-avatar"], $contact["about"], $contact["location"], + "", "", "", datetime_convert(), 2, $contact["id"], $contact["uid"]); + } + + $update_photo = ($r[0]['avatar-date'] < datetime_convert('','','now -12 hours')); + + if ($update_photo AND isset($author["author-avatar"])) { + logger("Update profile picture for contact ".$contact["id"], LOGGER_DEBUG); + + $photos = import_profile_photo($author["author-avatar"], $importer["uid"], $contact["id"]); + + q("UPDATE `contact` SET `photo` = '%s', `thumb` = '%s', `micro` = '%s', `avatar-date` = '%s' WHERE `id` = %d", + dbesc($photos[0]), dbesc($photos[1]), dbesc($photos[2]), + dbesc(datetime_convert()), intval($contact["id"])); + } + } + return($author); } function ostatus_import($xml,$importer,&$contact, &$hub) { - // To-Do: - // Hub - $a = get_app(); logger("Import OStatus message", LOGGER_DEBUG); @@ -287,8 +329,18 @@ function ostatus_import($xml,$importer,&$contact, &$hub) { $orig_contact = $contact; $orig_author = ostatus_fetchauthor($xpath, $activityobjects, $importer, $orig_contact); - $prefix = share_header($orig_author['author-name'], $orig_author['author-link'], $orig_author['author-avatar'], "", $orig_created, $orig_uri); - $item["body"] = $prefix.html2bbcode($orig_body)."[/share]"; + //if (!intval(get_config('system','wall-to-wall_share'))) { + // $prefix = share_header($orig_author['author-name'], $orig_author['author-link'], $orig_author['author-avatar'], "", $orig_created, $orig_uri); + // $item["body"] = $prefix.add_page_info_to_body(html2bbcode($orig_body))."[/share]"; + //} else { + $item["author-name"] = $orig_author["author-name"]; + $item["author-link"] = $orig_author["author-link"]; + $item["author-avatar"] = $orig_author["author-avatar"]; + $item["body"] = add_page_info_to_body(html2bbcode($orig_body)); + $item["created"] = $orig_created; + + $item["uri"] = $orig_uri; + //} $item["verb"] = $xpath->query('activity:verb/text()', $activityobjects)->item(0)->nodeValue; $item["object-type"] = $xpath->query('activity:object-type/text()', $activityobjects)->item(0)->nodeValue; @@ -324,7 +376,9 @@ function ostatus_import($xml,$importer,&$contact, &$hub) { } else $item["parent-uri"] = $item["uri"]; - $item_id = item_store($item); + // We risk the chance of getting orphan items, we correct it some lines later + // To-Do: See To-Do line below. + $item_id = item_store($item, true); //echo $xml; //print_r($item); //echo $item_id." ".$item["parent-uri"]."\n"; @@ -364,6 +418,9 @@ function ostatus_import($xml,$importer,&$contact, &$hub) { if ($conversation != "") { // Check for duplicates. We really don't need to check the same conversation twice. if (!in_array($conversation, $conversationlist)) { + // To-Do: + // Call this before item_store is called to avoid posts with orphans + // The routine then needs to get the item array. complete_conversation($item_id, $conversation); $conversationlist[] = $conversation; } diff --git a/include/ostatus_conversation.php b/include/ostatus_conversation.php index cbf0163b7..e61f5fab4 100644 --- a/include/ostatus_conversation.php +++ b/include/ostatus_conversation.php @@ -1,5 +1,6 @@ id; - // To-Do: - // Only fetch a new parent if the new one doesn't have parents - // It can happen that OStatus servers have incomplete threads. - $new_parents = q("SELECT `id`, `uri`, `contact-id`, `type`, `verb`, `visible` FROM `item` WHERE `uid` = %d AND `uri` = '%s' AND `network` IN ('%s','%s') LIMIT 1", + $new_parents = q("SELECT `id`, `parent`, `uri`, `contact-id`, `type`, `verb`, `visible` FROM `item` WHERE `uid` = %d AND `uri` = '%s' AND `network` IN ('%s','%s') LIMIT 1", intval($message["uid"]), dbesc($first_id), dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN)); if ($new_parents) { - $parent = $new_parents[0]; - if ($parent["id"] != $message["parent"]) - logger('Fetch new parent id '.$parent["id"].' for '.$itemid.' Old parent: '.$message["parent"]); + // It can happen that OStatus servers have incomplete threads. + // Then keep the current parent + if ($parent["id"] == $parent["parent"]) { + $parent = $new_parents[0]; + + if ($parent["id"] != $message["parent"]) + logger('Fetch new parent id '.$parent["id"].' - Old parent: '.$message["parent"]); + } else { + $first_id = $parent["uri"]; + //logger('Keep parent for '.$itemid.' - Old parent: '.$message["parent"]); + } } else { $parent["id"] = 0; $parent["uri"] = $first_id; @@ -186,16 +192,28 @@ function complete_conversation($itemid, $conversation_url, $only_add_conversatio if ($parent["id"] != 0) { $existing_message = $message_exists[0]; - // Normally this shouldn't happen anymore, since we improved the way we fetch OStatus messages + // We improved the way we fetch OStatus messages, this shouldn't happen very often now if ($existing_message["parent"] != $parent["id"]) { - logger('updating id '.$existing_message["id"].' to parent '.$parent["id"].' uri '.$parent["uri"].' thread '.$parent_uri, LOGGER_DEBUG); + logger('updating id '.$existing_message["id"].' with parent '.$existing_message["parent"].' to parent '.$parent["id"].' uri '.$parent["uri"].' thread '.$parent_uri, LOGGER_DEBUG); - // This is partly bad, since the entry in the thread table isn't updated - $r = q("UPDATE `item` SET `parent` = %d, `parent-uri` = '%s', `thr-parent` = '%s' WHERE `id` = %d", - intval($parent["id"]), - dbesc($parent["uri"]), - dbesc($parent_uri), - intval($existing_message["id"])); + // Update the parent id of the selected item + $r = q("UPDATE `item` SET `parent` = %d, `parent-uri` = '%s' WHERE `id` = %d", + intval($parent["id"]), dbesc($parent["uri"]), intval($existing_message["id"])); + + // Update the parent uri in the thread - but only if it points to itself + $r = q("UPDATE `item` SET `thr-parent` = '%s' WHERE `id` = %d AND `uri` = `thr-parent`", + dbesc($parent_uri), intval($existing_message["id"])); + + // try to change all items of the same parent + $r = q("UPDATE `item` SET `parent` = %d, `parent-uri` = '%s' WHERE `parent` = %d", + intval($parent["id"]), dbesc($parent["uri"]), intval($existing_message["parent"])); + + // Update the parent uri in the thread - but only if it points to itself + $r = q("UPDATE `item` SET `thr-parent` = '%s' WHERE (`parent` = %d) AND (`uri` = `thr-parent`)", + dbesc($parent["uri"]), intval($existing_message["parent"])); + + // Now delete the thread + delete_thread($existing_message["parent"]); } } continue; @@ -235,12 +253,16 @@ function complete_conversation($itemid, $conversation_url, $only_add_conversatio $arr["thr-parent"] = $parent_uri; $arr["created"] = $single_conv->published; $arr["edited"] = $single_conv->published; - //$arr["owner-name"] = $single_conv->actor->contact->displayName; - $arr["owner-name"] = $single_conv->actor->contact->preferredUsername; + $arr["owner-name"] = $single_conv->actor->contact->displayName; + //$arr["owner-name"] = $single_conv->actor->contact->preferredUsername; + if ($arr["owner-name"] == '') + $arr["owner-name"] = $single_conv->actor->portablecontacts_net->displayName; + if ($arr["owner-name"] == '') + $arr["owner-name"] = $single_conv->actor->displayName; if ($arr["owner-name"] == '') $arr["owner-name"] = $single_conv->actor->portablecontacts_net->preferredUsername; if ($arr["owner-name"] == '') - $arr["owner-name"] = $single_conv->actor->displayName; + $arr["owner-name"] = $single_conv->actor->preferredUsername; $arr["owner-link"] = $actor; $arr["owner-avatar"] = $single_conv->actor->image->url; @@ -275,21 +297,19 @@ function complete_conversation($itemid, $conversation_url, $only_add_conversatio $newitem = item_store($arr); - logger('Stored new item '.$plink.' under id '.$newitem, LOGGER_DEBUG); + logger('Stored new item '.$plink.' for parent '.$arr["parent"].' under id '.$newitem, LOGGER_DEBUG); // Add the conversation entry (but don't fetch the whole conversation) complete_conversation($newitem, $conversation_url, true); // If the newly created item is the top item then change the parent settings of the thread - // This shouldn't happen anymore. This could is supposed to be absolote. + // This shouldn't happen anymore. This is supposed to be absolote. if ($newitem AND ($arr["uri"] == $first_id)) { logger('setting new parent to id '.$newitem); $new_parents = q("SELECT `id`, `uri`, `contact-id`, `type`, `verb`, `visible` FROM `item` WHERE `uid` = %d AND `id` = %d LIMIT 1", intval($message["uid"]), intval($newitem)); - if ($new_parents) { + if ($new_parents) $parent = $new_parents[0]; - logger('done changing parents to parent '.$newitem); - } } } }