Called when unfollowing a remote contact on a non-native network (like Twitter)
Hook data:
-- **contact** (input): the remote contact (uid = local unfollowing user id) array.
+- **contact** (input): the target public contact (uid = 0) array.
+- **uid** (input): the id of the source local user.
- **result** (output): wether the unfollowing is successful or not.
### revoke_follow
$owner = User::getOwnerDataById($uid);
if (!$owner) {
- (new \Friendica\Module\Security\Logout())->init();
- // NOTREACHED
+ throw new \Friendica\Network\HTTPException\NotFoundException();
}
$condition = ["`uid` = ? AND (`rel` = ? OR `rel` = ?) AND (`nurl` = ? OR `alias` = ? OR `alias` = ?)",
$return_path = $base_return_path . '/' . $contact['id'];
try {
- $result = Contact::terminateFriendship($owner, $contact);
-
- if ($result === false) {
- $notice_message = DI::l10n()->t('Unable to unfollow this contact, please retry in a few minutes or contact your administrator.');
- } else {
- $notice_message = DI::l10n()->t('Contact was successfully unfollowed');
- }
+ Contact::unfollow($contact);
+ $notice_message = DI::l10n()->t('Contact was successfully unfollowed');
} catch (Exception $e) {
- DI::logger()->error($e->getMessage(), ['owner' => $owner, 'contact' => $contact]);
+ DI::logger()->error($e->getMessage(), ['contact' => $contact]);
$notice_message = DI::l10n()->t('Unable to unfollow this contact, please contact your administrator');
}
throw new RuntimeException('Contact not found');
}
- $user = UserModel::getById($contact['uid']);
+ if (empty($contact['uid'])) {
+ throw new RuntimeException('Contact must be user-specific (uid != 0)');
+ }
try {
- $result = ContactModel::terminateFriendship($user, $contact);
- if ($result === false) {
- throw new RuntimeException('Unable to unfollow this contact, please retry in a few minutes or check the logs.');
- }
+ ContactModel::unfollow($contact);
$this->out('Contact was successfully unfollowed');
return true;
} catch (\Exception $e) {
- DI::logger()->error($e->getMessage(), ['owner' => $user, 'contact' => $contact]);
+ DI::logger()->error($e->getMessage(), ['contact' => $contact]);
throw new RuntimeException('Unable to unfollow this contact, please check the log');
}
}
}
/**
- * Sends an unfriend message. Does not remove the contact
+ * Sends an unfollow message. Does not remove the contact
*
- * @param array $user User unfriending
- * @param array $contact Contact unfriended
+ * @param array $contact Target public contact (uid = 0) array
+ * @param array $user Source local user array
* @return bool|null true if successful, false if not, null if no remote action was performed
* @throws HTTPException\InternalServerErrorException
* @throws \ImagickException
*/
- public static function terminateFriendship(array $user, array $contact): ?bool
+ public static function unfollow(array $contact, array $user): ?bool
{
if (empty($contact['network'])) {
throw new \InvalidArgumentException('Missing network key in contact array');
// Catch-all hook for connector addons
$hook_data = [
'contact' => $contact,
- 'result' => null
+ 'uid' => $user['uid'],
+ 'result' => null,
];
Hook::callAll('unfollow', $hook_data);
}
/**
- * Sends an unfriend message. Removes the contact for two-way unfriending or sharing only protocols (feed an mail)
+ * Unfollow the remote contact
*
- * @param array $user User unfriending
- * @param array $contact Contact (uid != 0) unfriended
- * @param boolean $two_way Revoke eventual inbound follow as well
- * @return bool|null true if successful, false if not, null if no remote action was performed
+ * @param array $contact Target user-specific contact (uid != 0) array
* @throws HTTPException\InternalServerErrorException
* @throws \ImagickException
*/
- public static function terminateFriendship(array $user, array $contact): ?bool
+ public static function unfollow(array $contact): void
{
- $result = Protocol::terminateFriendship($user, $contact);
+ if (empty($contact['network'])) {
+ throw new \InvalidArgumentException('Empty network in contact array');
+ }
- if ($contact['rel'] == Contact::SHARING || in_array($contact['network'], [Protocol::FEED, Protocol::MAIL])) {
- self::remove($contact['id']);
- } else {
- self::update(['rel' => Contact::FOLLOWER], ['id' => $contact['id']]);
+ if (empty($contact['uid'])) {
+ throw new \InvalidArgumentException('Unexpected public contact record');
}
- return $result;
+ if (in_array($contact['rel'], [self::SHARING, self::FRIEND])) {
+ $cdata = Contact::getPublicAndUserContactID($contact['id'], $contact['uid']);
+ Worker::add(PRIORITY_HIGH, 'Contact\Unfollow', $cdata['public'], $contact['uid']);
+ }
+
+ self::removeSharer($contact);
}
/**
return $result;
}
+ /**
+ * Completely severs a relationship with a contact
+ *
+ * @param array $contact User-specific contact (uid != 0) array
+ * @throws HTTPException\InternalServerErrorException
+ * @throws \ImagickException
+ */
+ public static function terminateFriendship(array $contact)
+ {
+ if (empty($contact['network'])) {
+ throw new \InvalidArgumentException('Empty network in contact array');
+ }
+
+ if (empty($contact['uid'])) {
+ throw new \InvalidArgumentException('Unexpected public contact record');
+ }
+
+ $cdata = Contact::getPublicAndUserContactID($contact['id'], $contact['uid']);
+
+ if (in_array($contact['rel'], [self::SHARING, self::FRIEND])) {
+ Worker::add(PRIORITY_HIGH, 'Contact\Unfollow', $cdata['public'], $contact['uid']);
+ }
+
+ if (in_array($contact['rel'], [self::FOLLOWER, self::FRIEND])) {
+ Worker::add(PRIORITY_HIGH, 'Contact\RevokeFollow', $cdata['public'], $contact['uid']);
+ }
+
+ self::remove($contact['id']);
+ }
+
/**
* Marks a contact for archival after a communication issue delay
return $result;
}
- /**
- * Unfollow a contact
- *
- * @param int $cid Public contact id
- * @param int $uid User ID
- *
- * @return bool "true" if unfollowing had been successful
- */
- public static function unfollow(int $cid, int $uid)
- {
- $cdata = self::getPublicAndUserContactID($cid, $uid);
- if (empty($cdata['user'])) {
- return false;
- }
-
- $contact = self::getById($cdata['user']);
-
- self::removeSharer([], $contact);
-
- return true;
- }
-
/**
* @param array $importer Owner (local user) data
* @param array $contact Existing owner-specific contact data we want to expand the relationship with. Optional.
}
}
- public static function removeSharer($importer, $contact)
+ /**
+ * Update the local relationship when a local user unfollow a contact.
+ * Removes the contact for sharing-only protocols (feed and mail).
+ *
+ * @param array $contact User-specific contact (uid != 0) array
+ * @throws HTTPException\InternalServerErrorException
+ */
+ public static function removeSharer(array $contact)
{
- if (($contact['rel'] == self::FRIEND) || ($contact['rel'] == self::FOLLOWER)) {
- self::update(['rel' => self::FOLLOWER], ['id' => $contact['id']]);
- } else {
+ if ($contact['rel'] == self::SHARING || in_array($contact['network'], [Protocol::FEED, Protocol::MAIL])) {
self::remove($contact['id']);
+ } else {
+ self::update(['rel' => self::FOLLOWER], ['id' => $contact['id']]);
}
}
Contact\User::setBlocked($cdata['user'], $uid, true);
// Mastodon-expected behavior: relationship is severed on block
- Contact::terminateFriendship($owner, $contact);
- Contact::revokeFollow($contact);
+ Contact::terminateFriendship($contact);
System::jsonExit(DI::mstdnRelationship()->createFromContactId($this->parameters['id'], $uid)->toArray());
}
DI::mstdnError()->UnprocessableEntity();
}
- Contact::unfollow($this->parameters['id'], $uid);
+ $cdata = Contact::getPublicAndUserContactID($this->parameters['id'], $uid);
+ if (empty($cdata['user'])) {
+ DI::mstdnError()->RecordNotFound();
+ }
+
+ $contact = Contact::getById($cdata['user']);
+
+ Contact::unfollow($contact);
System::jsonExit(DI::mstdnRelationship()->createFromContactId($this->parameters['id'], $uid)->toArray());
}
namespace Friendica\Module\Api\Twitter\Friendships;
use Exception;
+use Friendica\App;
+use Friendica\Core\L10n;
use Friendica\Core\Logger;
-use Friendica\DI;
+use Friendica\Factory\Api\Twitter\User as TwitterUser;
use Friendica\Model\Contact;
use Friendica\Model\User;
+use Friendica\Module\Api\ApiResponse;
use Friendica\Module\Api\Twitter\ContactEndpoint;
use Friendica\Module\BaseApi;
use Friendica\Network\HTTPException;
+use Friendica\Util\Profiler;
+use Psr\Log\LoggerInterface;
/**
* Unfollow Contact
*/
class Destroy extends ContactEndpoint
{
+ /** @var TwitterUser */
+ private $twitterUser;
+
+ public function __construct(App $app, L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, ApiResponse $response, TwitterUser $twitterUser, array $server, array $parameters = [])
+ {
+ parent::__construct($app, $l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
+
+ $this->twitterUser = $twitterUser;
+ }
+
protected function post(array $request = [])
{
BaseApi::checkAllowedScope(BaseApi::SCOPE_WRITE);
$user = $this->twitterUser->createFromContactId($contact_id, $uid, true)->toArray();
try {
- $result = Contact::terminateFriendship($owner, $contact);
-
- if ($result === null) {
- Logger::notice(BaseApi::LOG_PREFIX . 'Not supported for {network}', ['module' => 'api', 'action' => 'friendships_destroy', 'network' => $contact['network']]);
- throw new HTTPException\ExpectationFailedException('Unfollowing is currently not supported by this contact\'s network.');
- }
-
- if ($result === false) {
- throw new HTTPException\ServiceUnavailableException('Unable to unfollow this contact, please retry in a few minutes or contact your administrator.');
- }
+ Contact::unfollow($contact);
} catch (Exception $e) {
- Logger::error(BaseApi::LOG_PREFIX . $e->getMessage(), ['owner' => $owner, 'contact' => $contact]);
+ Logger::error(BaseApi::LOG_PREFIX . $e->getMessage(), ['contact' => $contact]);
throw new HTTPException\InternalServerErrorException('Unable to unfollow this contact, please contact your administrator');
}
}
if ($activity->match($item["verb"], Activity::UNFRIEND)) {
Logger::notice("Lost sharer");
- Contact::removeSharer($importer, $contact, $item);
+ Contact::removeSharer($contact);
return false;
}
} else {
--- /dev/null
+<?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\Worker\Contact;
+
+use Friendica\Core\Protocol;
+use Friendica\Core\Worker;
+use Friendica\Model\Contact;
+use Friendica\Model\User;
+
+class Unfollow
+{
+ /**
+ * Issue asynchronous unfollow message to remote servers.
+ * The local relationship has already been updated, so we can't use the user-specific contact.
+ *
+ * @param int $cid Target public contact (uid = 0) id
+ * @param int $uid Source local user id
+ * @throws \Friendica\Network\HTTPException\InternalServerErrorException
+ * @throws \ImagickException
+ */
+ public static function execute(int $cid, int $uid)
+ {
+ $contact = Contact::getById($cid);
+ if (empty($contact)) {
+ return;
+ }
+
+ $owner = User::getOwnerDataById($uid, false);
+ if (empty($owner)) {
+ return;
+ }
+
+ $result = Protocol::unfollow($contact, $owner);
+ if ($result === false) {
+ Worker::defer();
+ }
+ }
+}
}
while($contact = DBA::fetch($contacts_stmt)) {
- Protocol::terminateFriendship($owner, $contact, true);
+ Contact::terminateFriendship($contact);
}
DBA::close($contacts_stmt);