mirror of
https://codeberg.org/streams/streams.git
synced 2024-09-19 22:35:18 +00:00
implement Activity::store
This commit is contained in:
parent
2c1f8d894e
commit
4c1bfdfae8
2 changed files with 238 additions and 44 deletions
39
README.md
39
README.md
|
@ -1,46 +1,23 @@
|
|||
**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.
|
||||
|
||||
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.
|
||||
|
||||
|
||||
01-August-2018
|
||||
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.
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
**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;
|
||||
|
||||
|
||||
/**
|
||||
* 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']));
|
||||
|
||||
if($i['title'])
|
||||
$ret['title'] = bbcode($i['title']);
|
||||
|
||||
$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) {
|
||||
|
||||
|
|
Loading…
Reference in a new issue