use Friendica\Core\Protocol;
use Friendica\Core\System;
use Friendica\Database\DBA;
-use Friendica\Model\Contact;
use Friendica\Model\Profile;
use Friendica\Protocol\ActivityPub;
use Friendica\Protocol\Email;
*/
private static function rearrangeData($data)
{
- $fields = ["name", "nick", "guid", "url", "addr", "alias",
- "photo", "community", "keywords", "location", "about",
+ $fields = ["name", "nick", "guid", "url", "addr", "alias", "photo", "account-type",
+ "community", "keywords", "location", "about", "gender", "hide",
"batch", "notify", "poll", "request", "confirm", "poco",
+ "following", "followers", "inbox", "outbox", "sharedinbox",
"priority", "network", "pubkey", "baseurl"];
$newdata = [];
if (!self::$istimeout) {
$ap_profile = ActivityPub::probeProfile($uri);
- if (!empty($ap_profile) && empty($network) && (defaults($data, 'network', '') != Protocol::DFRN)) {
+ if (empty($data) || (!empty($ap_profile) && empty($network) && (defaults($data, 'network', '') != Protocol::DFRN))) {
$data = $ap_profile;
+ } elseif (!empty($ap_profile)) {
+ $ap_profile['batch'] = '';
+ $data = array_merge($ap_profile, $data);
}
} else {
Logger::notice('Time out detected. AP will not be probed.', ['uri' => $uri]);
$data['network'] = Protocol::PHANTOM;
}
+ if (!isset($data['hide']) && in_array($data['network'], Protocol::FEDERATED)) {
+ $data['hide'] = self::getHideStatus($data['url']);
+ }
+
$data = self::rearrangeData($data);
// Only store into the cache if the value seems to be valid
if (!in_array($data['network'], [Protocol::PHANTOM, Protocol::MAIL])) {
Cache::set('Probe::uri:' . $network . ':' . $uri, $data, Cache::DAY);
+ }
- /// @todo temporary fix - we need a real contact update function that updates only changing fields
- /// The biggest problem is the avatar picture that could have a reduced image size.
- /// It should only be updated if the existing picture isn't existing anymore.
- /// We only update the contact when it is no probing for a specific network.
- if (($data['network'] != Protocol::FEED)
- && ($network == '')
- && $data['name']
- && $data['nick']
- && $data['url']
- && $data['addr']
- && $data['poll']
- ) {
- $fields = [
- 'name' => $data['name'],
- 'nick' => $data['nick'],
- 'url' => $data['url'],
- 'addr' => $data['addr'],
- 'photo' => $data['photo'],
- 'keywords' => $data['keywords'],
- 'location' => $data['location'],
- 'about' => $data['about'],
- 'notify' => $data['notify'],
- 'network' => $data['network'],
- 'server_url' => $data['baseurl']
- ];
-
- // This doesn't cover the case when a community isn't a community anymore
- if (!empty($data['community']) && $data['community']) {
- $fields['community'] = $data['community'];
- $fields['contact-type'] = Contact::TYPE_COMMUNITY;
- }
+ return $data;
+ }
- $fieldnames = [];
- foreach ($fields as $key => $val) {
- if (empty($val)) {
- unset($fields[$key]);
- } else {
- $fieldnames[] = $key;
- }
- }
+ /**
+ * Fetches the "hide" status from the profile
+ *
+ * @param string $url URL of the profile
+ *
+ * @return boolean "hide" status
+ */
+ private static function getHideStatus($url)
+ {
+ $curlResult = Network::curl($url);
+ if (!$curlResult->isSuccess()) {
+ return false;
+ }
- $fields['updated'] = DateTimeFormat::utcNow();
+ // If the file is too large then exit
+ if (defaults($curlResult->getInfo(), 'download_content_length', 0) > 1000000) {
+ return false;
+ }
- $condition = ['nurl' => Strings::normaliseLink($data['url'])];
+ // If it isn't a HTML file then exit
+ if (($curlResult->getContentType() != '') && !strstr(strtolower($curlResult->getContentType()), 'html')) {
+ return false;
+ }
- $old_fields = DBA::selectFirst('gcontact', $fieldnames, $condition);
+ $body = $curlResult->getBody();
- // When the gcontact doesn't exist, the value "true" will trigger an insert.
- // In difference to the public contacts we want to have every contact
- // in the world in our global contacts.
- if (!$old_fields) {
- $old_fields = true;
+ $doc = new DOMDocument();
+ @$doc->loadHTML($body);
- // These values have to be set only on insert
- $fields['photo'] = $data['photo'];
- $fields['created'] = DateTimeFormat::utcNow();
- }
+ $xpath = new DOMXPath($doc);
- DBA::update('gcontact', $fields, $condition, $old_fields);
-
- $fields = [
- 'name' => $data['name'],
- 'nick' => $data['nick'],
- 'url' => $data['url'],
- 'addr' => $data['addr'],
- 'alias' => $data['alias'],
- 'keywords' => $data['keywords'],
- 'location' => $data['location'],
- 'about' => $data['about'],
- 'batch' => $data['batch'],
- 'notify' => $data['notify'],
- 'poll' => $data['poll'],
- 'request' => $data['request'],
- 'confirm' => $data['confirm'],
- 'poco' => $data['poco'],
- 'network' => $data['network'],
- 'pubkey' => $data['pubkey'],
- 'priority' => $data['priority'],
- 'writable' => true,
- 'rel' => Contact::SHARING
- ];
-
- $fieldnames = [];
-
- foreach ($fields as $key => $val) {
- if (empty($val)) {
- unset($fields[$key]);
- } else {
- $fieldnames[] = $key;
- }
+ $list = $xpath->query('//meta[@name]');
+ foreach ($list as $node) {
+ $meta_tag = [];
+ if ($node->attributes->length) {
+ foreach ($node->attributes as $attribute) {
+ $meta_tag[$attribute->name] = $attribute->value;
}
+ }
- $condition = ['nurl' => Strings::normaliseLink($data['url']), 'self' => false, 'uid' => 0];
-
- // "$old_fields" will return a "false" when the contact doesn't exist.
- // This won't trigger an insert. This is intended, since we only need
- // public contacts for everyone we store items from.
- // We don't need to store every contact on the planet.
- $old_fields = DBA::selectFirst('contact', $fieldnames, $condition);
+ if (empty($meta_tag['content'])) {
+ continue;
+ }
- $fields['name-date'] = DateTimeFormat::utcNow();
- $fields['uri-date'] = DateTimeFormat::utcNow();
- $fields['success_update'] = DateTimeFormat::utcNow();
+ $content = strtolower(trim($meta_tag['content']));
- DBA::update('contact', $fields, $condition, $old_fields);
+ switch (strtolower(trim($meta_tag['name']))) {
+ case 'dfrn-global-visibility':
+ if ($content == 'false') {
+ return true;
+ }
+ break;
+ case 'robots':
+ if (strpos($content, 'noindex') !== false) {
+ return true;
+ }
+ break;
}
}
- return $data;
+ return false;
}
/**
}
if ($host == 'twitter.com') {
- return ["network" => Protocol::TWITTER];
+ return self::twitter($uri);
}
$lrdd = self::hostMeta($host);
$nick = substr($uri, 0, strpos($uri, '@'));
if (strpos($uri, '@twitter.com')) {
- return ["network" => Protocol::TWITTER];
+ return self::twitter($uri);
}
$lrdd = self::hostMeta($host);
}
if (!empty($json["tags"])) {
- $keywords = implode(" ", $json["tags"]);
+ $keywords = implode(", ", $json["tags"]);
if ($keywords != "") {
$data["keywords"] = $keywords;
}
$data["about"] = $json["about"];
}
+ if (!empty($json["gender"])) {
+ $data["gender"] = $json["gender"];
+ }
+
if (!empty($json["key"])) {
$data["pubkey"] = $json["key"];
}
$data["poll"] = $json["dfrn-poll"];
}
+ if (isset($json["hide"])) {
+ $data["hide"] = (bool)$json["hide"];
+ } else {
+ $data["hide"] = false;
+ }
+
return $data;
}
return $data;
}
+ /**
+ * @brief Check for twitter contact
+ *
+ * @param string $uri
+ *
+ * @return array twitter data
+ */
+ private static function twitter($uri)
+ {
+ if (preg_match('=(.*)@twitter.com=i', $uri, $matches)) {
+ $nick = $matches[1];
+ } elseif (preg_match('=https?://twitter.com/(.*)=i', $uri, $matches)) {
+ $nick = $matches[1];
+ } else {
+ return [];
+ }
+
+ $data = [];
+ $data['url'] = 'https://twitter.com/' . $nick;
+ $data['addr'] = $nick . '@twitter.com';
+ $data['nick'] = $data['name'] = $nick;
+ $data['network'] = Protocol::TWITTER;
+ $data['baseurl'] = 'https://twitter.com';
+
+ $curlResult = Network::curl($data['url'], false);
+ if (!$curlResult->isSuccess()) {
+ return [];
+ }
+
+ $body = $curlResult->getBody();
+ $doc = new DOMDocument();
+ @$doc->loadHTML($body);
+ $xpath = new DOMXPath($doc);
+
+ $list = $xpath->query('//img[@class]');
+ foreach ($list as $node) {
+ $img_attr = [];
+ if ($node->attributes->length) {
+ foreach ($node->attributes as $attribute) {
+ $img_attr[$attribute->name] = $attribute->value;
+ }
+ }
+
+ if (empty($img_attr['class'])) {
+ continue;
+ }
+
+ if (strpos($img_attr['class'], 'ProfileAvatar-image') !== false) {
+ if (!empty($img_attr['src'])) {
+ $data['photo'] = $img_attr['src'];
+ }
+ if (!empty($img_attr['alt'])) {
+ $data['name'] = $img_attr['alt'];
+ }
+ }
+ }
+
+ return $data;
+ }
+
/**
* @brief Check page for feed link
*