aboutsummaryrefslogtreecommitdiffstats
path: root/include/feedutils.php
diff options
context:
space:
mode:
Diffstat (limited to 'include/feedutils.php')
-rw-r--r--include/feedutils.php836
1 files changed, 604 insertions, 232 deletions
diff --git a/include/feedutils.php b/include/feedutils.php
index 6bb7d103e..07cb79340 100644
--- a/include/feedutils.php
+++ b/include/feedutils.php
@@ -79,9 +79,11 @@ function get_feed_for($channel, $observer_hash, $params) {
$feed_author = '';
if(intval($params['compat']) === 1) {
- $feed_author = atom_author('author',$channel['channel_address'],$channel['channel_name'],$channel['xchan_url'],300,300,$channel['xchan_photo_mimetype'],$channel['xchan_photo_l']);
+ $feed_author = atom_render_author('author',$channel);
}
+ $owner = atom_render_author('zot:owner',$channel);
+
$atom .= replace_macros($feed_template, array(
'$version' => xmlify(Zotlabs\Lib\System::get_project_version()),
'$red' => xmlify(Zotlabs\Lib\System::get_platform_name()),
@@ -89,6 +91,7 @@ function get_feed_for($channel, $observer_hash, $params) {
'$feed_title' => xmlify($channel['channel_name']),
'$feed_updated' => xmlify(datetime_convert('UTC', 'UTC', 'now', ATOM_TIME)),
'$author' => $feed_author,
+ '$owner' => $owner,
'$name' => xmlify($channel['channel_name']),
'$profile_page' => xmlify($channel['xchan_url']),
'$mimephoto' => xmlify($channel['xchan_photo_mimetype']),
@@ -112,7 +115,7 @@ function get_feed_for($channel, $observer_hash, $params) {
$items = items_fetch(
[
- 'wall' => '1',
+ 'wall' => '1',
'datequery' => $params['end'],
'datequery2' => $params['begin'],
'start' => intval($params['start']),
@@ -133,7 +136,7 @@ function get_feed_for($channel, $observer_hash, $params) {
continue;
/** @BUG $owner is undefined in this call */
- $atom .= atom_entry($item, $type, null, $owner, true);
+ $atom .= atom_entry($item, $type, null, $owner, true, '', $params['compat']);
}
}
@@ -243,9 +246,17 @@ function get_atom_elements($feed, $item, &$author) {
$found_author = $item->get_author();
if($found_author) {
+ if($rawauthor) {
+ if($rawauthor[0]['child'][NAMESPACE_POCO]['displayName'][0]['data'])
+ $author['full_name'] = unxmlify($rawauthor[0]['child'][NAMESPACE_POCO]['displayName'][0]['data']);
+ }
$author['author_name'] = unxmlify($found_author->get_name());
$author['author_link'] = unxmlify($found_author->get_link());
$author['author_is_feed'] = false;
+
+ $rawauthor = $feed->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
+ logger('rawauthor: ' . print_r($rawauthor, true));
+
}
else {
$author['author_name'] = unxmlify($feed->get_title());
@@ -256,13 +267,15 @@ function get_atom_elements($feed, $item, &$author) {
if(substr($author['author_link'],-1,1) == '/')
$author['author_link'] = substr($author['author_link'],0,-1);
- $res['mid'] = unxmlify($item->get_id());
+ $res['mid'] = normalise_id(unxmlify($item->get_id()));
$res['title'] = unxmlify($item->get_title());
$res['body'] = unxmlify($item->get_content());
$res['plink'] = unxmlify($item->get_link(0));
$res['item_rss'] = 1;
+ $summary = unxmlify($item->get_description(true));
+
// removing the content of the title if its identically to the body
// This helps with auto generated titles e.g. from tumblr
@@ -275,6 +288,23 @@ function get_atom_elements($feed, $item, &$author) {
$base_url = '';
+ $rawcreated = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'published');
+ if($rawcreated)
+ $res['created'] = unxmlify($rawcreated[0]['data']);
+
+ $rawedited = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'updated');
+ if($rawedited)
+ $res['edited'] = unxmlify($rawedited[0]['data']);
+
+ if((x($res,'edited')) && (! (x($res,'created'))))
+ $res['created'] = $res['edited'];
+
+ if(! $res['created'])
+ $res['created'] = $item->get_date('c');
+
+ if(! $res['edited'])
+ $res['edited'] = $item->get_date('c');
+
$rawverb = $item->get_item_tags(NAMESPACE_ACTIVITY, 'verb');
// select between supported verbs
@@ -283,121 +313,125 @@ function get_atom_elements($feed, $item, &$author) {
$res['verb'] = unxmlify($rawverb[0]['data']);
}
- // translate OStatus unfollow to activity streams if it happened to get selected
-
- if((x($res,'verb')) && ($res['verb'] === 'http://ostatus.org/schema/1.0/unfollow'))
- $res['verb'] = ACTIVITY_UNFOLLOW;
+ // look for a photo. We should check media size and find the best one,
+ // but for now let's just find any author photo
- if(array_key_exists('verb',$res) && $res['verb'] === ACTIVITY_SHARE) {
- // For Mastodon shares ("boosts"), we need to parse the original author information
- // from the activity:object -> author structure
- $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'object');
+ $rawauthor = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
- if($rawobj) {
- $rawauthor = $rawobj->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
- if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name']) {
- $author['author_name'] = unxmlify($rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name']);
+ if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
+ $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
+ foreach($base as $link) {
+ if(!x($author, 'author_photo') || ! $author['author_photo']) {
+ if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
+ $author['author_photo'] = unxmlify($link['attribs']['']['href']);
}
+ }
+ }
+ $rawactor = $item->get_item_tags(NAMESPACE_ACTIVITY, 'actor');
- if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri']) {
- $author['author_link'] = unxmlify($rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri']);
- }
- if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
- $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
- foreach($base as $link) {
- if(!x($author, 'author_photo') || ! $author['author_photo']) {
- if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
- $author['author_photo'] = unxmlify($link['attribs']['']['href']);
- }
+ if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['obj_type'][0]['data'], ACTIVITY_OBJ_PERSON)) {
+ $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
+ if($base && count($base)) {
+ foreach($base as $link) {
+ if($link['attribs']['']['rel'] === 'alternate' && (! $res['author_link']))
+ $author['author_link'] = unxmlify($link['attribs']['']['href']);
+ if(!x($author, 'author_photo') || ! $author['author_photo']) {
+ if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
+ $author['author_photo'] = unxmlify($link['attribs']['']['href']);
}
}
}
}
- else {
- // look for a photo. We should check media size and find the best one,
- // but for now let's just find any author photo
- $rawauthor = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
+ // check for a yahoo media element (github etc.)
+ if(! $author['author_photo']) {
+ $rawmedia = $item->get_item_tags(NAMESPACE_YMEDIA,'thumbnail');
+ if($rawmedia && $rawmedia[0]['attribs']['']['url']) {
+ $author['author_photo'] = strip_tags(unxmlify($rawmedia[0]['attribs']['']['url']));
+ }
+ }
+
+
+ // No photo/profile-link on the item - look at the feed level
+
+
+ if((! (x($author,'author_link'))) || (! (x($author,'author_photo')))) {
+ $rawauthor = $feed->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
$base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
foreach($base as $link) {
- if(!x($author, 'author_photo') || ! $author['author_photo']) {
+ if($link['attribs']['']['rel'] === 'alternate' && (! $author['author_link'])) {
+ $author['author_link'] = unxmlify($link['attribs']['']['href']);
+ $author['author_is_feed'] = true;
+ }
+ if(! $author['author_photo']) {
if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
$author['author_photo'] = unxmlify($link['attribs']['']['href']);
}
}
}
- $rawactor = $item->get_item_tags(NAMESPACE_ACTIVITY, 'actor');
+ $rawactor = $feed->get_feed_tags(NAMESPACE_ACTIVITY, 'subject');
- if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['obj_type'][0]['data'], ACTIVITY_OBJ_PERSON)) {
+ if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['obj_type'][0]['data'],ACTIVITY_OBJ_PERSON)) {
$base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
+
if($base && count($base)) {
foreach($base as $link) {
if($link['attribs']['']['rel'] === 'alternate' && (! $res['author_link']))
$author['author_link'] = unxmlify($link['attribs']['']['href']);
- if(!x($author, 'author_photo') || ! $author['author_photo']) {
+ if(! (x($author,'author_photo'))) {
if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
$author['author_photo'] = unxmlify($link['attribs']['']['href']);
}
}
}
}
+ }
- // check for a yahoo media element (github etc.)
-
- if(! $author['author_photo']) {
- $rawmedia = $item->get_item_tags(NAMESPACE_YMEDIA,'thumbnail');
- if($rawmedia && $rawmedia[0]['attribs']['']['url']) {
- $author['author_photo'] = strip_tags(unxmlify($rawmedia[0]['attribs']['']['url']));
- }
+ $rawcnv = $item->get_item_tags(NAMESPACE_OSTATUS, 'conversation');
+ if($rawcnv) {
+ // new style
+ $ostatus_conversation = normalise_id(unxmlify($rawcnv[0]['attribs']['']['ref']));
+ if(! $ostatus_conversation) {
+ // old style
+ $ostatus_conversation = normalise_id(unxmlify($rawcnv[0]['data']));
}
+ if($ostatus_conversation) {
+ set_iconfig($res,'ostatus','conversation',$ostatus_conversation,true);
+ logger('ostatus_conversation: ' . $ostatus_conversation, LOGGER_DATA, LOG_INFO);
+ }
+ }
+ $ostatus_protocol = (($ostatus_conversation) ? true : false);
+
+ $mastodon = (($item->get_item_tags('http://mastodon.social/schema/1.0','scope')) ? true : false);
+ if($mastodon) {
+ $ostatus_protocol = true;
+ if(($mastodon[0]['data']) && ($mastodon[0]['data'] !== 'public'))
+ $res['item_private'] = 1;
+ }
- // No photo/profile-link on the item - look at the feed level
-
- if((! (x($author,'author_link'))) || (! (x($author,'author_photo')))) {
- $rawauthor = $feed->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
- if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
- $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
- foreach($base as $link) {
- if($link['attribs']['']['rel'] === 'alternate' && (! $author['author_link'])) {
- $author['author_link'] = unxmlify($link['attribs']['']['href']);
- $author['author_is_feed'] = true;
- }
- if(! $author['author_photo']) {
- if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
- $author['author_photo'] = unxmlify($link['attribs']['']['href']);
- }
- }
- }
+ $apps = $item->get_item_tags(NAMESPACE_STATUSNET, 'notice_info');
+ if($apps && $apps[0]['attribs']['']['source']) {
+ $res['app'] = strip_tags(unxmlify($apps[0]['attribs']['']['source']));
+ }
- $rawactor = $feed->get_feed_tags(NAMESPACE_ACTIVITY, 'subject');
+ if($ostatus_protocol) {
- if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['obj_type'][0]['data'], ACTIVITY_OBJ_PERSON)) {
- $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
+ // translate OStatus unfollow to activity streams if it happened to get selected
- if($base && count($base)) {
- foreach($base as $link) {
- if($link['attribs']['']['rel'] === 'alternate' && (! $res['author_link']))
- $author['author_link'] = unxmlify($link['attribs']['']['href']);
- if(! (x($author,'author_photo'))) {
- if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
- $author['author_photo'] = unxmlify($link['attribs']['']['href']);
- }
- }
- }
- }
+ if((x($res,'verb')) && ($res['verb'] === 'http://ostatus.org/schema/1.0/unfollow')) {
+ $res['verb'] = ACTIVITY_UNFOLLOW;
}
- }
- $ostatus_protocol = (($item->get_item_tags(NAMESPACE_OSTATUS, 'conversation')) ? true : false);
+ // And OStatus 'favorite' is pretty much what we call 'like' on other networks
- $apps = $item->get_item_tags(NAMESPACE_STATUSNET, 'notice_info');
- if($apps && $apps[0]['attribs']['']['source']) {
- $res['app'] = strip_tags(unxmlify($apps[0]['attribs']['']['source']));
+ if((x($res,'verb')) && ($res['verb'] === ACTIVITY_FAVORITE)) {
+ $res['verb'] = ACTIVITY_LIKE;
+ }
}
/*
@@ -483,6 +517,12 @@ function get_atom_elements($feed, $item, &$author) {
);
}
+ // turn Mastodon content warning into a #nsfw hashtag
+ if($mastodon && $summary) {
+ $res['body'] .= "\n\n#ContentWarning\n";
+ }
+
+
$private = $item->get_item_tags(NAMESPACE_DFRN, 'private');
if($private && intval($private[0]['data']) > 0)
$res['item_private'] = ((intval($private[0]['data'])) ? 1 : 0);
@@ -493,23 +533,6 @@ function get_atom_elements($feed, $item, &$author) {
if($rawlocation)
$res['location'] = unxmlify($rawlocation[0]['data']);
- $rawcreated = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'published');
- if($rawcreated)
- $res['created'] = unxmlify($rawcreated[0]['data']);
-
- $rawedited = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'updated');
- if($rawedited)
- $res['edited'] = unxmlify($rawedited[0]['data']);
-
- if((x($res,'edited')) && (! (x($res,'created'))))
- $res['created'] = $res['edited'];
-
- if(! $res['created'])
- $res['created'] = $item->get_date('c');
-
- if(! $res['edited'])
- $res['edited'] = $item->get_date('c');
-
// Disallow time travelling posts
@@ -591,6 +614,7 @@ function get_atom_elements($feed, $item, &$author) {
$attach = $item->get_enclosures();
if($attach) {
+
$res['attach'] = array();
foreach($attach as $att) {
$len = intval($att->get_length());
@@ -607,9 +631,21 @@ function get_atom_elements($feed, $item, &$author) {
if(! $type)
$type = 'application/octet-stream';
+ if($ostatus_protocol) {
+ if((strpos($type,'image') === 0) && (strpos($res['body'], ']' . $link . '[/img]') === false) && (strpos($link,'http') === 0)) {
+ $res['body'] .= "\n\n" . '[img]' . $link . '[/img]';
+ }
+ if((strpos($type,'video') === 0) && (strpos($res['body'], ']' . $link . '[/video]') === false) && (strpos($link,'http') === 0)) {
+ $res['body'] .= "\n\n" . '[video]' . $link . '[/video]';
+ }
+ if((strpos($type,'audio') === 0) && (strpos($res['body'], ']' . $link . '[/audio]') === false) && (strpos($link,'http') === 0)) {
+ $res['body'] .= "\n\n" . '[audio]' . $link . '[/audio]';
+ }
+ }
$res['attach'][] = array('href' => $link, 'length' => $len, 'type' => $type, 'title' => $title );
}
}
+
$rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'object');
@@ -687,6 +723,13 @@ function get_atom_elements($feed, $item, &$author) {
$res['target'] = $obj;
}
+
+
+ if(array_key_exists('verb',$res) && $res['verb'] === ACTIVITY_SHARE
+ && array_key_exists('obj_type',$res) && in_array($res['obj_type'], [ ACTIVITY_OBJ_NOTE, ACTIVITY_OBJ_COMMENT, ACTIVITY_OBJ_ACTIVITY ] )) {
+ feed_get_reshare($res,$item);
+ }
+
// build array to pass to hook
$arr = [
'feed' => $feed,
@@ -703,6 +746,120 @@ function get_atom_elements($feed, $item, &$author) {
return $arr['result'];
}
+function feed_get_reshare(&$res,$item) {
+
+ $share = [];
+
+ // For Mastodon shares ("boosts"), we need to parse the original author information
+ // from the activity:object -> author structure
+ $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'object');
+
+ if($rawobj) {
+
+ $rawauthor = $rawobj[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['author'];
+
+ if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name']) {
+ $share['author'] = unxmlify($rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']);
+ }
+
+ if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri']) {
+ $share['profile'] = unxmlify($rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']);
+ }
+
+ if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
+ $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
+ foreach($base as $link) {
+ if(! (array_key_exists('avatar',$share) && $share['avatar'])) {
+ if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
+ $share['avatar'] = unxmlify($link['attribs']['']['href']);
+ }
+ }
+ }
+
+ if(! $share['author'])
+ $share['author'] = t('unknown');
+ if(! $share['avatar'])
+ $share['avatar'] = z_root() . '/' . get_default_profile_photo(80);
+ if(! $share['profile'])
+ $share['profile'] = z_root();
+
+ $child = $rawobj[0]['child'];
+
+
+ if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
+ $share['links'] = encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']);
+
+ $rawcreated = $rawobj[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['published'];
+
+ if($rawcreated)
+ $share['created'] = unxmlify($rawcreated[0]['data']);
+ else
+ $share['created'] = $res['created'];
+
+ if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
+ $share['message_id'] = unxmlify($child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data']);
+
+ if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'content') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
+ $body = unxmlify($child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']);
+ if(! $body)
+ $body = unxmlify($child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data']);
+
+ if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
+ $body = purify_html($body);
+ $body = html2bbcode($body);
+ }
+ }
+
+ $attach = $share['links'];
+ if($attach) {
+ foreach($attach as $att) {
+ if($att['rel'] === 'alternate') {
+ $share['alternate'] = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att['href']))));
+ continue;
+ }
+ if($att['rel'] !== 'enclosure')
+ continue;
+ $len = intval($att['length']);
+ $link = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att['href']))));
+ $title = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att['title']))));
+ $type = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att['type']))));
+ if(strpos($type,';'))
+ $type = substr($type,0,strpos($type,';'));
+ if((! $link) || (strpos($link,'http') !== 0))
+ continue;
+
+ if(! $title)
+ $title = ' ';
+ if(! $type)
+ $type = 'application/octet-stream';
+
+ if((strpos($type,'image') === 0) && (strpos($body, ']' . $link . '[/img]') === false) && (strpos($link,'http') === 0)) {
+ $body .= "\n\n" . '[img]' . $link . '[/img]';
+ }
+ if((strpos($type,'video') === 0) && (strpos($body, ']' . $link . '[/video]') === false) && (strpos($link,'http') === 0)) {
+ $body .= "\n\n" . '[video]' . $link . '[/video]';
+ }
+ if((strpos($type,'audio') === 0) && (strpos($body, ']' . $link . '[/audio]') === false) && (strpos($link,'http') === 0)) {
+ $body .= "\n\n" . '[audio]' . $link . '[/audio]';
+ }
+ }
+ }
+
+ $res['body'] = "[share author='" . urlencode($share['author']) .
+ "' profile='" . $share['profile'] .
+ "' avatar='" . $share['avatar'] .
+ "' link='" . $share['alternate'] .
+ "' posted='" . $share['created'] .
+ "' message_id='" . $share['message_id'] . "']";
+
+ $res['body'] .= $body;
+ $res['body'] .= "[/share]";
+ }
+
+}
+
+
+
/**
* @brief Encodes SimplePie_Item link arrays.
*
@@ -718,6 +875,10 @@ function encode_rel_links($links) {
$l = array();
if($link['attribs']['']['rel'])
$l['rel'] = $link['attribs']['']['rel'];
+ if($link['attribs']['']['length'])
+ $l['length'] = $link['attribs']['']['length'];
+ if($link['attribs']['']['title'])
+ $l['title'] = $link['attribs']['']['title'];
if($link['attribs']['']['type'])
$l['type'] = $link['attribs']['']['type'];
if($link['attribs']['']['href'])
@@ -838,7 +999,8 @@ function consume_feed($xml, $importer, &$contact, $pass = 0) {
foreach($items as $item) {
$is_reply = false;
- $item_id = normalise_id($item->get_id());
+ $send_downstream = false;
+ $parent_link = '';
logger('processing ' . $item->get_id(), LOGGER_DEBUG);
@@ -847,6 +1009,11 @@ function consume_feed($xml, $importer, &$contact, $pass = 0) {
$is_reply = true;
$parent_mid = normalise_id($rawthread[0]['attribs']['']['ref']);
}
+ if(isset($rawthread[0]['attribs']['']['href'])) {
+ $parent_link = $rawthread[0]['attribs']['']['href'];
+ }
+
+ logger('in-reply-to: ' . $parent_mid, LOGGER_DEBUG);
if($is_reply) {
@@ -855,18 +1022,35 @@ function consume_feed($xml, $importer, &$contact, $pass = 0) {
// Have we seen it? If not, import it.
- $item_id = normalise_id($item->get_id());
$author = array();
$datarray = get_atom_elements($feed,$item,$author);
- if($datarray['mid'])
- $datarray['mid'] = normalise_id($item->get_id());
+ if(! $datarray['mid'])
+ continue;
+
+ // This probably isn't an appropriate default but we're about to change it
+ // if it's wrong.
+
+ $datarray['comment_policy'] = 'authenticated';
+
+ // A Mastodon privacy tag has been found. We cannot send private comments
+ // through the OStatus protocol, so block commenting.
+
+ if(array_key_exists('item_private',$datarray) && intval($datarray['item_private'])) {
+ $datarray['public_policy'] = 'specific';
+ $datarray['comment_policy'] = 'none';
+ }
if($contact['xchan_network'] === 'rss') {
$datarray['public_policy'] = 'specific';
$datarray['comment_policy'] = 'none';
}
+ // if we have everything but a photo, provide the default profile photo
+
+ if($author['author_name'] && $author['author_link'] && (! $author['author_photo']))
+ $author['author_photo'] = z_root() . '/' . get_default_profile_photo(80);
+
if((! x($author,'author_name')) || ($author['author_is_feed']))
$author['author_name'] = $contact['xchan_name'];
if((! x($author,'author_link')) || ($author['author_is_feed']))
@@ -877,7 +1061,16 @@ function consume_feed($xml, $importer, &$contact, $pass = 0) {
$datarray['author_xchan'] = '';
if($author['author_link'] != $contact['xchan_url']) {
- $x = import_author_unknown(array('name' => $author['author_name'],'url' => $author['author_link'],'photo' => array('src' => $author['author_photo'])));
+ $name = '';
+ if($author['full_name']) {
+ $name = $author['full_name'];
+ if($author['author_name'])
+ $name .= ' (' . $author['author_name'] . ')';
+ }
+ else {
+ $name = $author['author_name'];
+ }
+ $x = import_author_unknown(array('name' => $name,'url' => $author['author_link'],'photo' => array('src' => $author['author_photo'])));
if($x)
$datarray['author_xchan'] = $x;
}
@@ -886,8 +1079,8 @@ function consume_feed($xml, $importer, &$contact, $pass = 0) {
$datarray['owner_xchan'] = $contact['xchan_hash'];
- $r = q("SELECT edited FROM item WHERE mid = '%s' AND uid = %d LIMIT 1",
- dbesc($item_id),
+ $r = q("SELECT id, edited FROM item WHERE mid = '%s' AND uid = %d LIMIT 1",
+ dbesc($datarray['mid']),
intval($importer['channel_id'])
);
@@ -902,20 +1095,148 @@ function consume_feed($xml, $importer, &$contact, $pass = 0) {
if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
continue;
+ $datarray['uid'] = $importer['channel_id'];
+ $datarray['aid'] = $importer['channel_account_id'];
+ $datarray['id'] = $r[0]['id'];
+
update_feed_item($importer['channel_id'],$datarray);
+
}
continue;
}
- $x = q("select mid from item where mid = '%s' and uid = %d limit 1",
- dbesc($parent_mid),
- intval($importer['channel_id'])
- );
- if($x)
- $parent_mid = $x[0]['mid'];
+ $pmid = '';
+ $conv_id = get_iconfig($datarray,'ostatus','conversation');
+
+ // match conversations - first try ostatus:conversation
+ // next try thr:in_reply_to
+
+ if($conv_id) {
+ logger('find_parent: conversation_id: ' . $conv_id, LOGGER_DEBUG);
+ $c = q("select parent_mid from item left join iconfig on item.id = iconfig.iid where iconfig.cat = 'ostatus' and iconfig.k = 'conversation' and iconfig.v = '%s' and item.uid = %d order by item.id limit 1",
+ dbesc($conv_id),
+ intval($importer['channel_id'])
+ );
+ if($c) {
+ logger('find_parent: matched conversation: ' . $conv_id, LOGGER_DEBUG);
+ $pmid = $c[0]['parent_mid'];
+ $datarray['parent_mid'] = $pmid;
+ }
+ }
+ if(! $pmid) {
+ $x = q("select parent_mid from item where mid = '%s' and uid = %d limit 1",
+ dbesc($parent_mid),
+ intval($importer['channel_id'])
+ );
+
+ if($x) {
+ logger('find_parent: matched in-reply-to: ' . $parent_mid, LOGGER_DEBUG);
+ $pmid = $x[0]['parent_mid'];
+ $datarray['parent_mid'] = $pmid;
+ }
+ }
+ if((! $pmid) && $parent_link !== '') {
+ $f = feed_conversation_fetch($importer,$contact,$parent_link);
+ if($f) {
+ // check both potential conversation parents again
+ if($conv_id) {
+ $c = q("select parent_mid from item left join iconfig on item.id = iconfig.iid where iconfig.cat = 'ostatus' and iconfig.k = 'conversation' and iconfig.v = '%s' and item.uid = %d order by item.id limit 1",
+ dbesc($conv_id),
+ intval($importer['channel_id'])
+ );
+ if($c) {
+ $pmid = $c[0]['parent_mid'];
+ $datarray['parent_mid'] = $pmid;
+ }
+ }
+ if(! $pmid) {
+ $x = q("select parent_mid from item where mid = '%s' and uid = %d limit 1",
+ dbesc($parent_mid),
+ intval($importer['channel_id'])
+ );
+
+ if($x) {
+ $pmid = $x[0]['parent_mid'];
+ $datarray['parent_mid'] = $pmid;
+ }
+ }
+ }
+
+ // the conversation parent might just be the post we are trying to import.
+ // check existence again in case it was just delivered.
- $datarray['parent_mid'] = $parent_mid;
+ $r = q("SELECT id FROM item WHERE mid = '%s' AND uid = %d LIMIT 1",
+ dbesc($datarray['mid']),
+ intval($importer['channel_id'])
+ );
+ if($r) {
+ continue;
+ }
+ }
+
+ if($pmid) {
+
+ // check comment permissions on the parent
+
+ $parent_item = 0;
+
+ $r = q("select * from item where parent_mid = '%s' and parent_mid = mid and uid = %d limit 1",
+ dbesc($pmid),
+ intval($importer['channel_id'])
+ );
+ if($r) {
+ $parent_item = $r[0];
+ if(intval($parent_item['item_nocomment']) || $parent_item['comment_policy'] === 'none'
+ || ($parent_item['comments_closed'] > NULL_DATE && $parent_item['comments_closed'] < datetime_convert())) {
+ logger('comments disabled for post ' . $parent_item['mid']);
+ continue;
+ }
+ }
+
+ $allowed = false;
+
+ if($parent_item) {
+ if($parent_item['owner_xchan'] == $importer['channel_hash'])
+ $allowed = perm_is_allowed($importer['channel_id'],$contact['xchan_hash'],'post_comments');
+ else
+ $allowed = true;
+
+ if(! $allowed) {
+ logger('Ignoring this comment author.');
+ $status = 202;
+ continue;
+ }
+
+ // The salmon endpoint sets this to indicate that we should send comments from
+ // interactive feeds (such as OStatus) downstream to our followers
+ // We do not want to set it for non-interactive feeds or conversations we do not own
+
+ if(array_key_exists('send_downstream',$importer) && intval($importer['send_downstream'])
+ && ($parent_item['owner_xchan'] == $importer['channel_hash'])) {
+ $send_downstream = true;
+ }
+ }
+ else {
+ if((! perm_is_allowed($importer['channel_id'],$contact['xchan_hash'],'send_stream')) && (! $importer['system'])) {
+ // @fixme check for and process ostatus autofriend
+ // otherwise
+
+ logger('Ignoring this author.');
+ continue;
+ }
+ }
+ }
+ else {
+ // immediate parent wasn't found. Turn into a top-level post if permissions allow
+ // but save the thread_parent in case we need to refer to it later.
+
+ if(! post_is_importable($datarray, $contact))
+ continue;
+ $datarray['parent_mid'] = $datarray['mid'];
+ set_iconfig($datarray,'system','parent_mid',$parent_mid,true);
+ }
+
$datarray['aid'] = $importer['channel_account_id'];
$datarray['uid'] = $importer['channel_id'];
@@ -923,24 +1244,48 @@ function consume_feed($xml, $importer, &$contact, $pass = 0) {
$xx = item_store($datarray);
$r = $xx['item_id'];
+
+ if($send_downstream) {
+ \Zotlabs\Daemon\Master::Summon(array('Notifier', 'comment', $r));
+ }
+
continue;
}
else {
// Head post of a conversation. Have we seen it? If not, import it.
- $item_id = normalise_id($item->get_id());
$author = array();
$datarray = get_atom_elements($feed,$item,$author);
- if($datarray['mid'])
- $datarray['mid'] = normalise_id($item->get_id());
+ if(! $datarray['mid'])
+ continue;
+
+ // This probably isn't an appropriate default but we're about to change it
+ // if it's wrong.
+
+ $datarray['comment_policy'] = 'authenticated';
+
+ // A Mastodon privacy tag has been found. We cannot send private comments
+ // through the OStatus protocol, so block commenting.
+
+ if(array_key_exists('item_private',$datarray) && intval($datarray['item_private'])) {
+ $datarray['public_policy'] = 'specific';
+ $datarray['comment_policy'] = 'none';
+ }
if($contact['xchan_network'] === 'rss') {
$datarray['public_policy'] = 'specific';
$datarray['comment_policy'] = 'none';
}
+
+
+ // if we have everything but a photo, provide the default profile photo
+
+ if($author['author_name'] && $author['author_link'] && (! $author['author_photo']))
+ $author['author_photo'] = z_root() . '/' . get_default_profile_photo(80);
+
if(is_array($contact)) {
if((! x($author,'author_name')) || ($author['author_is_feed']))
$author['author_name'] = $contact['xchan_name'];
@@ -957,19 +1302,17 @@ function consume_feed($xml, $importer, &$contact, $pass = 0) {
$datarray['author_xchan'] = '';
- if(activity_match($datarray['verb'],ACTIVITY_FOLLOW) && $datarray['obj_type'] === ACTIVITY_OBJ_PERSON) {
- $cb = array('item' => $datarray,'channel' => $importer, 'xchan' => [ 'placeholder' => '' ], 'author' => $author, 'caught' => false);
- call_hooks('follow_from_feed',$cb);
- if($cb['caught']) {
- if($cb['return_code'])
- http_status_exit($cb['return_code']);
-
- continue;
- }
- }
-
if($author['author_link'] != $contact['xchan_url']) {
- $x = import_author_unknown(array('name' => $author['author_name'],'url' => $author['author_link'],'photo' => array('src' => $author['author_photo'])));
+ $name = '';
+ if($author['full_name']) {
+ $name = $author['full_name'];
+ if($author['author_name'])
+ $name .= ' (' . $author['author_name'] . ')';
+ }
+ else {
+ $name = $author['author_name'];
+ }
+ $x = import_author_unknown(array('name' => $name,'url' => $author['author_link'],'photo' => array('src' => $author['author_photo'])));
if($x)
$datarray['author_xchan'] = $x;
}
@@ -988,8 +1331,8 @@ function consume_feed($xml, $importer, &$contact, $pass = 0) {
}
- $r = q("SELECT edited FROM item WHERE mid = '%s' AND uid = %d LIMIT 1",
- dbesc($item_id),
+ $r = q("SELECT id, edited FROM item WHERE mid = '%s' AND uid = %d LIMIT 1",
+ dbesc($datarray['mid']),
intval($importer['channel_id'])
);
@@ -1003,13 +1346,17 @@ function consume_feed($xml, $importer, &$contact, $pass = 0) {
if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
continue;
+ $datarray['uid'] = $importer['channel_id'];
+ $datarray['aid'] = $importer['channel_account_id'];
+ $datarray['id'] = $r[0]['id'];
+
update_feed_item($importer['channel_id'],$datarray);
}
continue;
}
- $datarray['parent_mid'] = $item_id;
+ $datarray['parent_mid'] = $datarray['mid'];
$datarray['uid'] = $importer['channel_id'];
$datarray['aid'] = $importer['channel_account_id'];
@@ -1035,6 +1382,51 @@ function consume_feed($xml, $importer, &$contact, $pass = 0) {
}
}
+
+function feed_conversation_fetch($importer,$contact,$parent_link) {
+
+ logger('parent_link: ' . $parent_link, LOGGER_DEBUG, LOG_INFO);
+
+ $link = '';
+
+ // GNU-Social flavoured feeds
+ if(strpos($parent_link,'/notice/')) {
+ $link = str_replace('/notice/','/api/statuses/show/',$parent_link) . '.atom';
+ }
+
+ // Mastodon flavoured feeds
+ if(strpos($parent_link,'/users/') && strpos($parent_link,'/updates/')) {
+ $link = $parent_link . '.atom';
+ }
+
+ if(! $link)
+ return false;
+
+ logger('fetching: ' . $link, LOGGER_DEBUG, LOG_INFO);
+
+ $fetch = z_fetch_url($link);
+
+ if(! $fetch['success'])
+ return false;
+
+ $data = $fetch['body'];
+
+ // We will probably receive an atom 'entry' and not an atom 'feed'. Unfortunately
+ // our parser is a bit strict about compliance so we'll insert just enough of a feed
+ // tag to trick it into believing it's a compliant feed.
+
+ if(! strstr($data,'<feed')) {
+ $data = str_replace('<entry ','<feed xmlns="http://www.w3.org/2005/Atom"><entry ',$data);
+ $data .= '</feed>';
+ }
+
+ consume_feed($data,$importer,$contact,1);
+ consume_feed($data,$importer,$contact,2);
+
+ return true;
+
+}
+
/**
* @brief Normalise an id.
*
@@ -1112,7 +1504,7 @@ function process_salmon_feed($xml, $importer) {
// reset policies which are restricted by default for RSS connections
// This item is likely coming from GNU-social via salmon and allows public interaction
$datarray['public_policy'] = '';
- $datarray['comment_policy'] = '';
+ $datarray['comment_policy'] = 'authenticated';
$ret['item'] = $datarray;
}
@@ -1179,6 +1571,10 @@ function feed_meta($xml) {
}
}
+ if(! $author['author_photo'])
+ $author['author_photo'] = $feed->get_image_url();
+
+
if(substr($author['author_link'],-1,1) == '/')
$author['author_link'] = substr($author['author_link'],0,-1);
@@ -1194,7 +1590,7 @@ function feed_meta($xml) {
* @param array $datarray
*/
function update_feed_item($uid, $datarray) {
- logger('Not implemented! ' . $uid . ' ' . print_r($datarray, true), LOGGER_DATA);
+ item_store_update($datarray);
}
/**
@@ -1272,6 +1668,59 @@ function atom_author($tag, $nick, $name, $uri, $h, $w, $type, $photo) {
return $o;
}
+
+function atom_render_author($tag,$xchan) {
+
+
+ $nick = xmlify(substr($xchan['xchan_addr'],0,strpos($xchan['xchan_addr'],'@')));
+ $id = xmlify($xchan['xchan_url']);
+ $name = xmlify($xchan['xchan_name']);
+ $photo = xmlify($xchan['xchan_photo_l']);
+ $type = xmlify($xchan['xchan_photo_mimetype']);
+ $w = $h = 300;
+
+ $o .= "<$tag>\r\n";
+ $o .= " <as:object-type>http://activitystrea.ms/schema/1.0/person</as:object-type>\r\n";
+ $o .= " <id>$id</id>\r\n";
+ $o .= " <name>$nick</name>\r\n";
+ $o .= " <uri>$id</uri>\r\n";
+ $o .= ' <link rel="alternate" type="text/html" href="' . $id . '" />' . "\r\n";
+ $o .= ' <link rel="photo" type="' . $type . '" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
+ $o .= ' <link rel="avatar" type="' . $type . '" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
+ $o .= ' <poco:preferredUsername>' . $nick . '</poco:preferredUsername>' . "\r\n";
+ $o .= ' <poco:displayName>' . $name . '</poco:displayName>' . "\r\n";
+
+ call_hooks('atom_render_author', $o);
+
+ $o .= "</$tag>\r\n";
+
+ return $o;
+
+
+}
+
+function compat_photos_list($s) {
+
+ $ret = [];
+
+ $found = preg_match_all('/\[[zi]mg(.*?)\](.*?)\[/ism',$s,$matches,PREG_SET_ORDER);
+
+ if($found) {
+ foreach($matches as $match) {
+ $ret[] = [
+ 'href' => $match[2],
+ 'length' => 0,
+ 'type' => guess_image_type($match[2])
+ ];
+
+ }
+ }
+
+ return $ret;
+}
+
+
+
/**
* @brief Create an item for the Atom feed.
*
@@ -1285,7 +1734,8 @@ function atom_author($tag, $nick, $name, $uri, $h, $w, $type, $photo) {
* @param number $cid default 0
* @return void|string
*/
-function atom_entry($item, $type, $author, $owner, $comment = false, $cid = 0) {
+function atom_entry($item, $type, $author, $owner, $comment = false, $cid = 0, $compat = false) {
+
if(! $item['parent'])
return;
@@ -1300,23 +1750,31 @@ function atom_entry($item, $type, $author, $owner, $comment = false, $cid = 0) {
else
$body = $item['body'];
+ if($compat) {
+ $compat_photos = compat_photos_list($body);
+ }
+ else {
+ $compat_photos = null;
+ }
+
$o = "\r\n\r\n<entry>\r\n";
if(is_array($author)) {
- $reddress = substr($author['xchan_addr'],0,strpos($author['xchan_addr'],'@'));
- $o .= atom_author('author',$reddress,$author['xchan_name'],$author['xchan_url'],80,80,$author['xchan_photo_mimetype'],$author['xchan_photo_m']);
+ $o .= atom_render_author('author',$author);
}
else {
- $reddress = substr($item['author']['xchan_addr'],0,strpos($item['author']['xchan_addr'],'@'));
- $o .= atom_author('author',$reddress,$item['author']['xchan_name'],$item['author']['xchan_url'],80,80,$item['author']['xchan_photo_mimetype'], $item['author']['xchan_photo_m']);
+ $o .= atom_render_author('author',$item['author']);
}
- $reddress = substr($item['owner']['xchan_addr'],0,strpos($item['owner']['xchan_addr'],'@'));
- $o .= atom_author('zot:owner',$reddress,$item['owner']['xchan_name'],$item['owner']['xchan_url'],80,80,$item['owner']['xchan_photo_mimetype'],$item['owner']['xchan_photo_m']);
+ $o .= atom_render_author('zot:owner',$item['owner']);
if(($item['parent'] != $item['id']) || ($item['parent_mid'] !== $item['mid']) || (($item['thr_parent'] !== '') && ($item['thr_parent'] !== $item['mid']))) {
$parent_item = (($item['thr_parent']) ? $item['thr_parent'] : $item['parent_mid']);
- $o .= '<thr:in-reply-to ref="' . 'X-ZOT:' . xmlify($parent_item) . '" type="text/html" href="' . xmlify($item['plink']) . '" />' . "\r\n";
+ // ensure it's a legal uri and not just a message-id
+ if(! strpos($parent_item,':'))
+ $parent_item = 'X-ZOT:' . $parent_item;
+
+ $o .= '<thr:in-reply-to ref="' . xmlify($parent_item) . '" type="text/html" href="' . xmlify($item['plink']) . '" />' . "\r\n";
}
if(activity_match($item['obj_type'],ACTIVITY_OBJ_EVENT) && activity_match($item['verb'],ACTIVITY_POST)) {
@@ -1372,10 +1830,19 @@ function atom_entry($item, $type, $author, $owner, $comment = false, $cid = 0) {
. (($enc['href']) ? 'href="' . $enc['href'] . '" ' : '')
. (($enc['length']) ? 'length="' . $enc['length'] . '" ' : '')
. (($enc['type']) ? 'type="' . $enc['type'] . '" ' : '')
- . ' />';
+ . ' />' . "\r\n";
}
}
}
+ if($compat_photos) {
+ foreach($compat_photos as $enc) {
+ $o .= '<link rel="enclosure" '
+ . (($enc['href']) ? 'href="' . $enc['href'] . '" ' : '')
+ . ((array_key_exists('length',$enc)) ? 'length="' . $enc['length'] . '" ' : '')
+ . (($enc['type']) ? 'type="' . $enc['type'] . '" ' : '')
+ . ' />' . "\r\n";
+ }
+ }
if($item['term']) {
foreach($item['term'] as $term) {
@@ -1427,98 +1894,3 @@ function atom_entry($item, $type, $author, $owner, $comment = false, $cid = 0) {
return $x['entry'];
}
-/**
- * @brief
- *
- * @param array $items
- * @return array
- */
-function gen_asld($items) {
- $ret = array();
- if(! $items)
- return $ret;
-
- foreach($items as $item) {
- $ret[] = i2asld($item);
- }
-
- return $ret;
-}
-
-/**
- * @brief
- *
- * @param array $i
- * @return array
- */
-function i2asld($i) {
-
- if(! $i)
- return array();
-
- $ret = array();
-
- $ret['@context'] = array( 'http://www.w3.org/ns/activitystreams', 'zot' => 'http://purl.org/zot/protocol');
-
- if($i['verb']) {
- if(strpos(dirname($i['verb'],'activitystrea.ms/schema/1.0'))) {
- $ret['@type'] = ucfirst(basename($i['verb']));
- }
- elseif(strpos(dirname($i['verb'],'purl.org/zot'))) {
- $ret['@type'] = 'zot:' . ucfirst(basename($i['verb']));
- }
- }
- $ret['@id'] = $i['plink'];
-
- $ret['published'] = datetime_convert('UTC','UTC',$i['created'],ATOM_TIME);
-
- // we need to pass the parent into this
-// if($i['id'] != $i['parent'] && $i['obj_type'] === ACTIVITY_OBJ_NOTE) {
-// $ret['inReplyTo'] = asencode_note
-// }
-
- if($i['obj_type'] === ACTIVITY_OBJ_NOTE)
- $ret['object'] = asencode_note($i);
-
- $ret['actor'] = asencode_person($i['author']);
-
- return $ret;
-}
-
-function asencode_note($i) {
-
- $ret = array();
-
- $ret['@type'] = 'Note';
- $ret['@id'] = $i['plink'];
- if($i['title'])
- $ret['title'] = bbcode($i['title']);
-
- $ret['content'] = bbcode($i['body']);
- $ret['zot:owner'] = asencode_person($i['owner']);
- $ret['published'] = datetime_convert('UTC','UTC',$i['created'],ATOM_TIME);
- if($i['created'] !== $i['edited'])
- $ret['updated'] = datetime_convert('UTC','UTC',$i['edited'],ATOM_TIME);
-
- return $ret;
-}
-
-
-function asencode_person($p) {
- $ret = array();
- $ret['@type'] = 'Person';
- $ret['@id'] = 'acct:' . $p['xchan_addr'];
- $ret['displayName'] = $p['xchan_name'];
- $ret['icon'] = array(
- '@type' => 'Link',
- 'mediaType' => $p['xchan_photo_mimetype'],
- 'href' => $p['xchan_photo_m']
- );
- $ret['url'] = array(
- '@type' => 'Link',
- 'mediaType' => 'text/html',
- 'href' => $p['xchan_url']
- );
-
- return $ret;
-}