X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=plugins%2FOStatus%2FOStatusPlugin.php;h=7133e43a3cb211d3a2d9b1d1cd3d568ef9aa9a97;hb=2f65fa646acc9a0739e779de9e472b9957c2e7eb;hp=f7fed1f0db130938056ac77e3d90da0e0716332d;hpb=46f90f7b08381eec80c2e895d061af15b5e1c729;p=quix0rs-gnu-social.git diff --git a/plugins/OStatus/OStatusPlugin.php b/plugins/OStatus/OStatusPlugin.php index f7fed1f0db..7133e43a3c 100644 --- a/plugins/OStatus/OStatusPlugin.php +++ b/plugins/OStatus/OStatusPlugin.php @@ -1,17 +1,7 @@ -Author URI: http://status.net/ -*/ - /* * StatusNet - the distributed open-source microblogging tool - * Copyright (C) 2009, StatusNet, Inc. + * Copyright (C) 2009-2010, StatusNet, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by @@ -28,16 +18,13 @@ Author URI: http://status.net/ */ /** - * @package FeedSubPlugin + * @package OStatusPlugin * @maintainer Brion Vibber */ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } -define('FEEDSUB_SERVICE', 100); // fixme -- avoid hardcoding these? - -// We bundle the XML_Parse_Feed library... -set_include_path(get_include_path() . PATH_SEPARATOR . dirname(__FILE__) . '/extlib'); +set_include_path(get_include_path() . PATH_SEPARATOR . dirname(__FILE__) . '/extlib/'); class FeedSubException extends Exception { @@ -63,9 +50,9 @@ class OStatusPlugin extends Plugin $m->connect('main/ostatus?nickname=:nickname', array('action' => 'ostatusinit'), array('nickname' => '[A-Za-z0-9_-]+')); $m->connect('main/ostatussub', - array('action' => 'ostatussub')); + array('action' => 'ostatussub')); $m->connect('main/ostatussub', - array('action' => 'ostatussub'), array('feed' => '[A-Za-z0-9\.\/\:]+')); + array('action' => 'ostatussub'), array('feed' => '[A-Za-z0-9\.\/\:]+')); // PuSH actions $m->connect('main/push/hub', array('action' => 'pushhub')); @@ -73,12 +60,13 @@ class OStatusPlugin extends Plugin $m->connect('main/push/callback/:feed', array('action' => 'pushcallback'), array('feed' => '[0-9]+')); - $m->connect('settings/feedsub', - array('action' => 'feedsubsettings')); // Salmon endpoint $m->connect('main/salmon/user/:id', - array('action' => 'salmon'), + array('action' => 'usersalmon'), + array('id' => '[0-9]+')); + $m->connect('main/salmon/group/:id', + array('action' => 'groupsalmon'), array('id' => '[0-9]+')); return true; } @@ -90,9 +78,13 @@ class OStatusPlugin extends Plugin */ function onEndInitializeQueueManager(QueueManager $qm) { + // Outgoing from our internal PuSH hub $qm->connect('hubverify', 'HubVerifyQueueHandler'); $qm->connect('hubdistrib', 'HubDistribQueueHandler'); $qm->connect('hubout', 'HubOutQueueHandler'); + + // Incoming from a foreign PuSH hub + $qm->connect('pushinput', 'PushInputQueueHandler'); return true; } @@ -109,39 +101,37 @@ class OStatusPlugin extends Plugin * Set up a PuSH hub link to our internal link for canonical timeline * Atom feeds for users and groups. */ - function onStartApiAtom(Action $action) + function onStartApiAtom($feed) { - if ($action instanceof ApiTimelineUserAction || $action instanceof ApiTimelineGroupAction) { - $id = $action->arg('id'); - if (strval(intval($id)) === strval($id)) { - // Canonical form of id in URL? - // Updates will be handled for our internal PuSH hub. - $action->element('link', array('rel' => 'hub', - 'href' => common_local_url('pushhub'))); - - // Also, we'll add in the salmon link - $action->element('link', array('rel' => 'salmon', - 'href' => common_local_url('salmon'))); - } + $id = null; + + if ($feed instanceof AtomUserNoticeFeed) { + $salmonAction = 'usersalmon'; + $user = $feed->getUser(); + $id = $user->id; + $profile = $user->getProfile(); + $feed->setActivitySubject($profile->asActivityNoun('subject')); + } else if ($feed instanceof AtomGroupNoticeFeed) { + $salmonAction = 'groupsalmon'; + $group = $feed->getGroup(); + $id = $group->id; + $feed->setActivitySubject($group->asActivitySubject()); + } else { + return true; } - return true; - } - - /** - * Add the feed settings page to the Connect Settings menu - * - * @param Action &$action The calling page - * - * @return boolean hook return - */ - function onEndConnectSettingsNav(&$action) - { - $action_name = $action->trimmed('action'); - $action->menuItem(common_local_url('feedsubsettings'), - _m('Feeds'), - _m('Feed subscription options'), - $action_name === 'feedsubsettings'); + if (!empty($id)) { + $hub = common_config('ostatus', 'hub'); + if (empty($hub)) { + // Updates will be handled through our internal PuSH hub. + $hub = common_local_url('pushhub'); + } + $feed->addLink($hub, array('rel' => 'hub')); + + // Also, we'll add in the salmon link + $salmon = common_local_url($salmonAction, array('id' => $id)); + $feed->addLink($salmon, array('rel' => 'salmon')); + } return true; } @@ -158,6 +148,12 @@ class OStatusPlugin extends Plugin { $base = dirname(__FILE__); $lower = strtolower($cls); + $map = array('activityverb' => 'activity', + 'activityobject' => 'activity', + 'activityutils' => 'activity'); + if (isset($map[$lower])) { + $lower = $map[$lower]; + } $files = array("$base/classes/$cls.php", "$base/lib/$lower.php"); if (substr($lower, -6) == 'action') { @@ -175,7 +171,7 @@ class OStatusPlugin extends Plugin /** * Add in an OStatus subscribe button */ - function onStartProfilePageActionsElements($output, $profile) + function onStartProfileRemoteSubscribe($output, $profile) { $cur = common_current_user(); @@ -186,63 +182,321 @@ class OStatusPlugin extends Plugin array('nickname' => $profile->nickname)); $output->element('a', array('href' => $url, 'class' => 'entity_remote_subscribe'), - _('OStatus')); - + _m('Subscribe')); + $output->elementEnd('li'); } + + return false; } /** - * Check if we've got some Salmon stuff to send + * Check if we've got remote replies to send via Salmon. + * + * @fixme push webfinger lookup & sending to a background queue + * @fixme also detect short-form name for remote subscribees where not ambiguous */ + function onEndNoticeSave($notice) { - $count = preg_match_all('/(\w+\.)*\w+@(\w+\.)*\w+(\w+\-\w+)*\.\w+/', $notice->content, $matches); - if ($count) { - foreach ($matches[0] as $webfinger) { - // Check to see if we've got an actual webfinger - $w = new Webfinger; - - $endpoint_uri = ''; - - $result = $w->lookup($webfinger); - if (empty($result)) { - continue; - } - - foreach ($result->links as $link) { - if ($link['rel'] == 'salmon') { - $endpoint_uri = $link['href']; - } - } - - if (empty($endpoint_uri)) { - continue; - } - - $profile = $notice->getProfile(); - - $acct = $profile->nickname .'@'. common_config('site', 'server'); - + $mentioned = $notice->getReplies(); + + foreach ($mentioned as $profile_id) { + + $oprofile = Ostatus_profile::staticGet('profile_id', $profile_id); + + if (!empty($oprofile) && !empty($oprofile->salmonuri)) { + + common_log(LOG_INFO, "Sending notice '{$notice->uri}' to remote profile '{$oprofile->uri}'."); + + // FIXME: this needs to go out in a queue handler + $xml = ''; - $xml .= $notice->asAtomEntry(); - // TODO : need to set author/uri to webfinger acct. more cleanly - $xml = preg_replace('/([^<])*<\/uri>/i', 'acct:'.$acct.'', $xml); + $xml .= $notice->asAtomEntry(true, true); - $salmon = new Salmon(); - $salmon->post($endpoint_uri, $xml); + $salmon->post($oprofile->salmonuri, $xml); + } + } + } + + /** + * + */ + + function onEndFindMentions($sender, $text, &$mentions) + { + preg_match_all('/(?:^|\s+)@((?:\w+\.)*\w+@(?:\w+\.)*\w+(?:\w+\-\w+)*\.\w+)/', + $text, + $wmatches, + PREG_OFFSET_CAPTURE); + + foreach ($wmatches[1] as $wmatch) { + + $webfinger = $wmatch[0]; + + $oprofile = Ostatus_profile::ensureWebfinger($webfinger); + + if (!empty($oprofile)) { + + $profile = $oprofile->localProfile(); + + $mentions[] = array('mentioned' => array($profile), + 'text' => $wmatch[0], + 'position' => $wmatch[1], + 'url' => $profile->profileurl); } } + + return true; + } + + /** + * Notify remote server and garbage collect unused feeds on unsubscribe. + * @fixme send these operations to background queues + * + * @param User $user + * @param Profile $other + * @return hook return value + */ + function onEndUnsubscribe($profile, $other) + { + $user = User::staticGet('id', $profile->id); + + if (empty($user)) { + return true; + } + + $oprofile = Ostatus_profile::staticGet('profile_id', $other->id); + + if (empty($oprofile)) { + return true; + } + + // Drop the PuSH subscription if there are no other subscribers. + + if ($other->subscriberCount() == 0) { + common_log(LOG_INFO, "Unsubscribing from now-unused feed $oprofile->feeduri"); + $oprofile->unsubscribe(); + } + + $act = new Activity(); + + $act->verb = ActivityVerb::UNFOLLOW; + + $act->id = TagURI::mint('unfollow:%d:%d:%s', + $profile->id, + $other->id, + common_date_iso8601(time())); + + $act->time = time(); + $act->title = _("Unfollow"); + $act->content = sprintf(_("%s stopped following %s."), + $profile->getBestName(), + $other->getBestName()); + + $act->actor = ActivityObject::fromProfile($profile); + $act->object = ActivityObject::fromProfile($other); + + $oprofile->notifyActivity($act); + + return true; } - - + + /** + * Make sure necessary tables are filled out. + */ function onCheckSchema() { - // warning: the autoincrement doesn't seem to set. - // alter table feedinfo change column id id int(11) not null auto_increment; $schema = Schema::get(); - $schema->ensureTable('feedinfo', Feedinfo::schemaDef()); + $schema->ensureTable('ostatus_profile', Ostatus_profile::schemaDef()); + $schema->ensureTable('ostatus_source', Ostatus_source::schemaDef()); + $schema->ensureTable('feedsub', FeedSub::schemaDef()); $schema->ensureTable('hubsub', HubSub::schemaDef()); return true; - } + } + + function onEndShowStatusNetStyles($action) { + $action->cssLink(common_path('plugins/OStatus/theme/base/css/ostatus.css')); + return true; + } + + function onEndShowStatusNetScripts($action) { + $action->script(common_path('plugins/OStatus/js/ostatus.js')); + return true; + } + + /** + * Override the "from ostatus" bit in notice lists to link to the + * original post and show the domain it came from. + * + * @param Notice in $notice + * @param string out &$name + * @param string out &$url + * @param string out &$title + * @return mixed hook return code + */ + function onStartNoticeSourceLink($notice, &$name, &$url, &$title) + { + if ($notice->source == 'ostatus') { + $bits = parse_url($notice->uri); + $domain = $bits['host']; + + $name = $domain; + $url = $notice->uri; + $title = sprintf(_m("Sent from %s via OStatus"), $domain); + return false; + } + } + + /** + * Send incoming PuSH feeds for OStatus endpoints in for processing. + * + * @param FeedSub $feedsub + * @param DOMDocument $feed + * @return mixed hook return code + */ + function onStartFeedSubReceive($feedsub, $feed) + { + $oprofile = Ostatus_profile::staticGet('feeduri', $feedsub->uri); + if ($oprofile) { + $oprofile->processFeed($feed); + } else { + common_log(LOG_DEBUG, "No ostatus profile for incoming feed $feedsub->uri"); + } + } + + function onEndSubscribe($subscriber, $other) + { + $user = User::staticGet('id', $subscriber->id); + + if (empty($user)) { + return true; + } + + $oprofile = Ostatus_profile::staticGet('profile_id', $other->id); + + if (empty($oprofile)) { + return true; + } + + $act = new Activity(); + + $act->verb = ActivityVerb::FOLLOW; + + $act->id = TagURI::mint('follow:%d:%d:%s', + $subscriber->id, + $other->id, + common_date_iso8601(time())); + + $act->time = time(); + $act->title = _("Follow"); + $act->content = sprintf(_("%s is now following %s."), + $subscriber->getBestName(), + $other->getBestName()); + + $act->actor = ActivityObject::fromProfile($subscriber); + $act->object = ActivityObject::fromProfile($other); + + $oprofile->notifyActivity($act); + + return true; + } + + /** + * Notify remote users when their notices get favorited. + * + * @param Profile or User $profile of local user doing the faving + * @param Notice $notice being favored + * @return hook return value + */ + + function onEndFavorNotice(Profile $profile, Notice $notice) + { + $user = User::staticGet('id', $profile->id); + + if (empty($user)) { + return true; + } + + $oprofile = Ostatus_profile::staticGet('profile_id', $notice->profile_id); + + if (empty($oprofile)) { + return true; + } + + $act = new Activity(); + + $act->verb = ActivityVerb::FAVORITE; + $act->id = TagURI::mint('favor:%d:%d:%s', + $profile->id, + $notice->id, + common_date_iso8601(time())); + + $act->time = time(); + $act->title = _("Favor"); + $act->content = sprintf(_("%s marked notice %s as a favorite."), + $profile->getBestName(), + $notice->uri); + + $act->actor = ActivityObject::fromProfile($profile); + $act->object = ActivityObject::fromNotice($notice); + + $oprofile->notifyActivity($act); + + return true; + } + + /** + * Notify remote users when their notices get de-favorited. + * + * @param Profile $profile Profile person doing the de-faving + * @param Notice $notice Notice being favored + * + * @return hook return value + */ + + function onEndDisfavorNotice(Profile $profile, Notice $notice) + { + $user = User::staticGet('id', $profile->id); + + if (empty($user)) { + return true; + } + + $oprofile = Ostatus_profile::staticGet('profile_id', $notice->profile_id); + + if (empty($oprofile)) { + return true; + } + + $act = new Activity(); + + $act->verb = ActivityVerb::UNFAVORITE; + $act->id = TagURI::mint('disfavor:%d:%d:%s', + $profile->id, + $notice->id, + common_date_iso8601(time())); + $act->time = time(); + $act->title = _("Disfavor"); + $act->content = sprintf(_("%s marked notice %s as no longer a favorite."), + $profile->getBestName(), + $notice->uri); + + $act->actor = ActivityObject::fromProfile($profile); + $act->object = ActivityObject::fromNotice($notice); + + $oprofile->notifyActivity($act); + + return true; + } + + function onStartGetProfileUri($profile, &$uri) + { + $oprofile = Ostatus_profile::staticGet('profile_id', $profile->id); + if (!empty($oprofile)) { + $uri = $oprofile->uri; + return false; + } + return true; + } }