Merge pull request #12156 from MrPetovan/task/4090-move-mod-pubsubhubbub
Move remainder of OStatus stack to src/Module
This commit is contained in:
commit
0eaa2eae84
14 changed files with 867 additions and 771 deletions
|
@ -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;
|
||||
}
|
234
mod/poco.php
234
mod/poco.php
|
@ -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();
|
||||
}
|
||||
}
|
158
mod/pubsub.php
158
mod/pubsub.php
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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'],
|
||||
];
|
||||
|
||||
|
||||
|
|
159
src/Module/OStatus/PubSub.php
Normal file
159
src/Module/OStatus/PubSub.php
Normal 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);
|
||||
}
|
||||
}
|
174
src/Module/OStatus/PubSubHubBub.php
Normal file
174
src/Module/OStatus/PubSubHubBub.php
Normal 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();
|
||||
}
|
||||
}
|
164
src/Module/OStatus/Subscribe.php
Normal file
164
src/Module/OStatus/Subscribe.php
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 = [];
|
||||
|
|
277
src/Module/User/PortableContacts.php
Normal file
277
src/Module/User/PortableContacts.php
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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',
|
||||
|
|
|
@ -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 ]],
|
||||
|
|
|
@ -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 ""
|
||||
|
||||
|
|
Loading…
Reference in a new issue