Commit 4c1bfdfa authored by Mike Macgirvin's avatar Mike Macgirvin

implement Activity::store

parent 2c1f8d89
**ZAP**
**OSADA**
Zap is a social networking app running under the Zot/6 protocol and the LAMP web stack.
Osada is a social networking app and gateway which allows communication between the Zot/6 protocol/network to the ActivityPub protocol/network using the LAMP web stack.
Protocol documentation is located here:
Zot/6 protocol documentation is located here:
https://macgirvin.com/wiki/mike/Zot%2BVI/Home
Zap is based on Red, which in turn is based on Hubzilla. It is otherwise unrelated to those projects and the software has a completely different scope and purpose.
ActivityPub and many other protocols do not work well with Zot or Zot/6 because they do not understand the concept of nomadic identity. Osada provides a gateway between these services to smooth out the differences. It is ultimately designed to bridge ActivityPub with Zap, which is a pure Zot/6 social network application.
01-August-2018
==============
Most of the basic functionality is now present and working. This is still "use at your own risk", but it shouldn't burn down the house.
19-July-2018
============
There is a lot more work yet to be done, but the basic Zap application is nearing alpha quality.
TODO before alpha release:
* convert mail to ActivityStreams
* test nomadic channels
* correct any links in the documentation and remove the descriptions of Hubzilla features which do not exist in Zap
Osada is based on Zap which is based on Red, which in turn is based on Hubzilla. It is otherwise unrelated to those projects and the software has a completely different scope and purpose.
20-August-2018
==============
Most of the basic functionality is now present and working. This is still "use at your own risk", but it shouldn't burn down the house.
**Things you should know**
Zap is nomadic and does not federate with any other platform or protocol currently. It will only **ever** federate with nomadic-aware services/protocols. Full stop. Full federation support will eventually be provided by creating bridging identities in a companion project Osada; which provides a bridge between nomadic and non-nomadic networks. Osada identities cannot be nomadic since they federate with non-nomadic services, but they can be linked to Zap nomadic identities using Zot6 identity linking.
If you are looking for a specific Hubzilla feature, you came to the wrong place.
If you are looking for ActivityPub support, you came to the wrong place.
If you are looking for stable software, check back in a few months.
If you encounter issues, fix them and submit a pull request.
Pull requests which add unnecessary features will be ignored. These should be implemented using apps and/or addons.
......
......@@ -149,7 +149,7 @@ class Activity {
static function encode_item($i) {
static function encode_item($i, $activitypub = false) {
$ret = [];
......@@ -164,14 +164,43 @@ class Activity {
$ret['type'] = $objtype;
$ret['id'] = ((strpos($i['mid'],'http') === 0) ? $i['mid'] : z_root() . '/item/' . urlencode($i['mid']));
if($i['title'])
$ret['title'] = bbcode($i['title']);
/**
* If the destination is activitypub, see if the content needs conversion to
* Mastodon "quirks" mode. This will be the case if there is any markup beyond
* links or images OR if the number of images exceeds 1. This content may be
* purified into oblivion when using the Note type so turn it into an Article.
*/
$convert_to_article = false;
$images = false;
if($activitypub && $ret['type'] === 'Note') {
$bbtags = false;
$num_bbtags = preg_match_all('/\[([a-z]?)(.*?)/ism',$i['body'],$bbtags,PREG_SET_ORDER);
if($num_bbtags) {
foreach($bbtags as $t) {
if(in_array($t[1],['url','zrl','img','zmg'])) {
continue;
}
$convert_to_article = true;
}
}
$has_images = preg_match_all('/\[[zi]mg(.*?)\](.*?)\[/ism',$i['body'],$images,PREG_SET_ORDER);
if($has_images > 1) {
$convert_to_article = true;
}
if($convert_to_article) {
$ret['type'] = 'Article';
}
}
$ret['id'] = ((strpos($i['mid'],'http') === 0) ? $i['mid'] : z_root() . '/item/' . urlencode($i['mid']));
$ret['published'] = datetime_convert('UTC','UTC',$i['created'],ATOM_TIME);
if($i['created'] !== $i['edited'])
if($i['created'] !== $i['edited']) {
$ret['updated'] = datetime_convert('UTC','UTC',$i['edited'],ATOM_TIME);
}
if($i['app']) {
$ret['instrument'] = [ 'type' => 'Service', 'name' => $i['app'] ];
}
......@@ -195,11 +224,11 @@ class Activity {
if($i['mimetype'] === 'text/bbcode') {
if($i['title'])
$ret['name'] = bbcode($i['title']);
$ret['name'] = $i['title'];
if($i['summary'])
$ret['summary'] = bbcode($i['summary']);
$ret['content'] = bbcode($i['body']);
$ret['source'] = [ 'content' => $i['body'], 'mediaType' => 'text/bbcode' ];
$ret['source'] = [ 'content' => $i['body'], 'summary' => $i['summary'], 'mediaType' => 'text/bbcode' ];
}
$actor = self::encode_person($i['author'],false);
......@@ -218,6 +247,17 @@ class Activity {
$ret['attachment'] = $a;
}
if($activitypub && $has_images && $ret['type'] === 'Note') {
$img = [];
foreach($images as $match) {
$img[] = [ 'type' => 'Image', 'url' => $match[2] ];
}
if(! $ret['attachment'])
$ret['attachment'] = [];
$ret['attachment'] = array_merge($img,$ret['attachment']);
}
return $ret;
}
......@@ -333,7 +373,7 @@ class Activity {
static function encode_activity($i) {
static function encode_activity($i,$activitypub = false) {
$ret = [];
$reply = false;
......@@ -348,11 +388,13 @@ class Activity {
$ret['type'] = self::activity_mapper($i['verb']);
$ret['id'] = ((strpos($i['mid'],'http') === 0) ? $i['mid'] : z_root() . '/activity/' . urlencode($i['mid']));
if($i['title'])
$ret['name'] = html2plain(bbcode($i['title']));
if($i['title']) {
$ret['name'] = $i['title'];
}
if($i['summary'])
if($i['summary']) {
$ret['summary'] = bbcode($i['summary']);
}
if($ret['type'] === 'Announce') {
$tmp = preg_replace('/\[share(.*?)\[\/share\]/ism',EMPTY_STR, $i['body']);
......@@ -442,6 +484,62 @@ class Activity {
return [];
}
if($activitypub) {
if($i['item_private']) {
if($reply) {
if($i['author_xchan'] === $i['owner_xchan']) {
$m = self::map_acl($i,(($i['allow_gid']) ? false : true));
$ret['tag'] = (($ret['tag']) ? array_merge($ret['tag'],$m) : $m);
}
else {
if($is_directmessage) {
$m = [
'type' => 'Mention',
'href' => $reply_url,
'name' => '@' . $reply_addr
];
$ret['tag'] = (($ret['tag']) ? array_merge($ret['tag'],$m) : $m);
}
else {
$ret['to'] = [ $reply_url ];
}
}
}
else {
/* Add mentions only if the targets are individuals */
$m = self::map_acl($i,(($i['allow_gid']) ? false : true));
$ret['tag'] = (($ret['tag']) ? array_merge($ret['tag'],$m) : $m);
}
}
else {
if($reply) {
$ret['to'] = [ z_root() . '/followers/' . substr($i['author']['xchan_addr'],0,strpos($i['author']['xchan_addr'],'@')) ];
$ret['cc'] = [ ACTIVITY_PUBLIC_INBOX ];
}
else {
$ret['to'] = [ ACTIVITY_PUBLIC_INBOX ];
$ret['cc'] = [ z_root() . '/followers/' . substr($i['author']['xchan_addr'],0,strpos($i['author']['xchan_addr'],'@')) ];
}
}
$mentions = self::map_mentions($i);
if(count($mentions) > 0) {
if(! $ret['cc']) {
$ret['cc'] = $mentions;
}
else {
$ret['cc'] = array_merge($ret['cc'], $mentions);
}
}
if($ret['to'])
$ret['object']['to'] = $ret['to'];
if($ret['cc'])
$ret['object']['cc'] = $ret['cc'];
if($ret['tag'])
$ret['object']['tag'] = $ret['tag'];
}
return $ret;
}
......@@ -466,6 +564,7 @@ class Activity {
$private = false;
$list = [];
$x = collect_recipients($i,$private);
if($x) {
stringify_array_elms($x);
if(! $x)
......@@ -494,7 +593,7 @@ class Activity {
}
static function encode_person($p, $extended = true) {
static function encode_person($p, $extended = true, $activitypub = false) {
if(! $p['xchan_url'])
return [];
......@@ -531,7 +630,33 @@ class Activity {
]
];
$arr = [ 'xchan' => $p, 'encoded' => $ret ];
$c = channelx_by_hash($p['xchan_hash']);
if($c) {
$ret['inbox'] = z_root() . '/inbox/' . $c['channel_address'];
$ret['outbox'] = z_root() . '/outbox/' . $c['channel_address'];
$ret['followers'] = z_root() . '/followers/' . $c['channel_address'];
$ret['following'] = z_root() . '/following/' . $c['channel_address'];
$ret['endpoints'] = [ 'sharedInbox' => z_root() . '/inbox' ];
$ret['publicKey'] = [
'id' => $p['xchan_url'] . '/public_key_pem',
'owner' => $p['xchan_url'],
'publicKeyPem' => $p['xchan_pubkey']
];
}
else {
$collections = get_xconfig($p['xchan_hash'],'activitystreams','collections',[]);
if($collections) {
$ret = array_merge($ret,$collections);
}
else {
$ret['inbox'] = null;
$ret['outbox'] = null;
}
}
$arr = [ 'xchan' => $p, 'encoded' => $ret, 'activitypub' => $activitypub ];
call_hooks('encode_person', $arr);
$ret = $arr['encoded'];
......@@ -1372,6 +1497,98 @@ class Activity {
}
static function store($channel,$observer_hash,$act,$item) {
$is_sys_channel = is_sys_channel($channel['channel_id']);
// Mastodon only allows visibility in public timelines if the public inbox is listed in the 'to' field.
// They are hidden in the public timeline if the public inbox is listed in the 'cc' field.
// This is not part of the activitypub protocol - we might change this to show all public posts in pubstream at some point.
$pubstream = ((is_array($act->obj) && array_key_exists('to', $act->obj) && in_array(ACTIVITY_PUBLIC_INBOX, $act->obj['to'])) ? true : false);
if(! perm_is_allowed($channel['channel_id'],$observer_hash,'send_stream') && ! ($is_sys_channel && $pubstream)) {
logger('no permission');
return;
}
$content = self::get_content($act->obj);
if(! $content) {
logger('no content');
return;
}
$item['aid'] = $channel['channel_account_id'];
$item['uid'] = $channel['channel_id'];
if($channel['channel_system']) {
if(! \Zotlabs\Lib\MessageFilter::evaluate($item,get_config('system','pubstream_incl'),get_config('system','pubstream_excl'))) {
logger('post is filtered');
return;
}
}
$abook = q("select * from abook where abook_xchan = '%s' and abook_channel = %d limit 1",
dbesc($observer_hash),
intval($channel['channel_id'])
);
if($abook) {
if(! post_is_importable($item,$abook[0])) {
logger('post is filtered');
return;
}
}
if($act->obj['conversation']) {
set_iconfig($item,'ostatus','conversation',$act->obj['conversation'],1);
}
set_iconfig($item,'activitypub','recips',$act->raw_recips);
$r = q("select created, edited from item where mid = '%s' and uid = %d limit 1",
dbesc($item['mid']),
intval($item['uid'])
);
if($r) {
if($item['edited'] > $r[0]['edited']) {
$x = item_store_update($item);
}
else {
return;
}
}
else {
$x = item_store($item);
}
if(is_array($x) && $x['item_id']) {
if($parent) {
if($item['owner_xchan'] === $channel['channel_hash']) {
// We are the owner of this conversation, so send all received comments back downstream
Zotlabs\Daemon\Master::Summon(array('Notifier','comment-import',$x['item_id']));
}
$r = q("select * from item where id = %d limit 1",
intval($x['item_id'])
);
if($r) {
send_status_notifications($x['item_id'],$r[0]);
}
}
sync_an_item($channel['channel_id'],$x['item_id']);
}
}
static function announce_note($channel,$observer_hash,$act) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment