[RemoteFollow] Initial work in the RemoteFollow plugin

lib/default.php
- Add RemoteFollow to the list of default plugins

RemoteFollowPlugin:
- Subscribe events to add the remote-follow button

RemoteFollowInitAction:
- Handles the remote-follow form and getting the redirection url for follow completion

RemoteFollowSubAction:
- Handles the remote profile pulling and actual following
This commit is contained in:
tenma 2019-08-25 03:09:50 +01:00 committed by Diogo Cordeiro
parent 6d81848f25
commit e61c0c45aa
9 changed files with 1342 additions and 0 deletions

View File

@ -355,6 +355,7 @@ $default =
'Nodeinfo' => [],
'OpenID' => [],
'OpportunisticQM' => [],
'RemoteFollow' => [],
'ActivityPub' => [], // The order is important here (IT HAS TO COME BEFORE OSTATUS)
'OStatus' => [],
'Poll' => [],

View File

@ -0,0 +1,8 @@
RemoteFollowConnectProfile: when the plugin is "one url away" from redirecting to the remote follower instance; federation plugins must find and retrieve the url for redirection
- User $target local user to be followed
- string $profile remote follower's ID
- string|null &$url url for redirection
RemoteFollowPullProfile: when the plugin needs to pull the remote profile from its uri; federation plugins must pull, store and retrieve the profile's local object
- string $uri the remote profile's uri
- null|Profile &$profile pulled profile

View File

@ -0,0 +1,41 @@
# RemoteFollow Plugin
(c) 2019 Free Software Foundation, Inc
This is the README file for GNU social's ActivityPub plugin.
It includes general information about the plugin.
## About
This plugin adds remote-follow button support to GNU social.
## Installation
This plugin is enabled by default.
## Settings
This plugin has no settings.
## License
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, in the file "COPYING". If not, see
<http://www.gnu.org/licenses/>.
IMPORTANT NOTE: The GNU Affero General Public License (AGPL) has
*different requirements* from the "regular" GPL. In particular, if
you make modifications to the plugin source code on your server,
you *MUST MAKE AVAILABLE* the modified version of the source code
to your users under the same license. This is a legal requirement
of using the software, and if you do not wish to share your
modifications, *YOU MAY NOT USE THIS PLUGIN*.

View File

@ -0,0 +1,159 @@
<?php
// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social 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.
//
// GNU social 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 GNU social. If not, see <http://www.gnu.org/licenses/>.
/**
* Remote Follow implementation for GNU social
*
* @package GNUsocial
* @author Bruno Casteleiro <brunoccast@fc.up.pt>
* @copyright 2019 Free Software Foundation, Inc http://www.fsf.org
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/
defined('GNUSOCIAL') || die();
class RemoteFollowPlugin extends Plugin
{
const PLUGIN_VERSION = '0.1.0';
/**
* Route/Reroute urls
*
* @param URLMapper $m
* @return void
*/
public function onRouterInitialized(URLMapper $m): void
{
// discovery
$m->connect('main/remotefollow/nickname/:nickname',
['action' => 'RemoteFollowInit'],
['nickname' => Nickname::DISPLAY_FMT]);
$m->connect('main/remotefollow',
['action' => 'RemoteFollowInit']);
// remote follow
$m->connect('main/remotefollowsub',
['action' => 'RemoteFollowSub']);
}
/**
* Add remote-follow button to someone's profile
*
* @param HTMLOutputter $out
* @param Profile $target
* @return bool hook return value
*/
public function onStartProfileRemoteSubscribe(HTMLOutputter $out, Profile $target): bool
{
if (common_logged_in() || !$target->isLocal()) {
return true;
}
$out->elementStart('li', 'entity_subscribe');
$url = common_local_url('RemoteFollowInit', ['nickname' => $target->getNickname()]);
$out->element('a',
['href' => $url,
'class' => 'entity_remote_subscribe'],
// TRANS: Link text for the follow button
_m('Subscribe'));
$out->elementEnd('li');
return true;
}
/**
* Add remote-follow button in the subscriptions list
*
* @param Action $action
* @return bool hook return value
*/
public function onStartShowSubscriptionsContent(Action $action): bool
{
$this->showEntityRemoteSubscribe($action);
return true;
}
/**
* Add remote-follow button to the profile subscriptions minilist
*
* @param Action $action
* @return bool hook return value
*/
public function onEndShowSubscriptionsMiniList(Action $action): bool
{
$this->showEntityRemoteSubscribe($action);
return true;
}
/**
* Add webfinger profile link for remote subscription
*/
function onEndWebFingerProfileLinks(XML_XRD $xrd, Profile $target): bool
{
$xrd->links[] = new XML_XRD_Element_Link('http://ostatus.org/schema/1.0/subscribe',
common_local_url('RemoteFollowSub') . '?profile={uri}',
null, // type not set
true); // isTemplate
return true;
}
/**
* Plugin version information
*
* @param array $versions
* @return bool hook return value
*/
public function onPluginVersion(array &$versions): bool
{
$versions[] = [
'name' => 'RemoteFollow',
'version' => self::PLUGIN_VERSION,
'author' => 'Bruno Casteleiro',
'homepage' => 'https://notabug.org/diogo/gnu-social/src/nightly/plugins/RemoteFollow',
// TRANS: Plugin description.
'rawdescription' => _m('Add remote-follow button support to GNU social')
];
return true;
}
/**
* Add remote-follow button to some required action
*
* @param Action $action
* @return void
*/
public function showEntityRemoteSubscribe(Action $action): void
{
if (!$action->getScoped() instanceof Profile) {
// not logged in
return;
}
if ($action->getScoped()->sameAs($action->getTarget())) {
$action->elementStart('div', 'entity_actions');
$action->elementStart('p', ['id' => 'entity_remote_subscribe',
'class' => 'entity_subscribe']);
$action->element('a',
['href' => common_local_url('RemoteFollowSub'),
'class' => 'entity_remote_subscribe'],
// TRANS: Link text for link to remote subscribe.
_m('Remote'));
$action->elementEnd('p');
$action->elementEnd('div');
}
}
}

View File

@ -0,0 +1,236 @@
<?php
// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social 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.
//
// GNU social 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 GNU social. If not, see <http://www.gnu.org/licenses/>.
/**
* Remote Follow implementation for GNU social
*
* @package GNUsocial
* @author Bruno Casteleiro <brunoccast@fc.up.pt>
* @copyright 2019 Free Software Foundation, Inc http://www.fsf.org
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/
defined('GNUSOCIAL') || die();
/**
* Remote-follow preparation action
*
* @category Plugin
* @package GNUsocial
* @author Bruno Casteleiro <brunoccast@fc.up.pt>
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/
class RemoteFollowInitAction extends Action
{
protected $target = null;
protected $profile = null;
protected function prepare(array $args = [])
{
parent::prepare($args);
if (common_logged_in()) {
// TRANS: Client error displayed when the user is logged in.
$this->clientError(_m('You can use the local subscription!'));
}
// Local user the remote wants to follow
$nickname = $this->trimmed('nickname');
$this->target = User::getKV('nickname', $nickname);
if (!$this->target instanceof User) {
// TRANS: Client error displayed when targeting an invalid user.
$this->clientError(_m('No such user.'));
}
// Webfinger or profile URL of the remote user
$this->profile = $this->trimmed('profile');
return true;
}
protected function handle()
{
parent::handle();
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
/* Use a session token for CSRF protection. */
$token = $this->trimmed('token');
if (!$token || $token != common_session_token()) {
// TRANS: Error displayed when the session token does not match or is not given.
$this->showForm(_m('There was a problem with your session token. '.
'Try again, please.'));
return;
}
$url = null;
if (Event::handle('RemoteFollowConnectProfile', [$this->target, $this->profile, &$url])) {
// use ported ostatus connect functions to find remote url
$url = self::ostatusConnect($this->target, $this->profile);
}
if (!is_null($url)) {
common_redirect($url, 303);
}
// TRANS: Error displayed when there is failure in connecting with the remote profile.
$this->showForm(_m('There was a problem connecting with the remote profile. '.
'Try again, please.'));
} else {
$this->showForm();
}
}
function showContent()
{
// TRANS: Form legend. %s is a nickname.
$header = sprintf(_m('Subscribe to %s'), $this->target->getNickname());
// TRANS: Button text to subscribe to a profile.
$submit = _m('BUTTON', 'Subscribe');
$this->elementStart('form',
['id' => 'form_ostatus_connect',
'method' => 'post',
'class' => 'form_settings',
'action' => common_local_url('RemoteFollowInit')]);
$this->elementStart('fieldset');
$this->element('legend', null, $header);
$this->hidden('token', common_session_token());
$this->elementStart('ul', 'form_data');
$this->elementStart('li', ['id' => 'ostatus_nickname']);
$this->input('nickname',
// TRANS: Field label.
_m('User nickname'),
$this->target->getNickname(),
// TRANS: Field title.
_m('Nickname of the user you want to follow.'));
$this->elementEnd('li');
$this->elementStart('li', ['id' => 'ostatus_profile']);
$this->input('profile',
// TRANS: Field label.
_m('Profile Account'),
$this->profile,
// TRANS: Tooltip for field label "Profile Account".
_m('Your account ID (e.g. user@example.com).'));
$this->elementEnd('li');
$this->elementEnd('ul');
$this->submit('submit', $submit);
$this->elementEnd('fieldset');
$this->elementEnd('form');
}
public function showForm($err = null)
{
if ($err) {
$this->error = $err;
}
if ($this->boolean('ajax')) {
$this->startHTML('text/xml;charset=utf-8');
$this->elementStart('head');
// TRANS: Form title.
$this->element('title', null, _m('TITLE','Subscribe to user'));
$this->elementEnd('head');
$this->elementStart('body');
$this->showContent();
$this->elementEnd('body');
$this->endHTML();
} else {
$this->showPage();
}
}
/**
* Find remote url to finish follow interaction
*
* @param User $target local user to be followed
* @param string $remote ID of the remote subscriber
* @return string|null
*/
public static function ostatusConnect(User $target, string $remote): ?string
{
$validate = new Validate();
$opts = ['allowed_schemes' => ['http', 'https', 'acct']];
if ($validate->uri($remote, $opts)) {
$bits = parse_url($remote);
if ($bits['scheme'] == 'acct') {
return self::connectWebfinger($bits['path'], $target);
} else {
return self::connectProfile($remote, $target);
}
} else if (strpos($remote, '@') !== false) {
return self::connectWebfinger($remote, $target);
}
common_log(LOG_ERR, 'Must provide a remote profile');
return null;
}
/**
* Find remote url to finish follow interaction from a webfinger ID
*
* @param string $acct
* @param User $target
* @return string|null
* @see ostatusConnect
*/
public static function connectWebfinger(string $acct, User $target): ?string
{
$target = common_local_url('userbyid', ['id' => $target->getID()]);
$disco = new Discovery;
$xrd = $disco->lookup($acct);
$link = $xrd->get('http://ostatus.org/schema/1.0/subscribe');
if (!is_null($link)) {
// We found a URL - let's redirect!
if (!empty($link->template)) {
$url = Discovery::applyTemplate($link->template, $target);
} else {
$url = $link->href;
}
common_log(LOG_INFO, "Retrieving url $url for remote subscriber $acct");
return $url;
}
common_log(LOG_ERR, "Could not confirm remote profile $acct");
return null;
}
/**
* Find remote url to finish follow interaction from an url ID
*
* @param string $acct
* @param User $target
* @return string
* @see ostatusConnect
*/
public static function connectProfile(string $url, User $target): string
{
$target = common_local_url('userbyid', ['id' => $target->getID()]);
// @fixme hack hack! We should look up the remote sub URL from XRDS
$suburl = preg_replace('!^(.*)/(.*?)$!', '$1/main/ostatussub', $url);
$suburl .= '?profile=' . urlencode($target);
common_log(LOG_INFO, "Retrieving url $suburl for remote subscriber $url");
return $suburl;
}
}

View File

@ -0,0 +1,394 @@
<?php
// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social 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.
//
// GNU social 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 GNU social. If not, see <http://www.gnu.org/licenses/>.
/**
* Remote Follow implementation for GNU social
*
* @package GNUsocial
* @author Bruno Casteleiro <brunoccast@fc.up.pt>
* @copyright 2019 Free Software Foundation, Inc http://www.fsf.org
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/
defined('GNUSOCIAL') || die();
/**
* Remote-follow follow action
*
* @category Plugin
* @package GNUsocial
* @author Bruno Casteleiro <brunoccast@fc.up.pt>
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/
class RemoteFollowSubAction extends Action
{
protected $uri; // acct: or uri of remote entity
protected $profile; // profile of remote entity, if valid
protected function prepare(array $args = [])
{
parent::prepare($args);
if (!common_logged_in()) {
common_set_returnto($_SERVER['REQUEST_URI']);
if (Event::handle('RedirectToLogin', [$this, null])) {
common_redirect(common_local_url('login'), 303);
}
return false;
}
if (!$this->profile && $this->arg('profile')) {
$this->uri = $this->trimmed('profile');
$profile = null;
if (!Event::handle('RemoteFollowPullProfile', [$this->uri, &$profile]) && !is_null($profile)) {
$this->profile = $profile;
} else {
// TRANS: Error displayed when there's failure in fetching the remote profile.
$this->error = _m('Sorry, we could not reach that address. ' .
'Please make sure it is a valid address and try again later.');
}
}
return true;
}
/**
* Handles the submission.
*
* @return void
*/
protected function handle(): void
{
parent::handle();
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$this->handlePost();
} else {
$this->showForm();
}
}
/**
* Show the initial form, when we haven't yet been given a valid
* remote profile.
*
* @return void
*/
public function showInputForm(): void
{
$this->elementStart('form', ['method' => 'post',
'id' => 'form_ostatus_sub',
'class' => 'form_settings',
'action' => $this->selfLink()]);
$this->hidden('token', common_session_token());
$this->elementStart('fieldset', ['id' => 'settings_feeds']);
$this->elementStart('ul', 'form_data');
$this->elementStart('li');
$this->input('profile',
// TRANS: Field label for a field that takes an user address.
_m('Subscribe to'),
$this->uri,
// TRANS: Tooltip for field label "Subscribe to".
_m('User\'s address, like nickname@example.com or http://example.net/nickname.'));
$this->elementEnd('li');
$this->elementEnd('ul');
// TRANS: Button text.
$this->submit('validate', _m('BUTTON','Continue'));
$this->elementEnd('fieldset');
$this->elementEnd('form');
}
/**
* Show the preview-and-confirm form. We've got a valid remote
* profile and are ready to poke it!
*
* @return void
*/
public function showPreviewForm(): void
{
if (!$this->preview()) {
return;
}
$this->elementStart('div', 'entity_actions');
$this->elementStart('ul');
$this->elementStart('li', 'entity_subscribe');
$this->elementStart('form', ['method' => 'post',
'id' => 'form_ostatus_sub',
'class' => 'form_remote_authorize',
'action' => $this->selfLink()]);
$this->elementStart('fieldset');
$this->hidden('token', common_session_token());
$this->hidden('profile', $this->uri);
$this->submit('submit',
// TRANS: Button text.
_m('BUTTON','Confirm'),
'submit',
null,
// TRANS: Tooltip for button "Confirm".
_m('Subscribe to this user'));
$this->elementEnd('fieldset');
$this->elementEnd('form');
$this->elementEnd('li');
$this->elementEnd('ul');
$this->elementEnd('div');
}
/**
* Show a preview for a remote user's profile.
*
* @return bool true if we're ok to try subscribing, false otherwise
*/
public function preview(): bool
{
if ($this->scoped->isSubscribed($this->profile)) {
$this->element('div',
['class' => 'error'],
// TRANS: Extra paragraph in remote profile view when already subscribed.
_m('You are already subscribed to this user.'));
$ok = false;
} else {
$ok = true;
}
$avatarUrl = $this->profile->avatarUrl(AVATAR_PROFILE_SIZE);
$this->showEntity($this->profile,
$this->profile->getUrl(),
$avatarUrl,
$this->profile->getDescription());
return $ok;
}
/**
* Show someone's profile.
*
* @return void
*/
public function showEntity(Profile $entity, string $profile_url, string $avatar, ?string $note): void
{
$nickname = $entity->getNickname();
$fullname = $entity->getFullname();
$homepage = $entity->getHomepage();
$location = $entity->getLocation();
$this->elementStart('div', 'entity_profile vcard');
$this->element('img', ['src' => $avatar,
'class' => 'photo avatar entity_depiction',
'width' => AVATAR_PROFILE_SIZE,
'height' => AVATAR_PROFILE_SIZE,
'alt' => $nickname]);
$hasFN = ($fullname !== '') ? 'nickname' : 'fn nickname entity_nickname';
$this->elementStart('a', ['href' => $profile_url,
'class' => 'url '.$hasFN]);
$this->text($nickname);
$this->elementEnd('a');
if (!is_null($fullname)) {
$this->elementStart('div', 'fn entity_fn');
$this->text($fullname);
$this->elementEnd('div');
}
if (!is_null($location)) {
$this->elementStart('div', 'label entity_location');
$this->text($location);
$this->elementEnd('div');
}
if (!is_null($homepage)) {
$this->elementStart('a', ['href' => $homepage,
'class' => 'url entity_url']);
$this->text($homepage);
$this->elementEnd('a');
}
if (!is_null($note)) {
$this->elementStart('div', 'note entity_note');
$this->text($note);
$this->elementEnd('div');
}
$this->elementEnd('div');
}
/**
* Redirect on successful remote follow
*
* @return void
*/
public function success(): void
{
$url = common_local_url('subscriptions', ['nickname' => $this->scoped->getNickname()]);
common_redirect($url, 303);
}
/**
* Attempt to finalize subscription.
*
* @return void
*/
public function follow(): void
{
if ($this->scoped->isSubscribed($this->profile)) {
// TRANS: Remote subscription dialog error.
$this->showForm(_m('Already subscribed!'));
} elseif (Subscription::start($this->scoped, $this->profile)) {
$this->success();
} else {
// TRANS: Remote subscription dialog error.
$this->showForm(_m('Remote subscription failed!'));
}
}
/**
* Handle posts to this form
*
* @return void
*/
public function handlePost(): void
{
// CSRF protection
$token = $this->trimmed('token');
if (!$token || $token != common_session_token()) {
// TRANS: Client error displayed when the session token does not match or is not given.
$this->showForm(_m('There was a problem with your session token. '.
'Try again, please.'));
return;
}
if ($this->profile && $this->arg('submit')) {
$this->follow();
return;
}
$this->showForm();
}
/**
* Show the appropriate form based on our input state.
*
* @return void
*/
public function showForm(?string $err = null): void
{
if ($err) {
$this->error = $err;
}
if ($this->boolean('ajax')) {
$this->startHTML('text/xml;charset=utf-8');
$this->elementStart('head');
// TRANS: Form title.
$this->element('title', null, _m('Subscribe to user'));
$this->elementEnd('head');
$this->elementStart('body');
$this->showContent();
$this->elementEnd('body');
$this->endHTML();
} else {
$this->showPage();
}
}
/**
* Title of the page
*
* @return string title of the page
*/
public function title(): string
{
// TRANS: Page title for remote subscription form.
return !empty($this->uri) ? _m('Confirm') : _m('Remote subscription');
}
/**
* Instructions for use
*
* @return string instructions for use
*/
public function getInstructions(): string
{
// TRANS: Instructions.
return _m('You can subscribe to users from other supported sites. Paste their address or profile URI below:');
}
/**
* Show page notice.
*
* @return void
*/
public function showPageNotice(): void
{
if (!empty($this->error)) {
$this->element('p', 'error', $this->error);
}
}
/**
* Content area of the page
*
* @return void
*/
public function showContent(): void
{
if ($this->profile) {
$this->showPreviewForm();
} else {
$this->showInputForm();
}
}
/**
* Show javascript headers
*
* @return void
*/
public function showScripts(): void
{
parent::showScripts();
$this->autofocus('profile');
}
/**
* Return url for this action
*
* @return string
*/
function selfLink(): string
{
return common_local_url('RemoteFollowSub');
}
/**
* Disable the send-notice form at the top of the page.
* This is really just a hack for the broken CSS in the Cloudy theme,
* I think; copying from other non-notice-navigation pages that do this
* as well. There will be plenty of others also broken.
*
* @fixme fix the cloudy theme
* @fixme do this in a more general way
*/
public function showNoticeForm(): void
{
// nop
}
}

View File

@ -0,0 +1,163 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-08-25 01:23+0100\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"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"
#. TRANS: Link text for the follow button
#: RemoteFollowPlugin.php:70
msgid "Subscribe"
msgstr ""
#. TRANS: Plugin description.
#: RemoteFollowPlugin.php:127
msgid "Add remote-follow button support to GNU social"
msgstr ""
#. TRANS: Link text for link to remote subscribe.
#: RemoteFollowPlugin:153
msgid "Remote"
msgstr ""
#. TRANS: Client error displayed when the user is logged in.
#: remotefollowinit.php:46
msgid "You can use the local subscription!"
msgstr ""
#. TRANS: Client error displayed when targeting an invalid user.
#: remotefollowinit.php:55
msgid "No such user."
msgstr ""
#. TRANS: Error displayed when the session token does not match or is not given.
#: remotefollowinit.php:73, remotefollowsub.php:272
msgid ""
"There was a problem with your session token. "
"Try again, please."
msgstr ""
#. TRANS: Error displayed when there is failure in connecting with the remote profile.
#: remotefollowinit.php:89
msgid ""
"There was a problem connecting with the remote profile. "
"Try again, please."
msgstr ""
#. TRANS: Form legend. %s is a nickname.
#: remotefollowinit.php:100
msgid "Subscribe to %s"
msgstr ""
#. TRANS: Button text to subscribe to a profile.
#: remotefollowinit.php:102
msgctxt "BUTTON"
msgid "Subscribe"
msgstr ""
#. TRANS: Field label.
#: remotefollowinit.php:118
msgid "User nickname"
msgstr ""
#. TRANS: Field title.
#: remotefollowinit.php:121
msgid "Nickname of the user you want to follow."
msgstr ""
#. TRANS: Field label.
#: remotefollowinit.php:127
msgid "Profile Account"
msgstr ""
#. TRANS: Tooltip for field label "Profile Account".
#: remotefollowinit.php:130
msgid "Your account ID (e.g. user@example.com)."
msgstr ""
#. TRANS: Form title.
#: remotefollowinit.php:148
msgctxt "TITLE"
msgid "Subscribe to user"
msgstr ""
#. TRANS: Error displayed when there's failure in fetching the remote profile.
#: remotefollowsub.php:60
msgid ""
"Sorry, we could not reach that address. "
"Please make sure it is a valid address and try again later."
msgstr ""
#. TRANS: Field label for a field that takes an user address.
#: remotefollowsub.php:104
msgid "Subscribe to"
msgstr ""
#. TRANS: Tooltip for field label "Subscribe to".
#: remotefollowsub.php:107
msgid "User\'s address, like nickname@example.com or http://example.net/nickname."
msgstr ""
#. TRANS: Button text.
#: remotefollowsub.php:111
msgctxt "BUTTON"
msgid "Continue"
msgstr ""
#. TRANS: Button text.
#: remotefollowsub.php:142
msgctxt "BUTTON"
msgid "Confirm"
msgstr ""
#. TRANS: Tooltip for button "Confirm".
#: remotefollowsub.php:147
msgid "Subscribe to this user"
msgstr ""
#. TRANS: Extra paragraph in remote profile view when already subscribed.
#: remotefollowsub.php:165
msgid "You are already subscribed to this user."
msgstr ""
#. TRANS: Remote subscription dialog error.
#: remotefollowsub.php:252
msgid "Already subscribed!"
msgstr ""
#. TRANS: Remote subscription dialog error.
#: remotefollowsub.php:257
msgid "Remote subscription failed!"
msgstr ""
#. TRANS: Form title.
#: remotefollowsub.php:300
msgid "Subscribe to user"
msgstr ""
#. TRANS: Page title for remote subscription form.
#: remotefollowsub.php:319
msgid "Confirm"
msgstr ""
#. TRANS: Page title for remote subscription form.
#: remotefollowsub.php:319
msgid "Remote subscription"
msgstr ""
#. TRANS: Instructions.
#: remotefollowsub.php:330
msgid "You can subscribe to users from other supported sites. Paste their address or profile URI below"
msgstr ""

View File

@ -0,0 +1,170 @@
# Translation file for GNU social - the free software social networking platform
# Copyright (C) 2019 Free Software Foundation, Inc http://www.fsf.org
# This file is under https://www.gnu.org/licenses/agpl v3 or later
#
# Translators:
# Bruno Casteleiro <brunoccast@fc.up.pt>, 2019
msgid ""
msgstr ""
"Project-Id-Version: GNU social\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-08-25 01:23+0100\n"
"PO-Revision-Date: 2019-08-25 01:23+0100\n"
"Last-Translator: Bruno Casteleiro <brunoccast@fc.up.pt>\n"
"Language-Team: English (United Kingdom) (http://www.transifex.com/gnu-social/gnu-social/language/en_GB/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: en_GB\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#. TRANS: Link text for the follow button
#: RemoteFollowPlugin.php:70
msgid "Subscribe"
msgstr "Subscribe"
#. TRANS: Plugin description.
#: RemoteFollowPlugin.php:127
msgid "Add remote-follow button support to GNU social"
msgstr "Add remote-follow button support to GNU social"
#. TRANS: Link text for link to remote subscribe.
#: RemoteFollowPlugin:153
msgid "Remote"
msgstr "Remote"
#. TRANS: Client error displayed when the user is logged in.
#: remotefollowinit.php:46
msgid "You can use the local subscription!"
msgstr "You can use the local subscription!"
#. TRANS: Client error displayed when targeting an invalid user.
#: remotefollowinit.php:55
msgid "No such user."
msgstr "No such user."
#. TRANS: Error displayed when the session token does not match or is not given.
#: remotefollowinit.php:73, remotefollowsub.php:272
msgid ""
"There was a problem with your session token. "
"Try again, please."
msgstr ""
"There was a problem with your session token. "
"Try again, please."
#. TRANS: Error displayed when there is failure in connecting with the remote profile.
#: remotefollowinit.php:89
msgid ""
"There was a problem connecting with the remote profile. "
"Try again, please."
msgstr ""
"There was a problem connecting with the remote profile. "
"Try again, please."
#. TRANS: Form legend. %s is a nickname.
#: remotefollowinit.php:100
msgid "Subscribe to %s"
msgstr "Subscribe to %s"
#. TRANS: Button text to subscribe to a profile.
#: remotefollowinit.php:102
msgctxt "BUTTON"
msgid "Subscribe"
msgstr "Subscribe"
#. TRANS: Field label.
#: remotefollowinit.php:118
msgid "User nickname"
msgstr "User nickname"
#. TRANS: Field title.
#: remotefollowinit.php:121
msgid "Nickname of the user you want to follow."
msgstr "Nickname of the user you want to follow."
#. TRANS: Field label.
#: remotefollowinit.php:127
msgid "Profile Account"
msgstr "Profile Account"
#. TRANS: Tooltip for field label "Profile Account".
#: remotefollowinit.php:130
msgid "Your account ID (e.g. user@example.com)."
msgstr "Your account ID (e.g. user@example.com)."
#. TRANS: Form title.
#: remotefollowinit.php:148
msgctxt "TITLE"
msgid "Subscribe to user"
msgstr "Subscribe to user"
#. TRANS: Error displayed when there's failure in fetching the remote profile.
#: remotefollowsub.php:60
msgid ""
"Sorry, we could not reach that address. "
"Please make sure it is a valid address and try again later."
msgstr ""
"Sorry, we could not reach that address. "
"Please make sure it is a valid address and try again later."
#. TRANS: Field label for a field that takes an user address.
#: remotefollowsub.php:104
msgid "Subscribe to"
msgstr "Subscribe to"
#. TRANS: Tooltip for field label "Subscribe to".
#: remotefollowsub.php:107
msgid "User's address, like nickname@example.com or http://example.net/nickname."
msgstr "User's address, like nickname@example.com or http://example.net/nickname."
#. TRANS: Button text.
#: remotefollowsub.php:111
msgctxt "BUTTON"
msgid "Continue"
msgstr "Continue"
#. TRANS: Button text.
#: remotefollowsub.php:142
msgctxt "BUTTON"
msgid "Confirm"
msgstr "Confirm"
#. TRANS: Tooltip for button "Confirm".
#: remotefollowsub.php:147
msgid "Subscribe to this user"
msgstr "Subscribe to this user"
#. TRANS: Extra paragraph in remote profile view when already subscribed.
#: remotefollowsub.php:165
msgid "You are already subscribed to this user."
msgstr "You are already subscribed to this user."
#. TRANS: Remote subscription dialog error.
#: remotefollowsub.php:252
msgid "Already subscribed!"
msgstr "Already subscribed!"
#. TRANS: Remote subscription dialog error.
#: remotefollowsub.php:257
msgid "Remote subscription failed!"
msgstr "Remote subscription failed!"
#. TRANS: Form title.
#: remotefollowsub.php:300
msgid "Subscribe to user"
msgstr "Subscribe to user"
#. TRANS: Page title for remote subscription form.
#: remotefollowsub.php:319
msgid "Confirm"
msgstr "Confirm"
#. TRANS: Page title for remote subscription form.
#: remotefollowsub.php:319
msgid "Remote subscription"
msgstr "Remote subscription"
#. TRANS: Instructions.
#: remotefollowsub.php:330
msgid "You can subscribe to users from other supported sites. Paste their address or profile URI below"
msgstr "You can subscribe to users from other supported sites. Paste their address or profile URI below"

View File

@ -0,0 +1,170 @@
# Translation file for GNU social - the free software social networking platform
# Copyright (C) 2019 Free Software Foundation, Inc http://www.fsf.org
# This file is under https://www.gnu.org/licenses/agpl v3 or later
#
# Translators:
# Bruno Casteleiro <brunoccast@fc.up.pt>, 2019
msgid ""
msgstr ""
"Project-Id-Version: GNU social\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-08-25 01:23+0100\n"
"PO-Revision-Date: 2019-08-25 01:23+0100\n"
"Last-Translator: Bruno Casteleiro <brunoccast@fc.up.pt>\n"
"Language-Team: Portuguese (http://www.transifex.com/gnu-social/gnu-social/language/pt/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: pt\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#. TRANS: Link text for the follow button
#: RemoteFollowPlugin.php:70
msgid "Subscribe"
msgstr "Subscrever"
#. TRANS: Plugin description.
#: RemoteFollowPlugin.php:127
msgid "Add remote-follow button support to GNU social"
msgstr "Adiciona suporte para o botão de subscrição remota ao GNU social"
#. TRANS: Link text for link to remote subscribe.
#: RemoteFollowPlugin:153
msgid "Remote"
msgstr "Remoto"
#. TRANS: Client error displayed when the user is logged in.
#: remotefollowinit.php:46
msgid "You can use the local subscription!"
msgstr "Pode usar a subscrição local!"
#. TRANS: Client error displayed when targeting an invalid user.
#: remotefollowinit.php:55
msgid "No such user."
msgstr "Utilizador inexistente."
#. TRANS: Error displayed when the session token does not match or is not given.
#: remotefollowinit.php:73, remotefollowsub.php:272
msgid ""
"There was a problem with your session token. "
"Try again, please."
msgstr ""
"Ocorreu um problema com o seu token de sessão. "
"Tente outra vez, por favor."
#. TRANS: Error displayed when there is failure in connecting with the remote profile.
#: remotefollowinit.php:89
msgid ""
"There was a problem connecting with the remote profile. "
"Try again, please."
msgstr ""
"Ocorreu um problema ao conectar com o perfil remoto. "
"Tente outra vez, por favor."
#. TRANS: Form legend. %s is a nickname.
#: remotefollowinit.php:100
msgid "Subscribe to %s"
msgstr "Subscrever %s"
#. TRANS: Button text to subscribe to a profile.
#: remotefollowinit.php:102
msgctxt "BUTTON"
msgid "Subscribe"
msgstr "Subscrever"
#. TRANS: Field label.
#: remotefollowinit.php:118
msgid "User nickname"
msgstr "Apelido do utilizador"
#. TRANS: Field title.
#: remotefollowinit.php:121
msgid "Nickname of the user you want to follow."
msgstr "Apelido do utilizador que pretende seguir."
#. TRANS: Field label.
#: remotefollowinit.php:127
msgid "Profile Account"
msgstr "Conta do perfil"
#. TRANS: Tooltip for field label "Profile Account".
#: remotefollowinit.php:130
msgid "Your account ID (e.g. user@example.com)."
msgstr "O ID da sua conta (e.g. utilizador@exemplo.com)."
#. TRANS: Form title.
#: remotefollowinit.php:148
msgctxt "TITLE"
msgid "Subscribe to user"
msgstr "Subscrever utilizador"
#. TRANS: Error displayed when there's failure in fetching the remote profile.
#: remotefollowsub.php:60
msgid ""
"Sorry, we could not reach that address. "
"Please make sure it is a valid address and try again later."
msgstr ""
"Desculpe, não conseguimos aceder a esse endereço. "
"Por favor verifique que é um endereço válido e tente outra vez mais tarde."
#. TRANS: Field label for a field that takes an user address.
#: remotefollowsub.php:104
msgid "Subscribe to"
msgstr "Subscrever"
#. TRANS: Tooltip for field label "Subscribe to".
#: remotefollowsub.php:107
msgid "User's address, like nickname@example.com or http://example.net/nickname."
msgstr "Endereço do utilizador, do tipo apelido@exemplo.com ou http://exemplo.net/apelido."
#. TRANS: Button text.
#: remotefollowsub.php:111
msgctxt "BUTTON"
msgid "Continue"
msgstr "Continuar"
#. TRANS: Button text.
#: remotefollowsub.php:142
msgctxt "BUTTON"
msgid "Confirm"
msgstr "Confirmar"
#. TRANS: Tooltip for button "Confirm".
#: remotefollowsub.php:147
msgid "Subscribe to this user"
msgstr "Subscrever este utilizador"
#. TRANS: Extra paragraph in remote profile view when already subscribed.
#: remotefollowsub.php:165
msgid "You are already subscribed to this user."
msgstr "Este utilizador ja foi subscrito."
#. TRANS: Remote subscription dialog error.
#: remotefollowsub.php:252
msgid "Already subscribed!"
msgstr "Já subscrito!"
#. TRANS: Remote subscription dialog error.
#: remotefollowsub.php:257
msgid "Remote subscription failed!"
msgstr "Subscrição remota falhou!"
#. TRANS: Form title.
#: remotefollowsub.php:300
msgid "Subscribe to user"
msgstr "Subscrever utilizador"
#. TRANS: Page title for remote subscription form.
#: remotefollowsub.php:319
msgid "Confirm"
msgstr "Confirmar"
#. TRANS: Page title for remote subscription form.
#: remotefollowsub.php:319
msgid "Remote subscription"
msgstr "Subscrição remota"
#. TRANS: Instructions.
#: remotefollowsub.php:330
msgid "You can subscribe to users from other supported sites. Paste their address or profile URI below"
msgstr "Pode subscrever utilizadores de outros sites suportados. Coloque o endereço do mesmo em baixo"