Merge pull request #7930 from MrPetovan/task/7887-api-followers-request
Add POST follow request Mastodon API endpoint
This commit is contained in:
commit
5197833c55
12 changed files with 424 additions and 142 deletions
|
@ -16,11 +16,18 @@ These endpoints use the [Mastodon API entities](https://docs.joinmastodon.org/ap
|
||||||
## Implemented endpoints
|
## Implemented endpoints
|
||||||
|
|
||||||
- [GET /api/v1/follow_requests](https://docs.joinmastodon.org/api/rest/follow-requests/#get-api-v1-follow-requests)
|
- [GET /api/v1/follow_requests](https://docs.joinmastodon.org/api/rest/follow-requests/#get-api-v1-follow-requests)
|
||||||
|
- [POST /api/v1/follow_requests/:id/authorize](https://docs.joinmastodon.org/api/rest/follow-requests/#post-api-v1-follow-requests-id-authorize)
|
||||||
|
- Returns a [Relationship](https://docs.joinmastodon.org/api/entities/#relationship) object.
|
||||||
|
- [POST /api/v1/follow_requests/:id/reject](https://docs.joinmastodon.org/api/rest/follow-requests/#post-api-v1-follow-requests-id-reject)
|
||||||
|
- Returns a [Relationship](https://docs.joinmastodon.org/api/entities/#relationship) object.
|
||||||
|
- POST /api/v1/follow_requests/:id/ignore
|
||||||
|
- Friendica-specific, hides the follow request from the list and prevents the remote contact from retrying.
|
||||||
|
- Returns a [Relationship](https://docs.joinmastodon.org/api/entities/#relationship) object.
|
||||||
|
|
||||||
|
|
||||||
- [GET /api/v1/instance](https://docs.joinmastodon.org/api/rest/instances)
|
- [GET /api/v1/instance](https://docs.joinmastodon.org/api/rest/instances)
|
||||||
- GET /api/v1/instance/peers - undocumented, but implemented by Mastodon and Pleroma
|
- GET /api/v1/instance/peers - undocumented, but implemented by Mastodon and Pleroma
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Non-implemented endpoints
|
## Non-implemented endpoints
|
||||||
|
|
||||||
- [POST /api/v1/follow_requests/:id/authorize](https://docs.joinmastodon.org/api/rest/follow-requests/#post-api-v1-follow-requests-id-authorize)
|
|
||||||
- [POST /api/v1/follow_requests/:id/reject](https://docs.joinmastodon.org/api/rest/follow-requests/#post-api-v1-follow-requests-id-reject)
|
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ use Friendica\Core\Renderer;
|
||||||
use Friendica\Core\System;
|
use Friendica\Core\System;
|
||||||
use Friendica\Database\DBA;
|
use Friendica\Database\DBA;
|
||||||
use Friendica\Module\Login;
|
use Friendica\Module\Login;
|
||||||
use Friendica\Model\Contact;
|
use Friendica\Model\Introduction;
|
||||||
use Friendica\Model\Notify;
|
use Friendica\Model\Notify;
|
||||||
|
|
||||||
function notifications_post(App $a)
|
function notifications_post(App $a)
|
||||||
|
@ -30,43 +30,20 @@ function notifications_post(App $a)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request_id) {
|
if ($request_id) {
|
||||||
$intro = DBA::selectFirst('intro', ['id', 'contact-id', 'fid'], ['id' => $request_id, 'uid' => local_user()]);
|
/** @var Introduction $Intro */
|
||||||
|
$Intro = \Friendica\BaseObject::getClass(Introduction::class);
|
||||||
|
$Intro->fetch(['id' => $request_id, 'uid' => local_user()]);
|
||||||
|
|
||||||
if (DBA::isResult($intro)) {
|
switch ($_POST['submit']) {
|
||||||
$intro_id = $intro['id'];
|
case L10n::t('Discard'):
|
||||||
$contact_id = $intro['contact-id'];
|
$Intro->discard();
|
||||||
} else {
|
break;
|
||||||
notice(L10n::t('Invalid request identifier.') . EOL);
|
case L10n::t('Ignore'):
|
||||||
return;
|
$Intro->ignore();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If it is a friend suggestion, the contact is not a new friend but an existing friend
|
$a->internalRedirect('notifications/intros');
|
||||||
// that should not be deleted.
|
|
||||||
|
|
||||||
$fid = $intro['fid'];
|
|
||||||
|
|
||||||
if ($_POST['submit'] == L10n::t('Discard')) {
|
|
||||||
DBA::delete('intro', ['id' => $intro_id]);
|
|
||||||
if (!$fid) {
|
|
||||||
// When the contact entry had been created just for that intro, we want to get rid of it now
|
|
||||||
$condition = ['id' => $contact_id, 'uid' => local_user(),
|
|
||||||
'self' => false, 'pending' => true, 'rel' => [0, Contact::FOLLOWER]];
|
|
||||||
$contact_pending = DBA::exists('contact', $condition);
|
|
||||||
|
|
||||||
// Remove the "pending" to stop the reappearing in any case
|
|
||||||
DBA::update('contact', ['pending' => false], ['id' => $contact_id]);
|
|
||||||
|
|
||||||
if ($contact_pending) {
|
|
||||||
Contact::remove($contact_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$a->internalRedirect('notifications/intros');
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($_POST['submit'] == L10n::t('Ignore')) {
|
|
||||||
DBA::update('intro', ['ignore' => true], ['id' => $intro_id]);
|
|
||||||
$a->internalRedirect('notifications/intros');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ namespace Friendica\Api\Mastodon;
|
||||||
|
|
||||||
use Friendica\Content\Text\BBCode;
|
use Friendica\Content\Text\BBCode;
|
||||||
use Friendica\Database\DBA;
|
use Friendica\Database\DBA;
|
||||||
|
use Friendica\Model\Contact;
|
||||||
use Friendica\Util\DateTimeFormat;
|
use Friendica\Util\DateTimeFormat;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -55,31 +56,33 @@ class Account
|
||||||
/**
|
/**
|
||||||
* Creates an account record from a contact record. Expects all contact table fields to be set
|
* Creates an account record from a contact record. Expects all contact table fields to be set
|
||||||
*
|
*
|
||||||
* @param array $contact
|
* @param array $contact Full contact table record
|
||||||
|
* @param array $apcontact Full apcontact table record
|
||||||
* @return Account
|
* @return Account
|
||||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||||
*/
|
*/
|
||||||
public static function createFromContact(array $contact) {
|
public static function createFromContact(array $contact, array $apcontact = [])
|
||||||
|
{
|
||||||
$account = new Account();
|
$account = new Account();
|
||||||
$account->id = $contact['id'];
|
$account->id = $contact['id'];
|
||||||
$account->username = $contact['nick'];
|
$account->username = $contact['nick'];
|
||||||
$account->acct = $contact['nick'];
|
$account->acct = $contact['nick'];
|
||||||
$account->display_name = $contact['name'];
|
$account->display_name = $contact['name'];
|
||||||
$account->locked = $contact['blocked'];
|
$account->locked = !empty($apcontact['manually-approve']);
|
||||||
$account->created_at = DateTimeFormat::utc($contact['created'], DateTimeFormat::ATOM);
|
$account->created_at = DateTimeFormat::utc($contact['created'], DateTimeFormat::ATOM);
|
||||||
// No data is available from contact
|
$account->followers_count = $apcontact['followers_count'] ?? 0;
|
||||||
$account->followers_count = 0;
|
$account->following_count = $apcontact['following_count'] ?? 0;
|
||||||
$account->following_count = 0;
|
$account->statuses_count = $apcontact['statuses_count'] ?? 0;
|
||||||
$account->statuses_count = 0;
|
$account->note = BBCode::convert($contact['about'], false);
|
||||||
$account->note = BBCode::convert($contact['about']);
|
$account->url = $contact['url'];
|
||||||
$account->url = $contact['url'];
|
$account->avatar = $contact['avatar'];
|
||||||
$account->avatar = $contact['avatar'];
|
$account->avatar_static = $contact['avatar'];
|
||||||
$account->avatar_static = $contact['avatar'];
|
|
||||||
// No header picture in Friendica
|
// No header picture in Friendica
|
||||||
$account->header = '';
|
$account->header = '';
|
||||||
$account->header_static = '';
|
$account->header_static = '';
|
||||||
// No custom emojis per account in Friendica
|
// No custom emojis per account in Friendica
|
||||||
$account->emojis = [];
|
$account->emojis = [];
|
||||||
|
$account->bot = ($contact['contact-type'] == Contact::TYPE_NEWS);
|
||||||
|
|
||||||
return $account;
|
return $account;
|
||||||
}
|
}
|
||||||
|
|
59
src/Api/Mastodon/Relationship.php
Normal file
59
src/Api/Mastodon/Relationship.php
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Friendica\Api\Mastodon;
|
||||||
|
|
||||||
|
use Friendica\Model\Contact;
|
||||||
|
use Friendica\Util\Network;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class Relationship
|
||||||
|
*
|
||||||
|
* @see https://docs.joinmastodon.org/api/entities/#relationship
|
||||||
|
*/
|
||||||
|
class Relationship
|
||||||
|
{
|
||||||
|
/** @var int */
|
||||||
|
var $id;
|
||||||
|
/** @var bool */
|
||||||
|
var $following = false;
|
||||||
|
/** @var bool */
|
||||||
|
var $followed_by = false;
|
||||||
|
/** @var bool */
|
||||||
|
var $blocking = false;
|
||||||
|
/** @var bool */
|
||||||
|
var $muting = false;
|
||||||
|
/** @var bool */
|
||||||
|
var $muting_notifications = false;
|
||||||
|
/** @var bool */
|
||||||
|
var $requested = false;
|
||||||
|
/** @var bool */
|
||||||
|
var $domain_blocking = false;
|
||||||
|
/** @var bool */
|
||||||
|
var $showing_reblogs = false;
|
||||||
|
/** @var bool */
|
||||||
|
var $endorsed = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $contact Full Contact table record
|
||||||
|
* @return Relationship
|
||||||
|
*/
|
||||||
|
public static function createFromContact(array $contact)
|
||||||
|
{
|
||||||
|
$relationship = new self();
|
||||||
|
|
||||||
|
$relationship->id = $contact['id'];
|
||||||
|
$relationship->following = in_array($contact['rel'], [Contact::SHARING, Contact::FRIEND]);
|
||||||
|
$relationship->followed_by = in_array($contact['rel'], [Contact::FOLLOWER, Contact::FRIEND]);
|
||||||
|
$relationship->blocking = (bool)$contact['blocked'];
|
||||||
|
$relationship->muting = (bool)$contact['readonly'];
|
||||||
|
$relationship->muting_notifications = (bool)$contact['readonly'];
|
||||||
|
$relationship->requested = (bool)$contact['pending'];
|
||||||
|
$relationship->domain_blocking = Network::isUrlBlocked($contact['url']);
|
||||||
|
// Unsupported
|
||||||
|
$relationship->showing_reblogs = true;
|
||||||
|
// Unsupported
|
||||||
|
$relationship->endorsed = false;
|
||||||
|
|
||||||
|
return $relationship;
|
||||||
|
}
|
||||||
|
}
|
|
@ -251,10 +251,6 @@ class Module
|
||||||
|
|
||||||
call_user_func([$this->module_class, 'init'], $this->module_parameters);
|
call_user_func([$this->module_class, 'init'], $this->module_parameters);
|
||||||
|
|
||||||
// "rawContent" is especially meant for technical endpoints.
|
|
||||||
// This endpoint doesn't need any theme initialization or other comparable stuff.
|
|
||||||
call_user_func([$this->module_class, 'rawContent'], $this->module_parameters);
|
|
||||||
|
|
||||||
if ($server['REQUEST_METHOD'] === 'POST') {
|
if ($server['REQUEST_METHOD'] === 'POST') {
|
||||||
Core\Hook::callAll($this->module . '_mod_post', $post);
|
Core\Hook::callAll($this->module . '_mod_post', $post);
|
||||||
call_user_func([$this->module_class, 'post'], $this->module_parameters);
|
call_user_func([$this->module_class, 'post'], $this->module_parameters);
|
||||||
|
@ -262,5 +258,9 @@ class Module
|
||||||
|
|
||||||
Core\Hook::callAll($this->module . '_mod_afterpost', $placeholder);
|
Core\Hook::callAll($this->module . '_mod_afterpost', $placeholder);
|
||||||
call_user_func([$this->module_class, 'afterpost'], $this->module_parameters);
|
call_user_func([$this->module_class, 'afterpost'], $this->module_parameters);
|
||||||
|
|
||||||
|
// "rawContent" is especially meant for technical endpoints.
|
||||||
|
// This endpoint doesn't need any theme initialization or other comparable stuff.
|
||||||
|
call_user_func([$this->module_class, 'rawContent'], $this->module_parameters);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
95
src/BaseModel.php
Normal file
95
src/BaseModel.php
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Friendica;
|
||||||
|
|
||||||
|
use Friendica\Database\Database;
|
||||||
|
use Friendica\Network\HTTPException;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class BaseModel
|
||||||
|
*
|
||||||
|
* The Model classes inheriting from this abstract class are meant to represent a single database record.
|
||||||
|
* The associated table name has to be provided in the child class, and the table is expected to have a unique `id` field.
|
||||||
|
*
|
||||||
|
* @property int id
|
||||||
|
*/
|
||||||
|
abstract class BaseModel
|
||||||
|
{
|
||||||
|
protected static $table_name;
|
||||||
|
|
||||||
|
/** @var Database */
|
||||||
|
protected $dba;
|
||||||
|
/** @var LoggerInterface */
|
||||||
|
protected $logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model record abstraction.
|
||||||
|
* Child classes never have to interact directly with it.
|
||||||
|
* Please use the magic getter instead.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $data = [];
|
||||||
|
|
||||||
|
public function __construct(Database $dba, LoggerInterface $logger)
|
||||||
|
{
|
||||||
|
$this->dba = $dba;
|
||||||
|
$this->logger = $logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Magic getter. This allows to retrieve model fields with the following syntax:
|
||||||
|
* - $model->field (outside of class)
|
||||||
|
* - $this->field (inside of class)
|
||||||
|
*
|
||||||
|
* @param $name
|
||||||
|
* @return mixed
|
||||||
|
* @throws HTTPException\InternalServerErrorException
|
||||||
|
*/
|
||||||
|
public function __get($name)
|
||||||
|
{
|
||||||
|
if (empty($this->data['id'])) {
|
||||||
|
throw new HTTPException\InternalServerErrorException(static::class . ' record uninitialized');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!array_key_exists($name, $this->data)) {
|
||||||
|
throw new HTTPException\InternalServerErrorException('Field ' . $name . ' not found in ' . static::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->data[$name];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches a single model record. The condition array is expected to contain a unique index (primary or otherwise).
|
||||||
|
*
|
||||||
|
* Chainable.
|
||||||
|
*
|
||||||
|
* @param array $condition
|
||||||
|
* @return BaseModel
|
||||||
|
* @throws HTTPException\NotFoundException
|
||||||
|
*/
|
||||||
|
public function fetch(array $condition)
|
||||||
|
{
|
||||||
|
$intro = $this->dba->selectFirst(static::$table_name, [], $condition);
|
||||||
|
|
||||||
|
if (!$intro) {
|
||||||
|
throw new HTTPException\NotFoundException(static::class . ' record not found.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->data = $intro;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the model record from the database.
|
||||||
|
* Prevents further methods from being called by wiping the internal model data.
|
||||||
|
*/
|
||||||
|
public function delete()
|
||||||
|
{
|
||||||
|
if ($this->dba->delete(static::$table_name, ['id' => $this->id])) {
|
||||||
|
$this->data = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -84,7 +84,7 @@ class APContact extends BaseObject
|
||||||
public static function getByURL($url, $update = null)
|
public static function getByURL($url, $update = null)
|
||||||
{
|
{
|
||||||
if (empty($url)) {
|
if (empty($url)) {
|
||||||
return false;
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
$fetched_contact = false;
|
$fetched_contact = false;
|
||||||
|
@ -110,7 +110,7 @@ class APContact extends BaseObject
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!is_null($update)) {
|
if (!is_null($update)) {
|
||||||
return DBA::isResult($apcontact) ? $apcontact : false;
|
return DBA::isResult($apcontact) ? $apcontact : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (DBA::isResult($apcontact)) {
|
if (DBA::isResult($apcontact)) {
|
||||||
|
|
156
src/Model/Introduction.php
Normal file
156
src/Model/Introduction.php
Normal file
|
@ -0,0 +1,156 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Friendica\Model;
|
||||||
|
|
||||||
|
use Friendica\BaseModel;
|
||||||
|
use Friendica\Core\Protocol;
|
||||||
|
use Friendica\Network\HTTPException;
|
||||||
|
use Friendica\Protocol\ActivityPub;
|
||||||
|
use Friendica\Protocol\Diaspora;
|
||||||
|
use Friendica\Util\DateTimeFormat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property int uid
|
||||||
|
* @property int fid
|
||||||
|
* @property int contact-id
|
||||||
|
* @property bool knowyou
|
||||||
|
* @property bool duplex
|
||||||
|
* @property string note
|
||||||
|
* @property string hash
|
||||||
|
* @property string datetime
|
||||||
|
* @property bool blocked
|
||||||
|
* @property bool ignored
|
||||||
|
*
|
||||||
|
* @package Friendica\Model
|
||||||
|
*/
|
||||||
|
final class Introduction extends BaseModel
|
||||||
|
{
|
||||||
|
static $table_name = 'intro';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Confirms a follow request and sends a notic to the remote contact.
|
||||||
|
*
|
||||||
|
* @param bool $duplex Is it a follow back?
|
||||||
|
* @param bool|null $hidden Should this contact be hidden? null = no change
|
||||||
|
* @throws HTTPException\InternalServerErrorException
|
||||||
|
* @throws \ImagickException
|
||||||
|
* @throws HTTPException\NotFoundException
|
||||||
|
*/
|
||||||
|
public function confirm(bool $duplex = false, bool $hidden = null)
|
||||||
|
{
|
||||||
|
$this->logger->info('Confirming follower', ['cid' => $this->{'contact-id'}]);
|
||||||
|
|
||||||
|
$contact = Contact::selectFirst([], ['id' => $this->{'contact-id'}, 'uid' => $this->uid]);
|
||||||
|
|
||||||
|
if (!$contact) {
|
||||||
|
throw new HTTPException\NotFoundException('Contact record not found.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$new_relation = $contact['rel'];
|
||||||
|
$writable = $contact['writable'];
|
||||||
|
|
||||||
|
if (!empty($contact['protocol'])) {
|
||||||
|
$protocol = $contact['protocol'];
|
||||||
|
} else {
|
||||||
|
$protocol = $contact['network'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($protocol == Protocol::ACTIVITYPUB) {
|
||||||
|
ActivityPub\Transmitter::sendContactAccept($contact['url'], $contact['hub-verify'], $contact['uid']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_array($protocol, [Protocol::DIASPORA, Protocol::ACTIVITYPUB])) {
|
||||||
|
if ($duplex) {
|
||||||
|
$new_relation = Contact::FRIEND;
|
||||||
|
} else {
|
||||||
|
$new_relation = Contact::FOLLOWER;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($new_relation != Contact::FOLLOWER) {
|
||||||
|
$writable = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$fields = [
|
||||||
|
'name-date' => DateTimeFormat::utcNow(),
|
||||||
|
'uri-date' => DateTimeFormat::utcNow(),
|
||||||
|
'blocked' => false,
|
||||||
|
'pending' => false,
|
||||||
|
'protocol' => $protocol,
|
||||||
|
'writable' => $writable,
|
||||||
|
'hidden' => $hidden ?? $contact['hidden'],
|
||||||
|
'rel' => $new_relation,
|
||||||
|
];
|
||||||
|
$this->dba->update('contact', $fields, ['id' => $contact['id']]);
|
||||||
|
|
||||||
|
array_merge($contact, $fields);
|
||||||
|
|
||||||
|
if ($new_relation == Contact::FRIEND) {
|
||||||
|
if ($protocol == Protocol::DIASPORA) {
|
||||||
|
$ret = Diaspora::sendShare(User::getById($contact['uid']), $contact);
|
||||||
|
$this->logger->info('share returns', ['return' => $ret]);
|
||||||
|
} elseif ($protocol == Protocol::ACTIVITYPUB) {
|
||||||
|
ActivityPub\Transmitter::sendActivity('Follow', $contact['url'], $contact['uid']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Silently ignores the introduction, hides it from notifications and prevents the remote contact from submitting
|
||||||
|
* additional follow requests.
|
||||||
|
*
|
||||||
|
* Chainable
|
||||||
|
*
|
||||||
|
* @return Introduction
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function ignore()
|
||||||
|
{
|
||||||
|
$this->dba->update('intro', ['ignore' => true], ['id' => $this->id]);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Discards the introduction and sends a rejection message to AP contacts.
|
||||||
|
*
|
||||||
|
* @throws HTTPException\InternalServerErrorException
|
||||||
|
* @throws HTTPException\NotFoundException
|
||||||
|
* @throws \ImagickException
|
||||||
|
*/
|
||||||
|
public function discard()
|
||||||
|
{
|
||||||
|
// If it is a friend suggestion, the contact is not a new friend but an existing friend
|
||||||
|
// that should not be deleted.
|
||||||
|
if (!$this->fid) {
|
||||||
|
// When the contact entry had been created just for that intro, we want to get rid of it now
|
||||||
|
$condition = ['id' => $this->{'contact-id'}, 'uid' => $this->uid,
|
||||||
|
'self' => false, 'pending' => true, 'rel' => [0, Contact::FOLLOWER]];
|
||||||
|
if ($this->dba->exists('contact', $condition)) {
|
||||||
|
Contact::remove($this->{'contact-id'});
|
||||||
|
} else {
|
||||||
|
$this->dba->update('contact', ['pending' => false], ['id' => $this->{'contact-id'}]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$contact = Contact::selectFirst([], ['id' => $this->{'contact-id'}, 'uid' => $this->uid]);
|
||||||
|
|
||||||
|
if (!$contact) {
|
||||||
|
throw new HTTPException\NotFoundException('Contact record not found.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($contact['protocol'])) {
|
||||||
|
$protocol = $contact['protocol'];
|
||||||
|
} else {
|
||||||
|
$protocol = $contact['network'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($protocol == Protocol::ACTIVITYPUB) {
|
||||||
|
ActivityPub\Transmitter::sendContactReject($contact['url'], $contact['hub-verify'], $contact['uid']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->delete();
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,11 +2,13 @@
|
||||||
|
|
||||||
namespace Friendica\Module\Api\Mastodon;
|
namespace Friendica\Module\Api\Mastodon;
|
||||||
|
|
||||||
use Friendica\Api\Mastodon\Account;
|
use Friendica\Api\Mastodon;
|
||||||
use Friendica\App\BaseURL;
|
use Friendica\App\BaseURL;
|
||||||
use Friendica\Core\System;
|
use Friendica\Core\System;
|
||||||
use Friendica\Database\DBA;
|
use Friendica\Database\DBA;
|
||||||
|
use Friendica\Model\APContact;
|
||||||
use Friendica\Model\Contact;
|
use Friendica\Model\Contact;
|
||||||
|
use Friendica\Model\Introduction;
|
||||||
use Friendica\Module\Base\Api;
|
use Friendica\Module\Base\Api;
|
||||||
use Friendica\Network\HTTPException;
|
use Friendica\Network\HTTPException;
|
||||||
|
|
||||||
|
@ -19,7 +21,40 @@ class FollowRequests extends Api
|
||||||
{
|
{
|
||||||
parent::init($parameters);
|
parent::init($parameters);
|
||||||
|
|
||||||
self::login();
|
if (!self::login()) {
|
||||||
|
throw new HTTPException\UnauthorizedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function post(array $parameters = [])
|
||||||
|
{
|
||||||
|
parent::post($parameters);
|
||||||
|
|
||||||
|
/** @var Introduction $Intro */
|
||||||
|
$Intro = self::getClass(Introduction::class);
|
||||||
|
$Intro->fetch(['id' => $parameters['id'], 'uid' => self::$current_user_id]);
|
||||||
|
|
||||||
|
$contactId = $Intro->{'contact-id'};
|
||||||
|
|
||||||
|
$relationship = new Mastodon\Relationship();
|
||||||
|
$relationship->id = $contactId;
|
||||||
|
|
||||||
|
switch ($parameters['action']) {
|
||||||
|
case 'authorize':
|
||||||
|
$Intro->confirm();
|
||||||
|
$relationship = Mastodon\Relationship::createFromContact(Contact::getById($contactId));
|
||||||
|
break;
|
||||||
|
case 'ignore':
|
||||||
|
$Intro->ignore();
|
||||||
|
break;
|
||||||
|
case 'reject':
|
||||||
|
$Intro->discard();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new HTTPException\BadRequestException('Unexpected action parameter, expecting "authorize", "ignore" or "reject"');
|
||||||
|
}
|
||||||
|
|
||||||
|
System::jsonExit($relationship);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -34,26 +69,32 @@ class FollowRequests extends Api
|
||||||
$limit = intval($_GET['limit'] ?? 40);
|
$limit = intval($_GET['limit'] ?? 40);
|
||||||
|
|
||||||
if (isset($since_id) && isset($max_id)) {
|
if (isset($since_id) && isset($max_id)) {
|
||||||
$condition = ['`uid` = ? AND NOT `self` AND `pending` AND `id` > ? AND `id` < ?', self::$current_user_id, $since_id, $max_id];
|
$condition = ['`uid` = ? AND NOT `ignore` AND `id` > ? AND `id` < ?', self::$current_user_id, $since_id, $max_id];
|
||||||
} elseif (isset($since_id)) {
|
} elseif (isset($since_id)) {
|
||||||
$condition = ['`uid` = ? AND NOT `self` AND `pending` AND `id` > ?', self::$current_user_id, $since_id];
|
$condition = ['`uid` = ? AND NOT `ignore` AND `id` > ?', self::$current_user_id, $since_id];
|
||||||
} elseif (isset($max_id)) {
|
} elseif (isset($max_id)) {
|
||||||
$condition = ['`uid` = ? AND NOT `self` AND `pending` AND `id` < ?', self::$current_user_id, $max_id];
|
$condition = ['`uid` = ? AND NOT `ignore` AND `id` < ?', self::$current_user_id, $max_id];
|
||||||
} else {
|
} else {
|
||||||
$condition = ['`uid` = ? AND NOT `self` AND `pending`', self::$current_user_id];
|
$condition = ['`uid` = ? AND NOT `ignore`', self::$current_user_id];
|
||||||
}
|
}
|
||||||
|
|
||||||
$count = DBA::count('contact', $condition);
|
$count = DBA::count('intro', $condition);
|
||||||
|
|
||||||
$contacts = Contact::selectToArray(
|
$intros = DBA::selectToArray(
|
||||||
|
'intro',
|
||||||
[],
|
[],
|
||||||
$condition,
|
$condition,
|
||||||
['order' => ['id' => 'DESC'], 'limit' => $limit]
|
['order' => ['id' => 'DESC'], 'limit' => $limit]
|
||||||
);
|
);
|
||||||
|
|
||||||
$return = [];
|
$return = [];
|
||||||
foreach ($contacts as $contact) {
|
foreach ($intros as $intro) {
|
||||||
$account = Account::createFromContact($contact);
|
$contact = Contact::getById($intro['contact-id']);
|
||||||
|
$apcontact = APContact::getByURL($contact['url'], false);
|
||||||
|
$account = Mastodon\Account::createFromContact($contact, $apcontact);
|
||||||
|
|
||||||
|
// Not ideal, the same "account" can have multiple ids depending on the context
|
||||||
|
$account->id = $intro['id'];
|
||||||
|
|
||||||
$return[] = $account;
|
$return[] = $account;
|
||||||
}
|
}
|
||||||
|
@ -68,9 +109,9 @@ class FollowRequests extends Api
|
||||||
|
|
||||||
$links = [];
|
$links = [];
|
||||||
if ($count > $limit) {
|
if ($count > $limit) {
|
||||||
$links[] = '<' . $BaseURL->get() . '/api/v1/follow_requests?' . http_build_query($base_query + ['max_id' => $contacts[count($contacts) - 1]['id']]) . '>; rel="next"';
|
$links[] = '<' . $BaseURL->get() . '/api/v1/follow_requests?' . http_build_query($base_query + ['max_id' => $intros[count($intros) - 1]['id']]) . '>; rel="next"';
|
||||||
}
|
}
|
||||||
$links[] = '<' . $BaseURL->get() . '/api/v1/follow_requests?' . http_build_query($base_query + ['since_id' => $contacts[0]['id']]) . '>; rel="prev"';
|
$links[] = '<' . $BaseURL->get() . '/api/v1/follow_requests?' . http_build_query($base_query + ['since_id' => $intros[0]['id']]) . '>; rel="prev"';
|
||||||
|
|
||||||
header('Link: ' . implode(', ', $links));
|
header('Link: ' . implode(', ', $links));
|
||||||
|
|
||||||
|
|
|
@ -54,6 +54,7 @@ class Api extends BaseModule
|
||||||
*
|
*
|
||||||
* @brief Login API user
|
* @brief Login API user
|
||||||
*
|
*
|
||||||
|
* @return bool Was a user authenticated?
|
||||||
* @throws HTTPException\ForbiddenException
|
* @throws HTTPException\ForbiddenException
|
||||||
* @throws HTTPException\UnauthorizedException
|
* @throws HTTPException\UnauthorizedException
|
||||||
* @throws HTTPException\InternalServerErrorException
|
* @throws HTTPException\InternalServerErrorException
|
||||||
|
@ -69,6 +70,8 @@ class Api extends BaseModule
|
||||||
api_login(self::getApp());
|
api_login(self::getApp());
|
||||||
|
|
||||||
self::$current_user_id = api_user();
|
self::$current_user_id = api_user();
|
||||||
|
|
||||||
|
return (bool)self::$current_user_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,17 +1,9 @@
|
||||||
<?php
|
<?php
|
||||||
namespace Friendica\Module;
|
namespace Friendica\Module;
|
||||||
|
|
||||||
use Friendica\App;
|
|
||||||
use Friendica\BaseModule;
|
use Friendica\BaseModule;
|
||||||
use Friendica\Core\L10n;
|
use Friendica\Core\L10n;
|
||||||
use Friendica\Core\Logger;
|
use Friendica\Model\Introduction;
|
||||||
use Friendica\Core\Protocol;
|
|
||||||
use Friendica\Database\DBA;
|
|
||||||
use Friendica\Model\Contact;
|
|
||||||
use Friendica\Model\User;
|
|
||||||
use Friendica\Protocol\Diaspora;
|
|
||||||
use Friendica\Protocol\ActivityPub;
|
|
||||||
use Friendica\Util\DateTimeFormat;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process follow request confirmations
|
* Process follow request confirmations
|
||||||
|
@ -30,67 +22,15 @@ class FollowConfirm extends BaseModule
|
||||||
|
|
||||||
$intro_id = intval($_POST['intro_id'] ?? 0);
|
$intro_id = intval($_POST['intro_id'] ?? 0);
|
||||||
$duplex = intval($_POST['duplex'] ?? 0);
|
$duplex = intval($_POST['duplex'] ?? 0);
|
||||||
$cid = intval($_POST['contact_id'] ?? 0);
|
|
||||||
$hidden = intval($_POST['hidden'] ?? 0);
|
$hidden = intval($_POST['hidden'] ?? 0);
|
||||||
|
|
||||||
if (empty($cid)) {
|
/** @var Introduction $Intro */
|
||||||
notice(L10n::t('No given contact.') . EOL);
|
$Intro = self::getClass(Introduction::class);
|
||||||
return;
|
$Intro->fetch(['id' => $intro_id, 'uid' => local_user()]);
|
||||||
}
|
|
||||||
|
|
||||||
Logger::info('Confirming follower', ['cid' => $cid]);
|
$cid = $Intro->{'contact-id'};
|
||||||
|
|
||||||
$contact = DBA::selectFirst('contact', [], ['id' => $cid, 'uid' => $uid]);
|
$Intro->confirm($duplex, $hidden);
|
||||||
if (!DBA::isResult($contact)) {
|
|
||||||
Logger::warning('Contact not found in DB.', ['cid' => $cid]);
|
|
||||||
notice(L10n::t('Contact not found.') . EOL);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$relation = $contact['rel'];
|
|
||||||
$new_relation = $contact['rel'];
|
|
||||||
$writable = $contact['writable'];
|
|
||||||
|
|
||||||
if (!empty($contact['protocol'])) {
|
|
||||||
$protocol = $contact['protocol'];
|
|
||||||
} else {
|
|
||||||
$protocol = $contact['network'];
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($protocol == Protocol::ACTIVITYPUB) {
|
|
||||||
ActivityPub\Transmitter::sendContactAccept($contact['url'], $contact['hub-verify'], $uid);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (in_array($protocol, [Protocol::DIASPORA, Protocol::ACTIVITYPUB])) {
|
|
||||||
if ($duplex) {
|
|
||||||
$new_relation = Contact::FRIEND;
|
|
||||||
} else {
|
|
||||||
$new_relation = Contact::FOLLOWER;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($new_relation != Contact::FOLLOWER) {
|
|
||||||
$writable = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$fields = ['name-date' => DateTimeFormat::utcNow(),
|
|
||||||
'uri-date' => DateTimeFormat::utcNow(),
|
|
||||||
'blocked' => false, 'pending' => false, 'protocol' => $protocol,
|
|
||||||
'writable' => $writable, 'hidden' => $hidden, 'rel' => $new_relation];
|
|
||||||
DBA::update('contact', $fields, ['id' => $cid]);
|
|
||||||
|
|
||||||
if ($new_relation == Contact::FRIEND) {
|
|
||||||
if ($protocol == Protocol::DIASPORA) {
|
|
||||||
$user = User::getById($uid);
|
|
||||||
$contact = Contact::getById($cid);
|
|
||||||
$ret = Diaspora::sendShare($user, $contact);
|
|
||||||
Logger::info('share returns', ['return' => $ret]);
|
|
||||||
} elseif ($protocol == Protocol::ACTIVITYPUB) {
|
|
||||||
ActivityPub\Transmitter::sendActivity('Follow', $contact['url'], $uid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DBA::delete('intro', ['id' => $intro_id]);
|
|
||||||
|
|
||||||
$a->internalRedirect('contact/' . intval($cid));
|
$a->internalRedirect('contact/' . intval($cid));
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ return [
|
||||||
'/api' => [
|
'/api' => [
|
||||||
'/v1' => [
|
'/v1' => [
|
||||||
'/follow_requests' => [Module\Api\Mastodon\FollowRequests::class, [R::GET ]],
|
'/follow_requests' => [Module\Api\Mastodon\FollowRequests::class, [R::GET ]],
|
||||||
|
'/follow_requests/{id:\d+}/{action}' => [Module\Api\Mastodon\FollowRequests::class, [ R::POST]],
|
||||||
'/instance' => [Module\Api\Mastodon\Instance::class, [R::GET]],
|
'/instance' => [Module\Api\Mastodon\Instance::class, [R::GET]],
|
||||||
'/instance/peers' => [Module\Api\Mastodon\Instance\Peers::class, [R::GET]],
|
'/instance/peers' => [Module\Api\Mastodon\Instance\Peers::class, [R::GET]],
|
||||||
],
|
],
|
||||||
|
|
Loading…
Reference in a new issue