Merge pull request #12156 from MrPetovan/task/4090-move-mod-pubsubhubbub

Move remainder of OStatus stack to src/Module
This commit is contained in:
Philipp 2022-11-14 20:04:35 +01:00 committed by GitHub
commit 0eaa2eae84
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 867 additions and 771 deletions

View File

@ -1,131 +0,0 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* 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 the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
use Friendica\App;
use Friendica\Core\Protocol;
use Friendica\DI;
use Friendica\Model\APContact;
use Friendica\Model\Contact;
use Friendica\Network\HTTPClient\Client\HttpClientAccept;
use Friendica\Protocol\ActivityPub;
function ostatus_subscribe_content(App $a): string
{
if (!DI::userSession()->getLocalUserId()) {
DI::sysmsg()->addNotice(DI::l10n()->t('Permission denied.'));
DI::baseUrl()->redirect('ostatus_subscribe');
// NOTREACHED
}
$o = '<h2>' . DI::l10n()->t('Subscribing to contacts') . '</h2>';
$uid = DI::userSession()->getLocalUserId();
$counter = intval($_REQUEST['counter'] ?? 0);
if (DI::pConfig()->get($uid, 'ostatus', 'legacy_friends') == '') {
if ($_REQUEST['url'] == '') {
DI::pConfig()->delete($uid, 'ostatus', 'legacy_contact');
return $o . DI::l10n()->t('No contact provided.');
}
$contact = Contact::getByURL($_REQUEST['url']);
if (!$contact) {
DI::pConfig()->delete($uid, 'ostatus', 'legacy_contact');
return $o . DI::l10n()->t('Couldn\'t fetch information for contact.');
}
if ($contact['network'] == Protocol::OSTATUS) {
$api = $contact['baseurl'] . '/api/';
// Fetching friends
$curlResult = DI::httpClient()->get($api . 'statuses/friends.json?screen_name=' . $contact['nick'], HttpClientAccept::JSON);
if (!$curlResult->isSuccess()) {
DI::pConfig()->delete($uid, 'ostatus', 'legacy_contact');
return $o . DI::l10n()->t('Couldn\'t fetch friends for contact.');
}
$friends = $curlResult->getBody();
if (empty($friends)) {
DI::pConfig()->delete($uid, 'ostatus', 'legacy_contact');
return $o . DI::l10n()->t('Couldn\'t fetch following contacts.');
}
DI::pConfig()->set($uid, 'ostatus', 'legacy_friends', $friends);
} elseif ($apcontact = APContact::getByURL($contact['url'])) {
if (empty($apcontact['following'])) {
DI::pConfig()->delete($uid, 'ostatus', 'legacy_contact');
return $o . DI::l10n()->t('Couldn\'t fetch remote profile.');
}
$followings = ActivityPub::fetchItems($apcontact['following']);
if (empty($followings)) {
DI::pConfig()->delete($uid, 'ostatus', 'legacy_contact');
return $o . DI::l10n()->t('Couldn\'t fetch following contacts.');
}
DI::pConfig()->set($uid, 'ostatus', 'legacy_friends', json_encode($followings));
} else {
DI::pConfig()->delete($uid, 'ostatus', 'legacy_contact');
return $o . DI::l10n()->t('Unsupported network');
}
}
$friends = json_decode(DI::pConfig()->get($uid, 'ostatus', 'legacy_friends'));
if (empty($friends)) {
$friends = [];
}
$total = sizeof($friends);
if ($counter >= $total) {
DI::page()['htmlhead'] = '<meta http-equiv="refresh" content="0; URL=' . DI::baseUrl() . '/settings/connectors">';
DI::pConfig()->delete($uid, 'ostatus', 'legacy_friends');
DI::pConfig()->delete($uid, 'ostatus', 'legacy_contact');
$o .= DI::l10n()->t('Done');
return $o;
}
$friend = $friends[$counter++];
$url = $friend->statusnet_profile_url ?? $friend;
$o .= '<p>' . $counter . '/' . $total . ': ' . $url;
$probed = Contact::getByURL($url);
if (in_array($probed['network'], Protocol::FEDERATED)) {
$result = Contact::createFromProbeForUser($a->getLoggedInUserId(), $probed['url']);
if ($result['success']) {
$o .= ' - ' . DI::l10n()->t('success');
} else {
$o .= ' - ' . DI::l10n()->t('failed');
}
} else {
$o .= ' - ' . DI::l10n()->t('ignored');
}
$o .= '</p>';
$o .= '<p>' . DI::l10n()->t('Keep this window open until done.') . '</p>';
DI::page()['htmlhead'] = '<meta http-equiv="refresh" content="0; URL=' . DI::baseUrl() . '/ostatus_subscribe?counter=' . $counter . '">';
return $o;
}

View File

@ -1,234 +0,0 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* 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 the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* @see https://web.archive.org/web/20160405005550/http://portablecontacts.net/draft-spec.html
*/
use Friendica\App;
use Friendica\Content\Text\BBCode;
use Friendica\Core\Logger;
use Friendica\Core\Protocol;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Util\DateTimeFormat;
function poco_init(App $a) {
if (intval(DI::config()->get('system', 'block_public')) || (DI::config()->get('system', 'block_local_dir'))) {
throw new \Friendica\Network\HTTPException\ForbiddenException();
}
if (DI::args()->getArgc() > 1) {
// Only the system mode is supported
throw new \Friendica\Network\HTTPException\NotFoundException();
}
$format = ($_GET['format'] ?? '') ?: 'json';
$totalResults = DBA::count('profile', ['net-publish' => true]);
if ($totalResults == 0) {
throw new \Friendica\Network\HTTPException\ForbiddenException();
}
if (!empty($_GET['startIndex'])) {
$startIndex = intval($_GET['startIndex']);
} else {
$startIndex = 0;
}
$itemsPerPage = (!empty($_GET['count']) ? intval($_GET['count']) : $totalResults);
Logger::info("Start system mode query");
$contacts = DBA::selectToArray('owner-view', [], ['net-publish' => true], ['limit' => [$startIndex, $itemsPerPage]]);
Logger::info("Query done");
$ret = [];
if (!empty($_GET['sorted'])) {
$ret['sorted'] = false;
}
if (!empty($_GET['filtered'])) {
$ret['filtered'] = false;
}
if (!empty($_GET['updatedSince'])) {
$ret['updatedSince'] = false;
}
$ret['startIndex'] = (int) $startIndex;
$ret['itemsPerPage'] = (int) $itemsPerPage;
$ret['totalResults'] = (int) $totalResults;
$ret['entry'] = [];
$fields_ret = [
'id' => false,
'displayName' => false,
'urls' => false,
'updated' => false,
'preferredUsername' => false,
'photos' => false,
'aboutMe' => false,
'currentLocation' => false,
'network' => false,
'tags' => false,
'address' => false,
'contactType' => false,
'generation' => false
];
if (empty($_GET['fields'])) {
foreach ($fields_ret as $k => $v) {
$fields_ret[$k] = true;
}
} else {
$fields_req = explode(',', $_GET['fields']);
foreach ($fields_req as $f) {
$fields_ret[trim($f)] = true;
}
}
if (!is_array($contacts)) {
throw new \Friendica\Network\HTTPException\InternalServerErrorException();
}
if (DBA::isResult($contacts)) {
foreach ($contacts as $contact) {
if (!isset($contact['updated'])) {
$contact['updated'] = '';
}
if (! isset($contact['generation'])) {
$contact['generation'] = 1;
}
if (($contact['keywords'] == "") && isset($contact['pub_keywords'])) {
$contact['keywords'] = $contact['pub_keywords'];
}
if (isset($contact['account-type'])) {
$contact['contact-type'] = $contact['account-type'];
}
$cacheKey = 'about:' . $contact['nick'] . ':' . DateTimeFormat::utc($contact['updated'], DateTimeFormat::ATOM);
$about = DI::cache()->get($cacheKey);
if (is_null($about)) {
$about = BBCode::convertForUriId($contact['uri-id'], $contact['about']);
DI::cache()->set($cacheKey, $about);
}
// Non connected persons can only see the keywords of a Diaspora account
if ($contact['network'] == Protocol::DIASPORA) {
$contact['location'] = "";
$about = "";
}
$entry = [];
if ($fields_ret['id']) {
$entry['id'] = (int)$contact['id'];
}
if ($fields_ret['displayName']) {
$entry['displayName'] = $contact['name'];
}
if ($fields_ret['aboutMe']) {
$entry['aboutMe'] = $about;
}
if ($fields_ret['currentLocation']) {
$entry['currentLocation'] = $contact['location'];
}
if ($fields_ret['generation']) {
$entry['generation'] = (int)$contact['generation'];
}
if ($fields_ret['urls']) {
$entry['urls'] = [['value' => $contact['url'], 'type' => 'profile']];
if ($contact['addr'] && ($contact['network'] !== Protocol::MAIL)) {
$entry['urls'][] = ['value' => 'acct:' . $contact['addr'], 'type' => 'webfinger'];
}
}
if ($fields_ret['preferredUsername']) {
$entry['preferredUsername'] = $contact['nick'];
}
if ($fields_ret['updated']) {
$entry['updated'] = $contact['success_update'];
if ($contact['name-date'] > $entry['updated']) {
$entry['updated'] = $contact['name-date'];
}
if ($contact['uri-date'] > $entry['updated']) {
$entry['updated'] = $contact['uri-date'];
}
if ($contact['avatar-date'] > $entry['updated']) {
$entry['updated'] = $contact['avatar-date'];
}
$entry['updated'] = date("c", strtotime($entry['updated']));
}
if ($fields_ret['photos']) {
$entry['photos'] = [['value' => $contact['photo'], 'type' => 'profile']];
}
if ($fields_ret['network']) {
$entry['network'] = $contact['network'];
if ($entry['network'] == Protocol::STATUSNET) {
$entry['network'] = Protocol::OSTATUS;
}
if (($entry['network'] == "") && ($contact['self'])) {
$entry['network'] = Protocol::DFRN;
}
}
if ($fields_ret['tags']) {
$tags = str_replace(",", " ", $contact['keywords']);
$tags = explode(" ", $tags);
$cleaned = [];
foreach ($tags as $tag) {
$tag = trim(strtolower($tag));
if ($tag != "") {
$cleaned[] = $tag;
}
}
$entry['tags'] = [$cleaned];
}
if ($fields_ret['address']) {
$entry['address'] = [];
if (isset($contact['locality'])) {
$entry['address']['locality'] = $contact['locality'];
}
if (isset($contact['region'])) {
$entry['address']['region'] = $contact['region'];
}
if (isset($contact['country'])) {
$entry['address']['country'] = $contact['country'];
}
}
if ($fields_ret['contactType']) {
$entry['contactType'] = intval($contact['contact-type']);
}
$ret['entry'][] = $entry;
}
} else {
$ret['entry'][] = [];
}
Logger::info("End of poco");
if ($format === 'json') {
System::jsonExit($ret);
} else {
throw new \Friendica\Network\HTTPException\UnsupportedMediaTypeException();
}
}

View File

@ -1,158 +0,0 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* 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 the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
use Friendica\App;
use Friendica\Core\Logger;
use Friendica\Core\Protocol;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Contact;
use Friendica\Protocol\OStatus;
use Friendica\Util\Strings;
use Friendica\Util\Network;
use Friendica\Model\GServer;
use Friendica\Model\Post;
function hub_return($valid, $body)
{
if ($valid) {
echo $body;
} else {
throw new \Friendica\Network\HTTPException\NotFoundException();
}
System::exit();
}
// when receiving an XML feed, always return OK
function hub_post_return()
{
throw new \Friendica\Network\HTTPException\OKException();
}
function pubsub_init(App $a)
{
$nick = ((DI::args()->getArgc() > 1) ? trim(DI::args()->getArgv()[1]) : '');
$contact_id = ((DI::args()->getArgc() > 2) ? intval(DI::args()->getArgv()[2]) : 0 );
if (DI::args()->getMethod() === App\Router::GET) {
$hub_mode = trim($_GET['hub_mode'] ?? '');
$hub_topic = trim($_GET['hub_topic'] ?? '');
$hub_challenge = trim($_GET['hub_challenge'] ?? '');
$hub_verify = trim($_GET['hub_verify_token'] ?? '');
Logger::notice('Subscription from ' . $_SERVER['REMOTE_ADDR'] . ' Mode: ' . $hub_mode . ' Nick: ' . $nick);
Logger::debug('Data: ', ['get' => $_GET]);
$subscribe = (($hub_mode === 'subscribe') ? 1 : 0);
$owner = DBA::selectFirst('user', ['uid'], ['nickname' => $nick, 'account_expired' => false, 'account_removed' => false]);
if (!DBA::isResult($owner)) {
Logger::notice('Local account not found: ' . $nick);
hub_return(false, '');
}
$condition = ['uid' => $owner['uid'], 'id' => $contact_id, 'blocked' => false, 'pending' => false];
if (!empty($hub_verify)) {
$condition['hub-verify'] = $hub_verify;
}
$contact = DBA::selectFirst('contact', ['id', 'poll'], $condition);
if (!DBA::isResult($contact)) {
Logger::notice('Contact ' . $contact_id . ' not found.');
hub_return(false, '');
}
if (!empty($hub_topic) && !Strings::compareLink($hub_topic, $contact['poll'])) {
Logger::notice('Hub topic ' . $hub_topic . ' != ' . $contact['poll']);
hub_return(false, '');
}
// We must initiate an unsubscribe request with a verify_token.
// Don't allow outsiders to unsubscribe us.
if (($hub_mode === 'unsubscribe') && empty($hub_verify)) {
Logger::notice('Bogus unsubscribe');
hub_return(false, '');
}
if (!empty($hub_mode)) {
Contact::update(['subhub' => $subscribe], ['id' => $contact['id']]);
Logger::notice($hub_mode . ' success for contact ' . $contact_id . '.');
}
hub_return(true, $hub_challenge);
}
}
function pubsub_post(App $a)
{
$xml = Network::postdata();
Logger::info('Feed arrived from ' . $_SERVER['REMOTE_ADDR'] . ' for ' . DI::args()->getCommand() . ' with user-agent: ' . $_SERVER['HTTP_USER_AGENT']);
Logger::debug('Data: ' . $xml);
$nick = ((DI::args()->getArgc() > 1) ? trim(DI::args()->getArgv()[1]) : '');
$contact_id = ((DI::args()->getArgc() > 2) ? intval(DI::args()->getArgv()[2]) : 0 );
$importer = DBA::selectFirst('user', [], ['nickname' => $nick, 'account_expired' => false, 'account_removed' => false]);
if (!DBA::isResult($importer)) {
hub_post_return();
}
$condition = ['id' => $contact_id, 'uid' => $importer['uid'], 'subhub' => true, 'blocked' => false];
$contact = DBA::selectFirst('contact', [], $condition);
if (!DBA::isResult($contact)) {
$author = OStatus::salmonAuthor($xml, $importer);
if (!empty($author['contact-id'])) {
$condition = ['id' => $author['contact-id'], 'uid' => $importer['uid'], 'subhub' => true, 'blocked' => false];
$contact = DBA::selectFirst('contact', [], $condition);
Logger::notice('No record for ' . $nick .' with contact id ' . $contact_id . ' - using '.$author['contact-id'].' instead.');
}
if (!DBA::isResult($contact)) {
Logger::notice('Contact ' . $author["author-link"] . ' (' . $contact_id . ') for user ' . $nick . " wasn't found - ignored. XML: " . $xml);
hub_post_return();
}
}
if (!empty($contact['gsid'])) {
GServer::setProtocol($contact['gsid'], Post\DeliveryData::OSTATUS);
}
if (!in_array($contact['rel'], [Contact::SHARING, Contact::FRIEND]) && ($contact['network'] != Protocol::FEED)) {
Logger::notice('Contact ' . $contact['id'] . ' is not expected to share with us - ignored.');
hub_post_return();
}
// We only import feeds from OStatus here
if (!in_array($contact['network'], [Protocol::ACTIVITYPUB, Protocol::OSTATUS])) {
Logger::warning('Unexpected network', ['contact' => $contact]);
hub_post_return();
}
Logger::info('Import item for ' . $nick . ' from ' . $contact['nick'] . ' (' . $contact['id'] . ')');
$feedhub = '';
OStatus::import($xml, $importer, $contact, $feedhub);
hub_post_return();
}

View File

@ -1,147 +0,0 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* 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 the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
use Friendica\App;
use Friendica\Core\Logger;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\PushSubscriber;
use Friendica\Util\Strings;
function pubsubhubbub_init(App $a) {
// PuSH subscription must be considered "public" so just block it
// if public access isn't enabled.
if (DI::config()->get('system', 'block_public')) {
throw new \Friendica\Network\HTTPException\ForbiddenException();
}
// Subscription request from subscriber
// https://pubsubhubbub.googlecode.com/git/pubsubhubbub-core-0.4.html#anchor4
// Example from GNU Social:
// [hub_mode] => subscribe
// [hub_callback] => http://status.local/main/push/callback/1
// [hub_verify] => sync
// [hub_verify_token] => af11...
// [hub_secret] => af11...
// [hub_topic] => http://friendica.local/dfrn_poll/sazius
if (DI::args()->getMethod() === App\Router::POST) {
$hub_mode = $_POST['hub_mode'] ?? '';
$hub_callback = $_POST['hub_callback'] ?? '';
$hub_verify_token = $_POST['hub_verify_token'] ?? '';
$hub_secret = $_POST['hub_secret'] ?? '';
$hub_topic = $_POST['hub_topic'] ?? '';
// check for valid hub_mode
if ($hub_mode === 'subscribe') {
$subscribe = 1;
} elseif ($hub_mode === 'unsubscribe') {
$subscribe = 0;
} else {
Logger::notice("Invalid hub_mode=$hub_mode, ignoring.");
throw new \Friendica\Network\HTTPException\NotFoundException();
}
Logger::info("$hub_mode request from " . $_SERVER['REMOTE_ADDR']);
if (DI::args()->getArgc() > 1) {
// Normally the url should now contain the nick name as last part of the url
$nick = DI::args()->getArgv()[1];
} else {
// Get the nick name from the topic as a fallback
$nick = $hub_topic;
}
// Extract nick name and strip any .atom extension
$nick = basename($nick, '.atom');
if (!$nick) {
Logger::notice('Bad hub_topic=$hub_topic, ignoring.');
throw new \Friendica\Network\HTTPException\NotFoundException();
}
// fetch user from database given the nickname
$condition = ['nickname' => $nick, 'account_expired' => false, 'account_removed' => false];
$owner = DBA::selectFirst('user', ['uid', 'nickname'], $condition);
if (!DBA::isResult($owner)) {
Logger::notice('Local account not found: ' . $nick . ' - topic: ' . $hub_topic . ' - callback: ' . $hub_callback);
throw new \Friendica\Network\HTTPException\NotFoundException();
}
// get corresponding row from contact table
$condition = ['uid' => $owner['uid'], 'blocked' => false,
'pending' => false, 'self' => true];
$contact = DBA::selectFirst('contact', ['poll'], $condition);
if (!DBA::isResult($contact)) {
Logger::notice('Self contact for user ' . $owner['uid'] . ' not found.');
throw new \Friendica\Network\HTTPException\NotFoundException();
}
// sanity check that topic URLs are the same
$hub_topic2 = str_replace('/feed/', '/dfrn_poll/', $hub_topic);
$self = DI::baseUrl() . '/api/statuses/user_timeline/' . $owner['nickname'] . '.atom';
if (!Strings::compareLink($hub_topic, $contact['poll']) && !Strings::compareLink($hub_topic2, $contact['poll']) && !Strings::compareLink($hub_topic, $self)) {
Logger::notice('Hub topic ' . $hub_topic . ' != ' . $contact['poll']);
throw new \Friendica\Network\HTTPException\NotFoundException();
}
// do subscriber verification according to the PuSH protocol
$hub_challenge = Strings::getRandomHex(40);
$params = http_build_query([
'hub.mode' => $subscribe == 1 ? 'subscribe' : 'unsubscribe',
'hub.topic' => $hub_topic,
'hub.challenge' => $hub_challenge,
'hub.verify_token' => $hub_verify_token,
// lease time is hard coded to one week (in seconds)
// we don't actually enforce the lease time because GNU
// Social/StatusNet doesn't honour it (yet)
'hub.lease_seconds' => 604800,
]);
$hub_callback = rtrim($hub_callback, ' ?&#');
$separator = parse_url($hub_callback, PHP_URL_QUERY) === null ? '?' : '&';
$fetchResult = DI::httpClient()->fetchFull($hub_callback . $separator . $params);
$body = $fetchResult->getBody();
$ret = $fetchResult->getReturnCode();
// give up if the HTTP return code wasn't a success (2xx)
if ($ret < 200 || $ret > 299) {
Logger::notice("Subscriber verification for $hub_topic at $hub_callback returned $ret, ignoring.");
throw new \Friendica\Network\HTTPException\NotFoundException();
}
// check that the correct hub_challenge code was echoed back
if (trim($body) !== $hub_challenge) {
Logger::notice("Subscriber did not echo back hub.challenge, ignoring.");
Logger::notice("\"$hub_challenge\" != \"".trim($body)."\"");
throw new \Friendica\Network\HTTPException\NotFoundException();
}
PushSubscriber::renew($owner['uid'], $nick, $subscribe, $hub_callback, $hub_topic, $hub_secret);
throw new \Friendica\Network\HTTPException\AcceptedException();
}
System::exit();
}

View File

@ -729,7 +729,6 @@ class Contact
'notify' => DI::baseUrl() . '/dfrn_notify/' . $user['nickname'],
'poll' => DI::baseUrl() . '/dfrn_poll/' . $user['nickname'],
'confirm' => DI::baseUrl() . '/dfrn_confirm/' . $user['nickname'],
'poco' => DI::baseUrl() . '/poco/' . $user['nickname'],
'name-date' => DateTimeFormat::utcNow(),
'uri-date' => DateTimeFormat::utcNow(),
'avatar-date' => DateTimeFormat::utcNow(),
@ -811,7 +810,6 @@ class Contact
'notify' => DI::baseUrl() . '/dfrn_notify/' . $user['nickname'],
'poll' => DI::baseUrl() . '/dfrn_poll/'. $user['nickname'],
'confirm' => DI::baseUrl() . '/dfrn_confirm/' . $user['nickname'],
'poco' => DI::baseUrl() . '/poco/' . $user['nickname'],
];

View File

@ -0,0 +1,159 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* 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 the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\Module\OStatus;
use Friendica\App;
use Friendica\Core\L10n;
use Friendica\Core\Protocol;
use Friendica\Core\System;
use Friendica\Database\Database;
use Friendica\Model\Contact;
use Friendica\Model\GServer;
use Friendica\Model\Post;
use Friendica\Module\Response;
use Friendica\Network\HTTPException;
use Friendica\Protocol\OStatus;
use Friendica\Util\Network;
use Friendica\Util\Profiler;
use Friendica\Util\Strings;
use Psr\Log\LoggerInterface;
class PubSub extends \Friendica\BaseModule
{
/** @var Database */
private $database;
/** @var App\Request */
private $request;
public function __construct(App\Request $request, Database $database, L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, array $server, array $parameters = [])
{
parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
$this->database = $database;
$this->request = $request;
}
protected function post(array $request = [])
{
$xml = Network::postdata();
$this->logger->info('Feed arrived.', ['from' => $this->request->getRemoteAddress(), 'for' => $this->args->getCommand(), 'user-agent' => $this->server['HTTP_USER_AGENT']]);
$this->logger->debug('Data stream.', ['xml' => $xml]);
$nickname = $this->parameters['nickname'] ?? '';
$contact_id = $this->parameters['cid'] ?? 0;
$importer = $this->database->selectFirst('user', [], ['nickname' => $nickname, 'account_expired' => false, 'account_removed' => false]);
if (!$importer) {
throw new HTTPException\OKException();
}
$condition = ['id' => $contact_id, 'uid' => $importer['uid'], 'subhub' => true, 'blocked' => false];
$contact = $this->database->selectFirst('contact', [], $condition);
if (!$contact) {
$author = OStatus::salmonAuthor($xml, $importer);
if (!empty($author['contact-id'])) {
$condition = ['id' => $author['contact-id'], 'uid' => $importer['uid'], 'subhub' => true, 'blocked' => false];
$contact = $this->database->selectFirst('contact', [], $condition);
$this->logger->notice('No record found for nickname, using author entry instead.', ['nickname' => $nickname, 'contact-id' => $contact_id, 'author-contact-id' => $author['contact-id']]);
}
if (!$contact) {
$this->logger->notice("Contact wasn't found - ignored.", ['author-link' => $author['author-link'], 'contact-id' => $contact_id, 'nickname' => $nickname, 'xml' => $xml]);
throw new HTTPException\OKException();
}
}
if (!empty($contact['gsid'])) {
GServer::setProtocol($contact['gsid'], Post\DeliveryData::OSTATUS);
}
if (!in_array($contact['rel'], [Contact::SHARING, Contact::FRIEND]) && ($contact['network'] != Protocol::FEED)) {
$this->logger->notice('Contact is not expected to share with us - ignored.', ['contact-id' => $contact['id']]);
throw new HTTPException\OKException();
}
// We only import feeds from OStatus here
if (!in_array($contact['network'], [Protocol::ACTIVITYPUB, Protocol::OSTATUS])) {
$this->logger->warning('Unexpected network', ['contact' => $contact, 'network' => $contact['network']]);
throw new HTTPException\OKException();
}
$this->logger->info('Import item from Contact.', ['nickname' => $nickname, 'contact-nickname' => $contact['nick'], 'contact-id' => $contact['id']]);
$feedhub = '';
OStatus::import($xml, $importer, $contact, $feedhub);
throw new HTTPException\OKException();
}
protected function rawContent(array $request = [])
{
$nickname = $this->parameters['nickname'] ?? '';
$contact_id = $this->parameters['cid'] ?? 0;
$hub_mode = trim($request['hub_mode'] ?? '');
$hub_topic = trim($request['hub_topic'] ?? '');
$hub_challenge = trim($request['hub_challenge'] ?? '');
$hub_verify = trim($request['hub_verify_token'] ?? '');
$this->logger->notice('Subscription start.', ['from' => $this->request->getRemoteAddress(), 'mode' => $hub_mode, 'nickname' => $nickname]);
$this->logger->debug('Data: ', ['get' => $request]);
$owner = $this->database->selectFirst('user', ['uid'], ['nickname' => $nickname, 'account_expired' => false, 'account_removed' => false]);
if (!$owner) {
$this->logger->notice('Local account not found.', ['nickname' => $nickname]);
throw new HTTPException\NotFoundException();
}
$condition = ['uid' => $owner['uid'], 'id' => $contact_id, 'blocked' => false, 'pending' => false];
if (!empty($hub_verify)) {
$condition['hub-verify'] = $hub_verify;
}
$contact = $this->database->selectFirst('contact', ['id', 'poll'], $condition);
if (!$contact) {
$this->logger->notice('Contact not found.', ['contact' => $contact_id]);
throw new HTTPException\NotFoundException();
}
if (!empty($hub_topic) && !Strings::compareLink($hub_topic, $contact['poll'])) {
$this->logger->notice("Hub topic isn't valid for Contact.", ['hub_topic' => $hub_topic, 'contact_poll' => $contact['poll']]);
throw new HTTPException\NotFoundException();
}
// We must initiate an unsubscribe request with a verify_token.
// Don't allow outsiders to unsubscribe us.
if (($hub_mode === 'unsubscribe') && empty($hub_verify)) {
$this->logger->notice('Bogus unsubscribe');
throw new HTTPException\NotFoundException();
}
if (!empty($hub_mode)) {
Contact::update(['subhub' => $hub_mode === 'subscribe'], ['id' => $contact['id']]);
$this->logger->notice('Success for contact.', ['mode' => $hub_mode, 'contact' => $contact_id]);
}
System::httpExit($hub_challenge);
}
}

View File

@ -0,0 +1,174 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* 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 the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\Module\OStatus;
use Friendica\App;
use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Core\L10n;
use Friendica\Database\Database;
use Friendica\Model\PushSubscriber;
use Friendica\Module\Response;
use Friendica\Network\HTTPClient\Capability\ICanSendHttpRequests;
use Friendica\Network\HTTPException;
use Friendica\Util\Profiler;
use Friendica\Util\Strings;
use Psr\Log\LoggerInterface;
/**
* An open, simple, web-scale and decentralized pubsub protocol.
*
* Part of the OStatus stack.
*
* See https://pubsubhubbub.github.io/PubSubHubbub/pubsubhubbub-core-0.4.html
*
* @version 0.4
*/
class PubSubHubBub extends \Friendica\BaseModule
{
/** @var IManageConfigValues */
private $config;
/** @var Database */
private $database;
/** @var ICanSendHttpRequests */
private $httpClient;
/** @var App\Request */
private $request;
public function __construct(App\Request $request, ICanSendHttpRequests $httpClient, Database $database, IManageConfigValues $config, L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, array $server, array $parameters = [])
{
parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
$this->config = $config;
$this->database = $database;
$this->httpClient = $httpClient;
$this->request = $request;
}
protected function post(array $request = [])
{
// PuSH subscription must be considered "public" so just block it
// if public access isn't enabled.
if ($this->config->get('system', 'block_public')) {
throw new HTTPException\ForbiddenException();
}
// Subscription request from subscriber
// https://pubsubhubbub.github.io/PubSubHubbub/pubsubhubbub-core-0.4.html#rfc.section.5.1
// Example from GNU Social:
// [hub_mode] => subscribe
// [hub_callback] => http://status.local/main/push/callback/1
// [hub_verify] => sync
// [hub_verify_token] => af11...
// [hub_secret] => af11...
// [hub_topic] => http://friendica.local/dfrn_poll/sazius
$hub_mode = $request['hub_mode'] ?? '';
$hub_callback = $request['hub_callback'] ?? '';
$hub_verify_token = $request['hub_verify_token'] ?? '';
$hub_secret = $request['hub_secret'] ?? '';
$hub_topic = $request['hub_topic'] ?? '';
// check for valid hub_mode
if ($hub_mode === 'subscribe') {
$subscribe = 1;
} elseif ($hub_mode === 'unsubscribe') {
$subscribe = 0;
} else {
$this->logger->notice('Invalid hub_mod - ignored.', ['mode' => $hub_mode]);
throw new HTTPException\NotFoundException();
}
$this->logger->info('hub_mode request details.', ['from' => $this->request->getRemoteAddress(), 'mode' => $hub_mode]);
$nickname = $this->parameters['nickname'] ?? $hub_topic;
// Extract nickname and strip any .atom extension
$nickname = basename($nickname, '.atom');
if (!$nickname) {
$this->logger->notice('Empty nick, ignoring.');
throw new HTTPException\NotFoundException();
}
// fetch user from database given the nickname
$condition = ['nickname' => $nickname, 'account_expired' => false, 'account_removed' => false];
$owner = $this->database->selectFirst('user', ['uid', 'nickname'], $condition);
if (!$owner) {
$this->logger->notice('Local account not found', ['nickname' => $nickname, 'topic' => $hub_topic, 'callback' => $hub_callback]);
throw new HTTPException\NotFoundException();
}
// get corresponding row from contact table
$condition = ['uid' => $owner['uid'], 'blocked' => false, 'pending' => false, 'self' => true];
$contact = $this->database->selectFirst('contact', ['poll'], $condition);
if (!$contact) {
$this->logger->notice('Self contact for user not found.', ['uid' => $owner['uid']]);
throw new HTTPException\NotFoundException();
}
// sanity check that topic URLs are the same
$hub_topic2 = str_replace('/feed/', '/dfrn_poll/', $hub_topic);
$self = $this->baseUrl . '/api/statuses/user_timeline/' . $owner['nickname'] . '.atom';
if (!Strings::compareLink($hub_topic, $contact['poll']) && !Strings::compareLink($hub_topic2, $contact['poll']) && !Strings::compareLink($hub_topic, $self)) {
$this->logger->notice('Hub topic invalid', ['hub_topic' => $hub_topic, 'poll' => $contact['poll']]);
throw new HTTPException\NotFoundException();
}
// do subscriber verification according to the PuSH protocol
$hub_challenge = Strings::getRandomHex(40);
$params = http_build_query([
'hub.mode' => $subscribe == 1 ? 'subscribe' : 'unsubscribe',
'hub.topic' => $hub_topic,
'hub.challenge' => $hub_challenge,
'hub.verify_token' => $hub_verify_token,
// lease time is hard coded to one week (in seconds)
// we don't actually enforce the lease time because GNU
// Social/StatusNet doesn't honour it (yet)
'hub.lease_seconds' => 604800,
]);
$hub_callback = rtrim($hub_callback, ' ?&#');
$separator = parse_url($hub_callback, PHP_URL_QUERY) === null ? '?' : '&';
$fetchResult = $this->httpClient->fetchFull($hub_callback . $separator . $params);
$body = $fetchResult->getBody();
$returnCode = $fetchResult->getReturnCode();
// give up if the HTTP return code wasn't a success (2xx)
if ($returnCode < 200 || $returnCode > 299) {
$this->logger->notice('Subscriber verification ignored', ['hub_topic' => $hub_topic, 'callback' => $hub_callback, 'returnCode' => $returnCode]);
throw new HTTPException\NotFoundException();
}
// check that the correct hub_challenge code was echoed back
if (trim($body) !== $hub_challenge) {
$this->logger->notice('Subscriber did not echo back hub.challenge, ignoring.', ['hub_challenge' => $hub_challenge, 'body' => trim($body)]);
throw new HTTPException\NotFoundException();
}
PushSubscriber::renew($owner['uid'], $nickname, $subscribe, $hub_callback, $hub_topic, $hub_secret);
throw new HTTPException\AcceptedException();
}
}

View File

@ -0,0 +1,164 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* 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 the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\Module\OStatus;
use Friendica\App;
use Friendica\Core\L10n;
use Friendica\Core\PConfig\Capability\IManagePersonalConfigValues;
use Friendica\Core\Protocol;
use Friendica\Core\Session\Capability\IHandleUserSessions;
use Friendica\Model\APContact;
use Friendica\Model\Contact;
use Friendica\Module\Response;
use Friendica\Navigation\SystemMessages;
use Friendica\Network\HTTPClient\Capability\ICanSendHttpRequests;
use Friendica\Network\HTTPClient\Client\HttpClientAccept;
use Friendica\Protocol\ActivityPub;
use Friendica\Util\Profiler;
use Psr\Log\LoggerInterface;
class Subscribe extends \Friendica\BaseModule
{
/** @var IHandleUserSessions */
private $session;
/** @var SystemMessages */
private $systemMessages;
/** @var ICanSendHttpRequests */
private $httpClient;
/** @var IManagePersonalConfigValues */
private $pConfig;
/** @var App\Page */
private $page;
public function __construct(App\Page $page, IManagePersonalConfigValues $pConfig, ICanSendHttpRequests $httpClient, SystemMessages $systemMessages, IHandleUserSessions $session, L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, array $server, array $parameters = [])
{
parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
$this->session = $session;
$this->systemMessages = $systemMessages;
$this->httpClient = $httpClient;
$this->pConfig = $pConfig;
$this->page = $page;
}
protected function content(array $request = []): string
{
if (!$this->session->getLocalUserId()) {
$this->systemMessages->addNotice($this->t('Permission denied.'));
$this->baseUrl->redirect('login');
}
$o = '<h2>' . $this->t('Subscribing to contacts') . '</h2>';
$uid = $this->session->getLocalUserId();
$counter = intval($request['counter'] ?? 0);
if ($this->pConfig->get($uid, 'ostatus', 'legacy_friends') == '') {
if (empty($request['url'])) {
$this->pConfig->delete($uid, 'ostatus', 'legacy_contact');
return $o . $this->t('No contact provided.');
}
$contact = Contact::getByURL($request['url']);
if (!$contact) {
$this->pConfig->delete($uid, 'ostatus', 'legacy_contact');
return $o . $this->t('Couldn\'t fetch information for contact.');
}
if ($contact['network'] == Protocol::OSTATUS) {
$api = $contact['baseurl'] . '/api/';
// Fetching friends
$curlResult = $this->httpClient->get($api . 'statuses/friends.json?screen_name=' . $contact['nick'], HttpClientAccept::JSON);
if (!$curlResult->isSuccess()) {
$this->pConfig->delete($uid, 'ostatus', 'legacy_contact');
return $o . $this->t('Couldn\'t fetch friends for contact.');
}
$friends = $curlResult->getBody();
if (empty($friends)) {
$this->pConfig->delete($uid, 'ostatus', 'legacy_contact');
return $o . $this->t('Couldn\'t fetch following contacts.');
}
$this->pConfig->set($uid, 'ostatus', 'legacy_friends', $friends);
} elseif ($apcontact = APContact::getByURL($contact['url'])) {
if (empty($apcontact['following'])) {
$this->pConfig->delete($uid, 'ostatus', 'legacy_contact');
return $o . $this->t('Couldn\'t fetch remote profile.');
}
$followings = ActivityPub::fetchItems($apcontact['following']);
if (empty($followings)) {
$this->pConfig->delete($uid, 'ostatus', 'legacy_contact');
return $o . $this->t('Couldn\'t fetch following contacts.');
}
$this->pConfig->set($uid, 'ostatus', 'legacy_friends', json_encode($followings));
} else {
$this->pConfig->delete($uid, 'ostatus', 'legacy_contact');
return $o . $this->t('Unsupported network');
}
}
$friends = json_decode($this->pConfig->get($uid, 'ostatus', 'legacy_friends'));
if (empty($friends)) {
$friends = [];
}
$total = sizeof($friends);
if ($counter >= $total) {
$this->page['htmlhead'] = '<meta http-equiv="refresh" content="0; URL=' . $this->baseUrl . '/settings/connectors">';
$this->pConfig->delete($uid, 'ostatus', 'legacy_friends');
$this->pConfig->delete($uid, 'ostatus', 'legacy_contact');
$o .= $this->t('Done');
return $o;
}
$friend = $friends[$counter++];
$url = $friend->statusnet_profile_url ?? $friend;
$o .= '<p>' . $counter . '/' . $total . ': ' . $url;
$probed = Contact::getByURL($url);
if (in_array($probed['network'], Protocol::FEDERATED)) {
$result = Contact::createFromProbeForUser($this->session->getLocalUserId(), $probed['url']);
if ($result['success']) {
$o .= ' - ' . $this->t('success');
} else {
$o .= ' - ' . $this->t('failed');
}
} else {
$o .= ' - ' . $this->t('ignored');
}
$o .= '</p>';
$o .= '<p>' . $this->t('Keep this window open until done.') . '</p>';
$this->page['htmlhead'] = '<meta http-equiv="refresh" content="0; URL=' . $this->baseUrl . '/ostatus/subscribe?counter=' . $counter . '">';
return $o;
}
}

View File

@ -334,7 +334,6 @@ class Profile extends BaseProfile
foreach ($dfrn_pages as $dfrn) {
$htmlhead .= '<link rel="dfrn-' . $dfrn . '" href="' . $baseUrl . '/dfrn_' . $dfrn . '/' . $nickname . '" />' . "\n";
}
$htmlhead .= '<link rel="dfrn-poco" href="' . $baseUrl . '/poco/' . $nickname . '" />' . "\n";
return $htmlhead;
}

View File

@ -140,7 +140,7 @@ class Connectors extends BaseSettings
$legacy_contact = $this->pconfig->get($this->session->getLocalUserId(), 'ostatus', 'legacy_contact');
if (!empty($legacy_contact)) {
$this->baseUrl->redirect('ostatus_subscribe?url=' . urlencode($legacy_contact));
$this->baseUrl->redirect('ostatus/subscribe?url=' . urlencode($legacy_contact));
}
$connector_settings_forms = [];

View File

@ -0,0 +1,277 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* 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 the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* @see https://web.archive.org/web/20160405005550/http://portablecontacts.net/draft-spec.html
*/
namespace Friendica\Module\User;
use Friendica\App;
use Friendica\BaseModule;
use Friendica\Content\Text\BBCode;
use Friendica\Core\Cache\Capability\ICanCache;
use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Core\L10n;
use Friendica\Core\Protocol;
use Friendica\Core\System;
use Friendica\Database\Database;
use Friendica\Module\Response;
use Friendica\Network\HTTPException;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Profiler;
use Psr\Log\LoggerInterface;
/**
* Minimal implementation of the Portable Contacts protocol
* @see https://portablecontacts.github.io
*/
class PortableContacts extends BaseModule
{
/** @var IManageConfigValues */
private $config;
/** @var Database */
private $database;
/** @var ICanCache */
private $cache;
public function __construct(ICanCache $cache, Database $database, IManageConfigValues $config, L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, array $server, array $parameters = [])
{
parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
$this->config = $config;
$this->database = $database;
$this->cache = $cache;
}
protected function rawContent(array $request = [])
{
if ($this->config->get('system', 'block_public') || $this->config->get('system', 'block_local_dir')) {
throw new HTTPException\ForbiddenException();
}
$format = $request['format'] ?? 'json';
if ($format !== 'json') {
throw new HTTPException\UnsupportedMediaTypeException();
}
$totalResults = $this->database->count('profile', ['net-publish' => true]);
if (!$totalResults) {
throw new HTTPException\ForbiddenException();
}
if (!empty($request['startIndex']) && is_numeric($request['startIndex'])) {
$startIndex = intval($request['startIndex']);
} else {
$startIndex = 0;
}
$itemsPerPage = !empty($request['count']) && is_numeric($request['count']) ? intval($request['count']) : $totalResults;
$this->logger->info('Start system mode query');
$contacts = $this->database->selectToArray('owner-view', [], ['net-publish' => true], ['limit' => [$startIndex, $itemsPerPage]]);
$this->logger->info('Query done');
$return = [];
if (!empty($request['sorted'])) {
$return['sorted'] = false;
}
if (!empty($request['filtered'])) {
$return['filtered'] = false;
}
if (!empty($request['updatedSince'])) {
$return['updatedSince'] = false;
}
$return['startIndex'] = $startIndex;
$return['itemsPerPage'] = $itemsPerPage;
$return['totalResults'] = $totalResults;
$return['entry'] = [];
$selectedFields = [
'id' => false,
'displayName' => false,
'urls' => false,
'updated' => false,
'preferredUsername' => false,
'photos' => false,
'aboutMe' => false,
'currentLocation' => false,
'network' => false,
'tags' => false,
'address' => false,
'contactType' => false,
'generation' => false
];
if (empty($request['fields']) || $request['fields'] == '@all') {
foreach ($selectedFields as $k => $v) {
$selectedFields[$k] = true;
}
} else {
$fields_req = explode(',', $request['fields']);
foreach ($fields_req as $f) {
$selectedFields[trim($f)] = true;
}
}
if (!$contacts) {
$return['entry'][] = [];
}
foreach ($contacts as $contact) {
if (!isset($contact['updated'])) {
$contact['updated'] = '';
}
if (!isset($contact['generation'])) {
$contact['generation'] = 1;
}
if (empty($contact['keywords']) && isset($contact['pub_keywords'])) {
$contact['keywords'] = $contact['pub_keywords'];
}
if (isset($contact['account-type'])) {
$contact['contact-type'] = $contact['account-type'];
}
$cacheKey = 'about:' . $contact['nick'] . ':' . DateTimeFormat::utc($contact['updated'], DateTimeFormat::ATOM);
$about = $this->cache->get($cacheKey);
if (is_null($about)) {
$about = BBCode::convertForUriId($contact['uri-id'], $contact['about']);
$this->cache->set($cacheKey, $about);
}
// Non connected persons can only see the keywords of a Diaspora account
if ($contact['network'] == Protocol::DIASPORA) {
$contact['location'] = '';
$about = '';
}
$entry = [];
if ($selectedFields['id']) {
$entry['id'] = (int)$contact['id'];
}
if ($selectedFields['displayName']) {
$entry['displayName'] = $contact['name'];
}
if ($selectedFields['aboutMe']) {
$entry['aboutMe'] = $about;
}
if ($selectedFields['currentLocation']) {
$entry['currentLocation'] = $contact['location'];
}
if ($selectedFields['generation']) {
$entry['generation'] = (int)$contact['generation'];
}
if ($selectedFields['urls']) {
$entry['urls'] = [['value' => $contact['url'], 'type' => 'profile']];
if ($contact['addr'] && ($contact['network'] !== Protocol::MAIL)) {
$entry['urls'][] = ['value' => 'acct:' . $contact['addr'], 'type' => 'webfinger'];
}
}
if ($selectedFields['preferredUsername']) {
$entry['preferredUsername'] = $contact['nick'];
}
if ($selectedFields['updated']) {
$entry['updated'] = $contact['success_update'];
if ($contact['name-date'] > $entry['updated']) {
$entry['updated'] = $contact['name-date'];
}
if ($contact['uri-date'] > $entry['updated']) {
$entry['updated'] = $contact['uri-date'];
}
if ($contact['avatar-date'] > $entry['updated']) {
$entry['updated'] = $contact['avatar-date'];
}
$entry['updated'] = date('c', strtotime($entry['updated']));
}
if ($selectedFields['photos']) {
$entry['photos'] = [['value' => $contact['photo'], 'type' => 'profile']];
}
if ($selectedFields['network']) {
$entry['network'] = $contact['network'];
if ($entry['network'] == Protocol::STATUSNET) {
$entry['network'] = Protocol::OSTATUS;
}
if (($entry['network'] == '') && ($contact['self'])) {
$entry['network'] = Protocol::DFRN;
}
}
if ($selectedFields['tags']) {
$tags = str_replace(',', ' ', $contact['keywords']);
$tags = explode(' ', $tags);
$cleaned = [];
foreach ($tags as $tag) {
$tag = trim(strtolower($tag));
if ($tag != '') {
$cleaned[] = $tag;
}
}
$entry['tags'] = [$cleaned];
}
if ($selectedFields['address']) {
$entry['address'] = [];
if (isset($contact['locality'])) {
$entry['address']['locality'] = $contact['locality'];
}
if (isset($contact['region'])) {
$entry['address']['region'] = $contact['region'];
}
if (isset($contact['country'])) {
$entry['address']['country'] = $contact['country'];
}
}
if ($selectedFields['contactType']) {
$entry['contactType'] = intval($contact['contact-type']);
}
$return['entry'][] = $entry;
}
$this->logger->info('End of poco');
System::jsonExit($return);
}
}

View File

@ -184,10 +184,6 @@ class Xrd extends BaseModule
'type' => 'text/html',
'href' => $baseURL . '/hcard/' . $owner['nickname'],
],
[
'rel' => ActivityNamespace::POCO,
'href' => $owner['poco'],
],
[
'rel' => 'http://webfinger.net/rel/avatar',
'type' => $avatar['type'],
@ -272,56 +268,50 @@ class Xrd extends BaseModule
]
],
'5:link' => [
'@attributes' => [
'rel' => 'http://portablecontacts.net/spec/1.0',
'href' => $owner['poco']
]
],
'6:link' => [
'@attributes' => [
'rel' => 'http://webfinger.net/rel/avatar',
'type' => $avatar['type'],
'href' => User::getAvatarUrl($owner)
]
],
'7:link' => [
'6:link' => [
'@attributes' => [
'rel' => 'http://joindiaspora.com/seed_location',
'type' => 'text/html',
'href' => $baseURL
]
],
'8:link' => [
'7:link' => [
'@attributes' => [
'rel' => 'salmon',
'href' => $baseURL . '/salmon/' . $owner['nickname']
]
],
'9:link' => [
'8:link' => [
'@attributes' => [
'rel' => 'http://salmon-protocol.org/ns/salmon-replies',
'href' => $baseURL . '/salmon/' . $owner['nickname']
]
],
'10:link' => [
'9:link' => [
'@attributes' => [
'rel' => 'http://salmon-protocol.org/ns/salmon-mention',
'href' => $baseURL . '/salmon/' . $owner['nickname'] . '/mention'
]
],
'11:link' => [
'10:link' => [
'@attributes' => [
'rel' => 'http://ostatus.org/schema/1.0/subscribe',
'template' => $baseURL . '/contact/follow?url={uri}'
]
],
'12:link' => [
'11:link' => [
'@attributes' => [
'rel' => 'magic-public-key',
'href' => 'data:application/magic-public-key,' . Salmon::salmonKey($owner['spubkey'])
]
],
'13:link' => [
'12:link' => [
'@attributes' => [
'rel' => 'http://purl.org/openwebauth/v1',
'type' => 'application/x-zot+json',

View File

@ -526,8 +526,6 @@ return [
'/openid' => [Module\Security\OpenID::class, [R::GET]],
'/opensearch' => [Module\OpenSearch::class, [R::GET]],
'/ostatus/repair' => [Module\OStatus\Repair::class, [R::GET]],
'/parseurl' => [Module\ParseUrl::class, [R::GET]],
'/permission/tooltip/{type}/{id:\d+}' => [Module\PermissionTooltip::class, [R::GET]],
@ -569,7 +567,13 @@ return [
'/{sub1}/{sub2}/{url}' => [Module\Proxy::class, [R::GET]],
],
'/salmon/{nickname}' => [Module\OStatus\Salmon::class, [ R::POST]],
// OStatus stack modules
'/ostatus/repair' => [Module\OStatus\Repair::class, [R::GET ]],
'/ostatus/subscribe' => [Module\OStatus\Subscribe::class, [R::GET ]],
'/poco' => [Module\User\PortableContacts::class, [R::GET ]],
'/pubsub/{nickname}/{cid:\d+}' => [Module\OStatus\PubSub::class, [R::GET, R::POST]],
'/pubsubhubbub/{nickname}' => [Module\OStatus\PubSubHubBub::class, [ R::POST]],
'/salmon/{nickname}' => [Module\OStatus\Salmon::class, [ R::POST]],
'/search' => [
'[/]' => [Module\Search\Index::class, [R::GET ]],

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: 2022.12-dev\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-11-09 21:27+0000\n"
"POT-Creation-Date: 2022-11-11 00:42-0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -39,32 +39,32 @@ msgid "The feed for this item is unavailable."
msgstr ""
#: mod/editpost.php:38 mod/item.php:181 mod/item.php:186 mod/item.php:870
#: mod/message.php:69 mod/message.php:114 mod/notes.php:44
#: mod/ostatus_subscribe.php:33 mod/photos.php:159 mod/photos.php:886
#: src/Module/Attach.php:56 src/Module/BaseApi.php:94
#: mod/message.php:69 mod/message.php:114 mod/notes.php:44 mod/photos.php:159
#: mod/photos.php:886 src/Module/Attach.php:56 src/Module/BaseApi.php:94
#: src/Module/BaseNotifications.php:98 src/Module/BaseSettings.php:52
#: src/Module/Calendar/Event/API.php:88 src/Module/Calendar/Event/Form.php:84
#: src/Module/Calendar/Event/Show.php:54 src/Module/Calendar/Export.php:62
#: src/Module/Calendar/Show.php:64 src/Module/Contact/Advanced.php:60
#: src/Module/Contact/Follow.php:86 src/Module/Contact/Follow.php:158
#: src/Module/Contact/Match.php:69 src/Module/Contact/Suggestions.php:54
#: src/Module/Contact/Unfollow.php:66 src/Module/Contact/Unfollow.php:80
#: src/Module/Contact/Unfollow.php:112 src/Module/Delegation.php:118
#: src/Module/FollowConfirm.php:38 src/Module/FriendSuggest.php:57
#: src/Module/Group.php:40 src/Module/Group.php:83 src/Module/Invite.php:42
#: src/Module/Invite.php:131 src/Module/Notifications/Notification.php:76
#: src/Module/Contact/MatchInterests.php:86
#: src/Module/Contact/Suggestions.php:54 src/Module/Contact/Unfollow.php:66
#: src/Module/Contact/Unfollow.php:80 src/Module/Contact/Unfollow.php:112
#: src/Module/Delegation.php:118 src/Module/FollowConfirm.php:38
#: src/Module/FriendSuggest.php:57 src/Module/Group.php:40
#: src/Module/Group.php:83 src/Module/Invite.php:42 src/Module/Invite.php:131
#: src/Module/Notifications/Notification.php:76
#: src/Module/Notifications/Notification.php:107
#: src/Module/OStatus/Repair.php:60 src/Module/Profile/Attachment/Upload.php:97
#: src/Module/Profile/Common.php:55 src/Module/Profile/Contacts.php:55
#: src/Module/Profile/Photos/Upload.php:108 src/Module/Profile/Schedule.php:39
#: src/Module/Profile/Schedule.php:56 src/Module/Profile/UnkMail.php:69
#: src/Module/Profile/UnkMail.php:121 src/Module/Profile/UnkMail.php:132
#: src/Module/Register.php:77 src/Module/Register.php:90
#: src/Module/Register.php:206 src/Module/Register.php:245
#: src/Module/Search/Directory.php:37 src/Module/Settings/Account.php:50
#: src/Module/Settings/Account.php:410 src/Module/Settings/Delegation.php:41
#: src/Module/Settings/Delegation.php:69 src/Module/Settings/Display.php:41
#: src/Module/Settings/Display.php:119
#: src/Module/OStatus/Repair.php:60 src/Module/OStatus/Subscribe.php:55
#: src/Module/Profile/Attachment/Upload.php:97 src/Module/Profile/Common.php:55
#: src/Module/Profile/Contacts.php:55 src/Module/Profile/Photos/Upload.php:108
#: src/Module/Profile/Schedule.php:39 src/Module/Profile/Schedule.php:56
#: src/Module/Profile/UnkMail.php:69 src/Module/Profile/UnkMail.php:121
#: src/Module/Profile/UnkMail.php:132 src/Module/Register.php:77
#: src/Module/Register.php:90 src/Module/Register.php:206
#: src/Module/Register.php:245 src/Module/Search/Directory.php:37
#: src/Module/Settings/Account.php:50 src/Module/Settings/Account.php:410
#: src/Module/Settings/Delegation.php:41 src/Module/Settings/Delegation.php:69
#: src/Module/Settings/Display.php:41 src/Module/Settings/Display.php:119
#: src/Module/Settings/Profile/Photo/Crop.php:165
#: src/Module/Settings/Profile/Photo/Index.php:111
#: src/Module/Settings/RemoveMe.php:126 src/Module/Settings/UserExport.php:84
@ -577,54 +577,6 @@ msgstr ""
msgid "Personal notes are visible only by yourself."
msgstr ""
#: mod/ostatus_subscribe.php:38
msgid "Subscribing to contacts"
msgstr ""
#: mod/ostatus_subscribe.php:47
msgid "No contact provided."
msgstr ""
#: mod/ostatus_subscribe.php:53
msgid "Couldn't fetch information for contact."
msgstr ""
#: mod/ostatus_subscribe.php:64
msgid "Couldn't fetch friends for contact."
msgstr ""
#: mod/ostatus_subscribe.php:70 mod/ostatus_subscribe.php:81
msgid "Couldn't fetch following contacts."
msgstr ""
#: mod/ostatus_subscribe.php:76
msgid "Couldn't fetch remote profile."
msgstr ""
#: mod/ostatus_subscribe.php:86
msgid "Unsupported network"
msgstr ""
#: mod/ostatus_subscribe.php:102
msgid "Done"
msgstr ""
#: mod/ostatus_subscribe.php:116
msgid "success"
msgstr ""
#: mod/ostatus_subscribe.php:118
msgid "failed"
msgstr ""
#: mod/ostatus_subscribe.php:121
msgid "ignored"
msgstr ""
#: mod/ostatus_subscribe.php:126 src/Module/OStatus/Repair.php:84
msgid "Keep this window open until done."
msgstr ""
#: mod/photos.php:68 mod/photos.php:139 mod/photos.php:793
#: src/Model/Event.php:514 src/Model/Profile.php:234
#: src/Module/Calendar/Export.php:67 src/Module/Feed.php:72
@ -5512,7 +5464,7 @@ msgstr ""
msgid "Forum Search - %s"
msgstr ""
#: src/Module/BaseSearch.php:119 src/Module/Contact/Match.php:122
#: src/Module/BaseSearch.php:119 src/Module/Contact/MatchInterests.php:139
msgid "No matches"
msgstr ""
@ -5973,18 +5925,19 @@ msgstr ""
msgid "The contact could not be added."
msgstr ""
#: src/Module/Contact/Match.php:77 src/Module/Profile/Attachment/Upload.php:80
#: src/Module/Contact/MatchInterests.php:94
#: src/Module/Profile/Attachment/Upload.php:80
#: src/Module/Profile/Attachment/Upload.php:102
#: src/Module/Profile/Photos/Upload.php:113
#: src/Module/Profile/Photos/Upload.php:162
msgid "Invalid request."
msgstr ""
#: src/Module/Contact/Match.php:84
#: src/Module/Contact/MatchInterests.php:101
msgid "No keywords to match. Please add keywords to your profile."
msgstr ""
#: src/Module/Contact/Match.php:127
#: src/Module/Contact/MatchInterests.php:144
msgid "Profile Match"
msgstr ""
@ -7982,11 +7935,11 @@ msgstr ""
msgid "Show unread"
msgstr ""
#: src/Module/Notifications/Ping.php:242
#: src/Module/Notifications/Ping.php:240
msgid "{0} requested registration"
msgstr ""
#: src/Module/Notifications/Ping.php:253
#: src/Module/Notifications/Ping.php:249
#, php-format
msgid "{0} and %d others requested registration"
msgstr ""
@ -8024,6 +7977,10 @@ msgstr ""
msgid "Resubscribing to OStatus contacts"
msgstr ""
#: src/Module/OStatus/Repair.php:84 src/Module/OStatus/Subscribe.php:148
msgid "Keep this window open until done."
msgstr ""
#: src/Module/OStatus/Repair.php:85
msgid "✔ Done"
msgstr ""
@ -8032,6 +7989,50 @@ msgstr ""
msgid "No OStatus contacts to resubscribe to."
msgstr ""
#: src/Module/OStatus/Subscribe.php:60
msgid "Subscribing to contacts"
msgstr ""
#: src/Module/OStatus/Subscribe.php:69
msgid "No contact provided."
msgstr ""
#: src/Module/OStatus/Subscribe.php:75
msgid "Couldn't fetch information for contact."
msgstr ""
#: src/Module/OStatus/Subscribe.php:86
msgid "Couldn't fetch friends for contact."
msgstr ""
#: src/Module/OStatus/Subscribe.php:92 src/Module/OStatus/Subscribe.php:103
msgid "Couldn't fetch following contacts."
msgstr ""
#: src/Module/OStatus/Subscribe.php:98
msgid "Couldn't fetch remote profile."
msgstr ""
#: src/Module/OStatus/Subscribe.php:108
msgid "Unsupported network"
msgstr ""
#: src/Module/OStatus/Subscribe.php:124
msgid "Done"
msgstr ""
#: src/Module/OStatus/Subscribe.php:138
msgid "success"
msgstr ""
#: src/Module/OStatus/Subscribe.php:140
msgid "failed"
msgstr ""
#: src/Module/OStatus/Subscribe.php:143
msgid "ignored"
msgstr ""
#: src/Module/PermissionTooltip.php:49
#, php-format
msgid "Wrong type \"%s\", expected one of: %s"
@ -10439,11 +10440,11 @@ msgid ""
"features and resources."
msgstr ""
#: src/Navigation/Notifications/Factory/FormattedNavNotification.php:138
#: src/Navigation/Notifications/Factory/FormattedNavNotification.php:151
msgid "{0} wants to follow you"
msgstr ""
#: src/Navigation/Notifications/Factory/FormattedNavNotification.php:140
#: src/Navigation/Notifications/Factory/FormattedNavNotification.php:153
msgid "{0} has started following you"
msgstr ""