diff --git a/composer.lock b/composer.lock index 0034780263..f9b96b1d9d 100644 --- a/composer.lock +++ b/composer.lock @@ -671,11 +671,11 @@ }, { "name": "friendica/json-ld", - "version": "1.1.1", + "version": "1.1.2", "source": { "type": "git", "url": "https://git.friendi.ca/friendica/php-json-ld", - "reference": "ca3916d10d2ad9073b3b1eae383978dbe828e1e1" + "reference": "5f6ea87b261d346e57f03457ae906e6835f0205f" }, "require": { "ext-json": "*", @@ -711,7 +711,7 @@ "Semantic Web", "jsonld" ], - "time": "2018-10-08T20:41:00+00:00" + "time": "2023-02-20T21:56:16+00:00" }, { "name": "fxp/composer-asset-plugin", diff --git a/database.sql b/database.sql index fa11843e43..ac3eaaf1df 100644 --- a/database.sql +++ b/database.sql @@ -340,22 +340,6 @@ CREATE TABLE IF NOT EXISTS `account-user` ( FOREIGN KEY (`uid`) REFERENCES `user` (`uid`) ON UPDATE RESTRICT ON DELETE CASCADE ) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Remote and local accounts'; --- --- TABLE addon --- -CREATE TABLE IF NOT EXISTS `addon` ( - `id` int unsigned NOT NULL auto_increment COMMENT '', - `name` varchar(50) NOT NULL DEFAULT '' COMMENT 'addon base (file)name', - `version` varchar(50) NOT NULL DEFAULT '' COMMENT 'currently unused', - `installed` boolean NOT NULL DEFAULT '0' COMMENT 'currently always 1', - `hidden` boolean NOT NULL DEFAULT '0' COMMENT 'currently unused', - `timestamp` int unsigned NOT NULL DEFAULT 0 COMMENT 'file timestamp to check for reloads', - `plugin_admin` boolean NOT NULL DEFAULT '0' COMMENT '1 = has admin config, 0 = has no admin config', - PRIMARY KEY(`id`), - INDEX `installed_name` (`installed`,`name`), - UNIQUE INDEX `name` (`name`) -) DEFAULT COLLATE utf8mb4_general_ci COMMENT='registered addons'; - -- -- TABLE apcontact -- diff --git a/doc/database.md b/doc/database.md index edfb7b8226..461c1c2d9b 100644 --- a/doc/database.md +++ b/doc/database.md @@ -10,7 +10,6 @@ Database Tables | [2fa_trusted_browser](help/database/db_2fa_trusted_browser) | Two-factor authentication trusted browsers | | [account-suggestion](help/database/db_account-suggestion) | Account suggestion | | [account-user](help/database/db_account-user) | Remote and local accounts | -| [addon](help/database/db_addon) | registered addons | | [apcontact](help/database/db_apcontact) | ActivityPub compatible contacts - used in the ActivityPub implementation | | [application](help/database/db_application) | OAuth application | | [application-marker](help/database/db_application-marker) | Timeline marker | diff --git a/doc/database/db_addon.md b/doc/database/db_addon.md deleted file mode 100644 index 7233b80095..0000000000 --- a/doc/database/db_addon.md +++ /dev/null @@ -1,29 +0,0 @@ -Table addon -=========== - -registered addons - -Fields ------- - -| Field | Description | Type | Null | Key | Default | Extra | -| ------------ | --------------------------------------------- | ------------ | ---- | --- | ------- | -------------- | -| id | | int unsigned | NO | PRI | NULL | auto_increment | -| name | addon base (file)name | varchar(50) | NO | | | | -| version | currently unused | varchar(50) | NO | | | | -| installed | currently always 1 | boolean | NO | | 0 | | -| hidden | currently unused | boolean | NO | | 0 | | -| timestamp | file timestamp to check for reloads | int unsigned | NO | | 0 | | -| plugin_admin | 1 = has admin config, 0 = has no admin config | boolean | NO | | 0 | | - -Indexes ------------- - -| Name | Fields | -| -------------- | --------------- | -| PRIMARY | id | -| installed_name | installed, name | -| name | UNIQUE, name | - - -Return to [database documentation](help/database) diff --git a/src/Console/ServerBlock.php b/src/Console/ServerBlock.php index ed9ff5dbde..4d04dcff81 100644 --- a/src/Console/ServerBlock.php +++ b/src/Console/ServerBlock.php @@ -107,6 +107,11 @@ HELP; { $filename = $this->getArgument(1); + if (empty($filename)) { + $this->out('A file name is required, e.g. ./bin/console serverblock export backup.csv'); + return 1; + } + $this->blocklist->exportToFile($filename); // Success diff --git a/src/Content/Conversation.php b/src/Content/Conversation.php index 8107759534..249190b1ad 100644 --- a/src/Content/Conversation.php +++ b/src/Content/Conversation.php @@ -1146,8 +1146,8 @@ class Conversation $emojis[$row['thr-parent-id']][$index]['emoji'] = $emoji; $emojis[$row['thr-parent-id']][$index]['verb'] = $row['verb']; - $emojis[$row['thr-parent-id']][$index]['total'] = $emojis[$row['thr-parent-id']][$emoji]['total'] ?? 0 + $row['total']; - $emojis[$row['thr-parent-id']][$index]['title'] = array_unique(array_merge($emojis[$row['thr-parent-id']][$emoji]['title'] ?? [], explode($separator, $row['title']))); + $emojis[$row['thr-parent-id']][$index]['total'] = ($emojis[$row['thr-parent-id']][$index]['total'] ?? 0) + $row['total']; + $emojis[$row['thr-parent-id']][$index]['title'] = array_unique(array_merge($emojis[$row['thr-parent-id']][$index]['title'] ?? [], explode($separator, $row['title']))); } DBA::close($rows); diff --git a/src/Factory/Api/Mastodon/Status.php b/src/Factory/Api/Mastodon/Status.php index 9f6d3bcde9..788841a9bc 100644 --- a/src/Factory/Api/Mastodon/Status.php +++ b/src/Factory/Api/Mastodon/Status.php @@ -59,12 +59,18 @@ class Status extends BaseFactory /** @var ContentItem */ private $contentItem; - public function __construct(LoggerInterface $logger, Database $dba, - Account $mstdnAccountFactory, Mention $mstdnMentionFactory, - Tag $mstdnTagFactory, Card $mstdnCardFactory, - Attachment $mstdnAttachementFactory, Error $mstdnErrorFactory, - Poll $mstdnPollFactory, ContentItem $contentItem) - { + public function __construct( + LoggerInterface $logger, + Database $dba, + Account $mstdnAccountFactory, + Mention $mstdnMentionFactory, + Tag $mstdnTagFactory, + Card $mstdnCardFactory, + Attachment $mstdnAttachementFactory, + Error $mstdnErrorFactory, + Poll $mstdnPollFactory, + ContentItem $contentItem + ) { parent::__construct($logger); $this->dba = $dba; $this->mstdnAccountFactory = $mstdnAccountFactory; @@ -144,10 +150,18 @@ class Status extends BaseFactory 'deleted' => false ]); + $count_dislike = Post::countPosts([ + 'thr-parent-id' => $uriId, + 'gravity' => Item::GRAVITY_ACTIVITY, + 'vid' => Verb::getID(Activity::DISLIKE), + 'deleted' => false + ]); + $counts = new \Friendica\Object\Api\Mastodon\Status\Counts( Post::countPosts(['thr-parent-id' => $uriId, 'gravity' => Item::GRAVITY_COMMENT, 'deleted' => false], []), $count_announce, - $count_like + $count_like, + $count_dislike ); $origin_like = ($count_like == 0) ? false : Post::exists([ @@ -323,7 +337,7 @@ class Status extends BaseFactory $replies = $this->dba->count('mail', ['thr-parent-id' => $item['uri-id'], 'reply' => true]); - $counts = new \Friendica\Object\Api\Mastodon\Status\Counts($replies, 0, 0); + $counts = new \Friendica\Object\Api\Mastodon\Status\Counts($replies, 0, 0, 0); $userAttributes = new \Friendica\Object\Api\Mastodon\Status\UserAttributes(false, false, false, false, false); diff --git a/src/Model/Item.php b/src/Model/Item.php index feb7ae6be6..a62d3c2e6e 100644 --- a/src/Model/Item.php +++ b/src/Model/Item.php @@ -1212,7 +1212,7 @@ class Item } // The content of activities normally doesn't matter - except for likes from Misskey - if (!in_array($item['verb'], self::ACTIVITIES) || in_array($item['verb'], [Activity::LIKE, Activity::DISLIKE]) && !empty($item['body']) && ($item['body'] != $item['verb'])) { + if (!in_array($item['verb'], self::ACTIVITIES) || in_array($item['verb'], [Activity::LIKE, Activity::DISLIKE]) && !empty($item['body']) && (mb_strlen($item['body']) == 1)) { Post\Content::insert($item['uri-id'], $item); } diff --git a/src/Module/Api/Friendica/Statuses/Dislike.php b/src/Module/Api/Friendica/Statuses/Dislike.php new file mode 100644 index 0000000000..e9690d4858 --- /dev/null +++ b/src/Module/Api/Friendica/Statuses/Dislike.php @@ -0,0 +1,54 @@ +. + * + */ + +namespace Friendica\Module\Api\Friendica\Statuses; + +use Friendica\Core\System; +use Friendica\Database\DBA; +use Friendica\DI; +use Friendica\Model\Item; +use Friendica\Model\Post; +use Friendica\Module\BaseApi; + +/** + * @see https://docs.joinmastodon.org/methods/statuses/ + */ +class Dislike extends BaseApi +{ + protected function post(array $request = []) + { + self::checkAllowedScope(self::SCOPE_WRITE); + $uid = self::getCurrentUserID(); + + if (empty($this->parameters['id'])) { + DI::mstdnError()->UnprocessableEntity(); + } + + $item = Post::selectFirstForUser($uid, ['id'], ['uri-id' => $this->parameters['id'], 'uid' => [$uid, 0]]); + if (!DBA::isResult($item)) { + DI::mstdnError()->RecordNotFound(); + } + + Item::performActivity($item['id'], 'dislike', $uid); + + System::jsonExit(DI::mstdnStatus()->createFromUriId($this->parameters['id'], $uid, self::appSupportsQuotes())->toArray()); + } +} diff --git a/src/Module/Api/Friendica/Statuses/DislikedBy.php b/src/Module/Api/Friendica/Statuses/DislikedBy.php new file mode 100644 index 0000000000..81860bc40c --- /dev/null +++ b/src/Module/Api/Friendica/Statuses/DislikedBy.php @@ -0,0 +1,62 @@ +. + * + */ + +namespace Friendica\Module\Api\Friendica\Statuses; + +use Friendica\Core\System; +use Friendica\DI; +use Friendica\Model\Item; +use Friendica\Model\Post; +use Friendica\Module\BaseApi; +use Friendica\Protocol\Activity; + +/** + * @see https://docs.joinmastodon.org/methods/statuses/ + */ +class DislikedBy extends BaseApi +{ + /** + * @throws \Friendica\Network\HTTPException\InternalServerErrorException + */ + protected function rawContent(array $request = []) + { + $uid = self::getCurrentUserID(); + + if (empty($this->parameters['id'])) { + DI::mstdnError()->UnprocessableEntity(); + } + + $id = $this->parameters['id']; + if (!Post::exists(['uri-id' => $id, 'uid' => [0, $uid]])) { + DI::mstdnError()->RecordNotFound(); + } + + $activities = Post::selectPosts(['author-id'], ['thr-parent-id' => $id, 'gravity' => Item::GRAVITY_ACTIVITY, 'verb' => Activity::DISLIKE, 'deleted' => false]); + + $accounts = []; + + while ($activity = Post::fetch($activities)) { + $accounts[] = DI::mstdnAccount()->createFromContactId($activity['author-id'], $uid); + } + + System::jsonExit($accounts); + } +} diff --git a/src/Module/Api/Friendica/Statuses/Undislike.php b/src/Module/Api/Friendica/Statuses/Undislike.php new file mode 100644 index 0000000000..933d7a1ed5 --- /dev/null +++ b/src/Module/Api/Friendica/Statuses/Undislike.php @@ -0,0 +1,54 @@ +. + * + */ + +namespace Friendica\Module\Api\Friendica\Statuses; + +use Friendica\Core\System; +use Friendica\Database\DBA; +use Friendica\DI; +use Friendica\Model\Item; +use Friendica\Model\Post; +use Friendica\Module\BaseApi; + +/** + * @see https://docs.joinmastodon.org/methods/statuses/ + */ +class Undislike extends BaseApi +{ + protected function post(array $request = []) + { + self::checkAllowedScope(self::SCOPE_WRITE); + $uid = self::getCurrentUserID(); + + if (empty($this->parameters['id'])) { + DI::mstdnError()->UnprocessableEntity(); + } + + $item = Post::selectFirstForUser($uid, ['id'], ['uri-id' => $this->parameters['id'], 'uid' => [$uid, 0]]); + if (!DBA::isResult($item)) { + DI::mstdnError()->RecordNotFound(); + } + + Item::performActivity($item['id'], 'undislike', $uid); + + System::jsonExit(DI::mstdnStatus()->createFromUriId($this->parameters['id'], $uid, self::appSupportsQuotes())->toArray()); + } +} diff --git a/src/Module/Api/Mastodon/InstanceV2.php b/src/Module/Api/Mastodon/InstanceV2.php new file mode 100644 index 0000000000..2038081d87 --- /dev/null +++ b/src/Module/Api/Mastodon/InstanceV2.php @@ -0,0 +1,168 @@ +. + * + */ + +namespace Friendica\Module\Api\Mastodon; + +use Exception; +use Friendica\App; +use Friendica\Core\Config\Capability\IManageConfigValues; +use Friendica\Core\L10n; +use Friendica\Core\System; +use Friendica\Database\Database; +use Friendica\DI; +use Friendica\Model\User; +use Friendica\Module\Api\ApiResponse; +use Friendica\Module\BaseApi; +use Friendica\Module\Register; +use Friendica\Object\Api\Mastodon\InstanceV2 as InstanceEntity; +use Friendica\Util\Images; +use Friendica\Util\Profiler; +use Psr\Log\LoggerInterface; + +/** + * @see https://docs.joinmastodon.org/methods/instance/ + */ +class InstanceV2 extends BaseApi +{ + /** @var Database */ + private $database; + + /** @var IManageConfigValues */ + private $config; + + public function __construct( + App $app, + L10n $l10n, + App\BaseURL $baseUrl, + App\Arguments $args, + LoggerInterface $logger, + Profiler $profiler, + ApiResponse $response, + Database $database, + IManageConfigValues $config, + array $server, + array $parameters = [] + ) { + parent::__construct($app, $l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters); + + $this->database = $database; + $this->config = $config; + } + + /** + * @param array $request + * @throws \Friendica\Network\HTTPException\InternalServerErrorException + * @throws \Friendica\Network\HTTPException\NotFoundException + * @throws \ImagickException + * @throws Exception + */ + protected function rawContent(array $request = []) + { + $domain = $this->baseUrl->getHost(); + $title = $this->config->get('config', 'sitename'); + $version = '2.8.0 (compatible; Friendica ' . App::VERSION . ')'; + $description = $this->config->get('config', 'info'); + $usage = $this->buildUsageInfo(); + $thumbnail = new InstanceEntity\Thumbnail($this->baseUrl->withPath('images/friendica-banner.jpg')); + $languages = [$this->config->get('system', 'language')]; + $configuration = $this->buildConfigurationInfo(); + $registration = $this->buildRegistrationsInfo(); + $contact = $this->buildContactInfo(); + $friendica_extensions = $this->buildFriendicaExtensionInfo(); + $rules = System::getRules(); + System::jsonExit(new InstanceEntity( + $domain, + $title, + $version, + $description, + $usage, + $thumbnail, + $languages, + $configuration, + $registration, + $contact, + $friendica_extensions, + $rules + )); + } + + private function buildConfigurationInfo(): InstanceEntity\Configuration + { + $statuses_config = new InstanceEntity\StatusesConfig((int)$this->config->get( + 'config', + 'api_import_size', + $this->config->get('config', 'max_import_size') + )); + + return new InstanceEntity\Configuration( + $statuses_config, + new InstanceEntity\MediaAttachmentsConfig(Images::supportedTypes()), + $this->config->get('system', 'maximagesize') + ); + } + + private function buildContactInfo(): InstanceEntity\Contact + { + $email = implode(',', User::getAdminEmailList()); + $administrator = User::getFirstAdmin(); + $account = null; + + if ($administrator) { + $adminContact = $this->database->selectFirst( + 'contact', + ['uri-id'], + ['nick' => $administrator['nickname'], 'self' => true] + ); + $account = DI::mstdnAccount()->createFromUriId($adminContact['uri-id']); + } + + return new InstanceEntity\Contact($email, $account); + } + + private function buildFriendicaExtensionInfo(): InstanceEntity\FriendicaExtensions + { + return new InstanceEntity\FriendicaExtensions( + App::VERSION, + App::CODENAME, + $this->config->get('system', 'build') + ); + } + + private function buildRegistrationsInfo(): InstanceEntity\Registrations + { + $register_policy = intval($this->config->get('config', 'register_policy')); + $enabled = ($register_policy != Register::CLOSED); + $approval_required = ($register_policy == Register::APPROVE); + + return new InstanceEntity\Registrations($enabled, $approval_required); + } + + private function buildUsageInfo(): InstanceEntity\Usage + { + if (!empty($this->config->get('system', 'nodeinfo'))) { + $active_monthly = intval(DI::keyValue()->get('nodeinfo_active_users_monthly')); + } else { + $active_monthly = 0; + } + + return new InstanceEntity\Usage(new InstanceEntity\UserStats($active_monthly)); + } +} diff --git a/src/Module/Api/Mastodon/Notifications.php b/src/Module/Api/Mastodon/Notifications.php index 3cf60b870c..31fe7a57fb 100644 --- a/src/Module/Api/Mastodon/Notifications.php +++ b/src/Module/Api/Mastodon/Notifications.php @@ -63,7 +63,8 @@ class Notifications extends BaseApi 'exclude_types' => [], // Array of types to exclude (follow, favourite, reblog, mention, poll, follow_request) 'account_id' => 0, // Return only notifications received from this account 'with_muted' => false, // Pleroma extension: return activities by muted (not by blocked!) users. - 'include_all' => false // Include dismissed and undismissed + 'include_all' => false, // Include dismissed and undismissed + 'summary' => false, ], $request); $params = ['order' => ['id' => true]]; @@ -87,8 +88,8 @@ class Notifications extends BaseApi $condition = DBA::mergeConditions( $condition, ["(`vid` != ? OR `type` != ? OR NOT `actor-id` IN (SELECT `id` FROM `contact` WHERE `pending`))", - Verb::getID(Activity::FOLLOW), - Post\UserNotification::TYPE_NONE] + Verb::getID(Activity::FOLLOW), + Post\UserNotification::TYPE_NONE] ); } @@ -96,8 +97,8 @@ class Notifications extends BaseApi $condition = DBA::mergeConditions( $condition, ["(`vid` != ? OR `type` != ? OR NOT `actor-id` IN (SELECT `id` FROM `contact` WHERE NOT `pending`))", - Verb::getID(Activity::FOLLOW), - Post\UserNotification::TYPE_NONE] + Verb::getID(Activity::FOLLOW), + Post\UserNotification::TYPE_NONE] ); } @@ -130,26 +131,31 @@ class Notifications extends BaseApi Verb::getID(Activity::POST), Post\UserNotification::TYPE_SHARED]); } - $mstdnNotifications = []; + if ($request['summary']) { + $count = DI::notification()->countForUser($uid, $condition); + System::jsonExit(['count' => $count]); + } else { + $mstdnNotifications = []; - $Notifications = DI::notification()->selectByBoundaries( - $condition, - $params, - $request['min_id'] ?: $request['since_id'], - $request['max_id'], - $request['limit'] - ); + $Notifications = DI::notification()->selectByBoundaries( + $condition, + $params, + $request['min_id'] ?: $request['since_id'], + $request['max_id'], + $request['limit'] + ); - foreach ($Notifications as $Notification) { - try { - $mstdnNotifications[] = DI::mstdnNotification()->createFromNotification($Notification, self::appSupportsQuotes()); - self::setBoundaries($Notification->id); - } catch (\Exception $e) { - // Skip this notification + foreach ($Notifications as $Notification) { + try { + $mstdnNotifications[] = DI::mstdnNotification()->createFromNotification($Notification, self::appSupportsQuotes()); + self::setBoundaries($Notification->id); + } catch (\Exception $e) { + // Skip this notification + } } - } - self::setLinkHeader(); - System::jsonExit($mstdnNotifications); + self::setLinkHeader(); + System::jsonExit($mstdnNotifications); + } } } diff --git a/src/Module/Api/Mastodon/Statuses/FavouritedBy.php b/src/Module/Api/Mastodon/Statuses/FavouritedBy.php index 3bf165798b..8f9fe02f17 100644 --- a/src/Module/Api/Mastodon/Statuses/FavouritedBy.php +++ b/src/Module/Api/Mastodon/Statuses/FavouritedBy.php @@ -49,7 +49,7 @@ class FavouritedBy extends BaseApi DI::mstdnError()->RecordNotFound(); } - $activities = Post::selectPosts(['author-id'], ['thr-parent-id' => $id, 'gravity' => Item::GRAVITY_ACTIVITY, 'verb' => Activity::LIKE]); + $activities = Post::selectPosts(['author-id'], ['thr-parent-id' => $id, 'gravity' => Item::GRAVITY_ACTIVITY, 'verb' => Activity::LIKE, 'deleted' => false]); $accounts = []; diff --git a/src/Object/Api/Mastodon/Instance.php b/src/Object/Api/Mastodon/Instance.php index b6239550a8..5459694e6a 100644 --- a/src/Object/Api/Mastodon/Instance.php +++ b/src/Object/Api/Mastodon/Instance.php @@ -34,7 +34,7 @@ use Friendica\Network\HTTPException; /** * Class Instance * - * @see https://docs.joinmastodon.org/api/entities/#instance + * @see https://docs.joinmastodon.org/entities/V1_Instance/ */ class Instance extends BaseDataTransferObject { diff --git a/src/Object/Api/Mastodon/InstanceV2.php b/src/Object/Api/Mastodon/InstanceV2.php new file mode 100644 index 0000000000..ec32b944f0 --- /dev/null +++ b/src/Object/Api/Mastodon/InstanceV2.php @@ -0,0 +1,108 @@ +. + * + */ + +namespace Friendica\Object\Api\Mastodon; + +use Friendica\BaseDataTransferObject; +use Friendica\Object\Api\Mastodon\InstanceV2\Configuration; +use Friendica\Object\Api\Mastodon\InstanceV2\Contact; +use Friendica\Object\Api\Mastodon\InstanceV2\FriendicaExtensions; +use Friendica\Object\Api\Mastodon\InstanceV2\Registrations; +use Friendica\Object\Api\Mastodon\InstanceV2\Thumbnail; +use Friendica\Object\Api\Mastodon\InstanceV2\Usage; + +/** + * Class Instance + * + * @see https://docs.joinmastodon.org/entities/Instance/ + */ +class InstanceV2 extends BaseDataTransferObject +{ + /** @var string */ + protected $domain; + /** @var string */ + protected $title; + /** @var string */ + protected $version; + /** @var string */ + protected $source_url; + /** @var string */ + protected $description; + /** @var Usage */ + protected $usage; + /** @var Thumbnail */ + protected $thumbnail; + /** @var array */ + protected $languages; + /** @var Configuration */ + protected $configuration; + /** @var Registrations */ + protected $registrations; + /** @var Contact */ + protected $contact; + /** @var array */ + protected $rules = []; + /** @var FriendicaExtensions */ + protected $friendica; + + /** + * @param string $domain + * @param string $title + * @param $version + * @param string $description + * @param Usage $usage + * @param Thumbnail $thumbnail + * @param array $languages + * @param Configuration $configuration + * @param Registrations $registrations + * @param Contact $contact + * @param FriendicaExtensions $friendica_extensions + * @param array $rules + */ + public function __construct( + string $domain, + string $title, + string $version, + string $description, + Usage $usage, + Thumbnail $thumbnail, + array $languages, + Configuration $configuration, + Registrations $registrations, + Contact $contact, + FriendicaExtensions $friendica_extensions, + array $rules + ) { + $this->domain = $domain; + $this->title = $title; + $this->version = $version; + $this->source_url = null; //not supported yet + $this->description = $description; + $this->usage = $usage; + $this->thumbnail = $thumbnail; + $this->languages = $languages; + $this->configuration = $configuration; + $this->registrations = $registrations; + $this->contact = $contact; + $this->rules = $rules; + $this->friendica = $friendica_extensions; + } +} diff --git a/src/Object/Api/Mastodon/InstanceV2/Configuration.php b/src/Object/Api/Mastodon/InstanceV2/Configuration.php new file mode 100644 index 0000000000..a44c2e060f --- /dev/null +++ b/src/Object/Api/Mastodon/InstanceV2/Configuration.php @@ -0,0 +1,54 @@ +. + * + */ + +namespace Friendica\Object\Api\Mastodon\InstanceV2; + +use Friendica\BaseDataTransferObject; + +/** + * Class Configuration + * + * @see https://docs.joinmastodon.org/entities/Instance/ + */ +class Configuration extends BaseDataTransferObject +{ + /** @var StatusesConfig */ + protected $statuses; + /** @var MediaAttachmentsConfig */ + protected $media_attachments; + /** @var int */ + protected $image_size_limit; + + /** + * @param StatusesConfig $statuses + * @param MediaAttachmentsConfig $media_attachments + * @param int $image_size_limit + */ + public function __construct( + StatusesConfig $statuses, + MediaAttachmentsConfig $media_attachments, + int $image_size_limit + ) { + $this->statuses = $statuses; + $this->media_attachments = $media_attachments; + $this->image_size_limit = $image_size_limit; + } +} diff --git a/src/Object/Api/Mastodon/InstanceV2/Contact.php b/src/Object/Api/Mastodon/InstanceV2/Contact.php new file mode 100644 index 0000000000..f48b027cf0 --- /dev/null +++ b/src/Object/Api/Mastodon/InstanceV2/Contact.php @@ -0,0 +1,49 @@ +. + * + */ + +namespace Friendica\Object\Api\Mastodon\InstanceV2; + +use Friendica\BaseDataTransferObject; +use Friendica\Object\Api\Mastodon\Account; + +/** + * Class Contact + * + * @see https://docs.joinmastodon.org/entities/Instance/ + */ +class Contact extends BaseDataTransferObject +{ + /** @var string */ + protected $email; + /** @var Account|null */ + protected $account = null; + + + /** + * @param string $email + * @param Account $account + */ + public function __construct(string $email, Account $account) + { + $this->email = $email; + $this->account = $account; + } +} diff --git a/src/Object/Api/Mastodon/InstanceV2/FriendicaExtensions.php b/src/Object/Api/Mastodon/InstanceV2/FriendicaExtensions.php new file mode 100644 index 0000000000..d3a2372105 --- /dev/null +++ b/src/Object/Api/Mastodon/InstanceV2/FriendicaExtensions.php @@ -0,0 +1,53 @@ +. + * + */ + +namespace Friendica\Object\Api\Mastodon\InstanceV2; + +use Friendica\BaseDataTransferObject; + +/** + * Class FriendicaExtensions + * + * Friendica specific additional fields on the Instance V2 object + * + * @see https://docs.joinmastodon.org/entities/Instance/ + */ +class FriendicaExtensions extends BaseDataTransferObject +{ + /** @var string */ + protected $version; + /** @var string */ + protected $codename; + /** @var int */ + protected $db_version; + + /** + * @param string $version + * @param string $codename + * @param int $db_version + */ + public function __construct(string $version, string $codename, int $db_version) + { + $this->version = $version; + $this->codename = $codename; + $this->db_version = $db_version; + } +} diff --git a/src/Object/Api/Mastodon/InstanceV2/MediaAttachmentsConfig.php b/src/Object/Api/Mastodon/InstanceV2/MediaAttachmentsConfig.php new file mode 100644 index 0000000000..085bdfd0e2 --- /dev/null +++ b/src/Object/Api/Mastodon/InstanceV2/MediaAttachmentsConfig.php @@ -0,0 +1,43 @@ +. + * + */ + +namespace Friendica\Object\Api\Mastodon\InstanceV2; + +use Friendica\BaseDataTransferObject; + +/** + * Class MediaAttachmentsConfig + * + * @see https://docs.joinmastodon.org/entities/Instance/ + */ +class MediaAttachmentsConfig extends BaseDataTransferObject +{ + /** @var string[] */ + protected $supported_mime_types; + + /** + * @param array $supported_mime_types + */ + public function __construct(array $supported_mime_types) + { + $this->supported_mime_types = $supported_mime_types; + } +} diff --git a/src/Object/Api/Mastodon/InstanceV2/Registrations.php b/src/Object/Api/Mastodon/InstanceV2/Registrations.php new file mode 100644 index 0000000000..b4a9054568 --- /dev/null +++ b/src/Object/Api/Mastodon/InstanceV2/Registrations.php @@ -0,0 +1,47 @@ +. + * + */ + +namespace Friendica\Object\Api\Mastodon\InstanceV2; + +use Friendica\BaseDataTransferObject; + +/** + * Class Registrations + * + * @see https://docs.joinmastodon.org/entities/Instance/ + */ +class Registrations extends BaseDataTransferObject +{ + /** @var bool */ + protected $enabled; + /** @var bool */ + protected $approval_required; + + /** + * @param bool $enabled + * @param bool $approval_required + */ + public function __construct(bool $enabled, bool $approval_required) + { + $this->enabled = $enabled; + $this->approval_required = $approval_required; + } +} diff --git a/src/Object/Api/Mastodon/InstanceV2/StatusesConfig.php b/src/Object/Api/Mastodon/InstanceV2/StatusesConfig.php new file mode 100644 index 0000000000..aa2af4f047 --- /dev/null +++ b/src/Object/Api/Mastodon/InstanceV2/StatusesConfig.php @@ -0,0 +1,43 @@ +. + * + */ + +namespace Friendica\Object\Api\Mastodon\InstanceV2; + +use Friendica\BaseDataTransferObject; + +/** + * Class StatusConfig + * + * @see https://docs.joinmastodon.org/entities/Instance/ + */ +class StatusesConfig extends BaseDataTransferObject +{ + /** @var int */ + protected $max_characters = 0; + + /** + * @param int $max_characters + */ + public function __construct(int $max_characters) + { + $this->max_characters = $max_characters; + } +} diff --git a/src/Object/Api/Mastodon/InstanceV2/Thumbnail.php b/src/Object/Api/Mastodon/InstanceV2/Thumbnail.php new file mode 100644 index 0000000000..3d4989e367 --- /dev/null +++ b/src/Object/Api/Mastodon/InstanceV2/Thumbnail.php @@ -0,0 +1,43 @@ +. + * + */ + +namespace Friendica\Object\Api\Mastodon\InstanceV2; + +use Friendica\BaseDataTransferObject; + +/** + * Class Thumbnail + * + * @see https://docs.joinmastodon.org/entities/Instance/ + */ +class Thumbnail extends BaseDataTransferObject +{ + /** @var string (URL) */ + protected $url; + + /** + * @param string $url + */ + public function __construct(string $url) + { + $this->url = $url; + } +} diff --git a/src/Object/Api/Mastodon/InstanceV2/Usage.php b/src/Object/Api/Mastodon/InstanceV2/Usage.php new file mode 100644 index 0000000000..69e26b8ab7 --- /dev/null +++ b/src/Object/Api/Mastodon/InstanceV2/Usage.php @@ -0,0 +1,43 @@ +. + * + */ + +namespace Friendica\Object\Api\Mastodon\InstanceV2; + +use Friendica\BaseDataTransferObject; + +/** + * Class Usage + * + * @see https://docs.joinmastodon.org/entities/Instance/ + */ +class Usage extends BaseDataTransferObject +{ + /** @var UserStats */ + protected $users; + + /** + * @param UserStats $users + */ + public function __construct(UserStats $users) + { + $this->users = $users; + } +} diff --git a/src/Object/Api/Mastodon/InstanceV2/UserStats.php b/src/Object/Api/Mastodon/InstanceV2/UserStats.php new file mode 100644 index 0000000000..8f398e0d5a --- /dev/null +++ b/src/Object/Api/Mastodon/InstanceV2/UserStats.php @@ -0,0 +1,43 @@ +. + * + */ + +namespace Friendica\Object\Api\Mastodon\InstanceV2; + +use Friendica\BaseDataTransferObject; + +/** + * Class UserStats + * + * @see https://docs.joinmastodon.org/entities/Instance/ + */ +class UserStats extends BaseDataTransferObject +{ + /** @var int */ + protected $active_monthly = 0; + + /** + * @param $active_monthly + */ + public function __construct($active_monthly) + { + $this->active_monthly = $active_monthly; + } +} diff --git a/src/Object/Api/Mastodon/Status.php b/src/Object/Api/Mastodon/Status.php index 51f0b1c396..30f554bdf5 100644 --- a/src/Object/Api/Mastodon/Status.php +++ b/src/Object/Api/Mastodon/Status.php @@ -151,7 +151,7 @@ class Status extends BaseDataTransferObject $this->emojis = []; $this->card = $card->toArray() ?: null; $this->poll = $poll; - $this->friendica = new FriendicaExtension($item['title']); + $this->friendica = new FriendicaExtension($item['title'], $counts->dislikes); } /** diff --git a/src/Object/Api/Mastodon/Status/Counts.php b/src/Object/Api/Mastodon/Status/Counts.php index 26eb361cf8..207f78a43d 100644 --- a/src/Object/Api/Mastodon/Status/Counts.php +++ b/src/Object/Api/Mastodon/Status/Counts.php @@ -35,6 +35,9 @@ class Counts /** @var int */ protected $favourites; + /** @var int */ + protected $dislikes; + /** * Creates a status count object * @@ -43,14 +46,16 @@ class Counts * @param int $favourites * @throws \Friendica\Network\HTTPException\InternalServerErrorException */ - public function __construct(int $replies, int $reblogs, int $favourites) + public function __construct(int $replies, int $reblogs, int $favourites, int $dislikes) { - $this->replies = $replies; - $this->reblogs = $reblogs; + $this->replies = $replies; + $this->reblogs = $reblogs; $this->favourites = $favourites; + $this->dislikes = $dislikes; } - public function __get($name) { + public function __get($name) + { return $this->$name; } } diff --git a/src/Object/Api/Mastodon/Status/FriendicaExtension.php b/src/Object/Api/Mastodon/Status/FriendicaExtension.php index 050c5a0268..1db70f7312 100644 --- a/src/Object/Api/Mastodon/Status/FriendicaExtension.php +++ b/src/Object/Api/Mastodon/Status/FriendicaExtension.php @@ -35,14 +35,18 @@ class FriendicaExtension extends BaseDataTransferObject /** @var string */ protected $title; + /** @var int */ + protected $dislikes_count; + /** * Creates a status count object * * @param string $title * @throws \Friendica\Network\HTTPException\InternalServerErrorException */ - public function __construct(string $title) + public function __construct(string $title, int $dislikes_count) { - $this->title = $title; + $this->title = $title; + $this->dislikes_count = $dislikes_count; } } diff --git a/src/Protocol/OStatus.php b/src/Protocol/OStatus.php index 80644329ff..a93a0584dd 100644 --- a/src/Protocol/OStatus.php +++ b/src/Protocol/OStatus.php @@ -496,7 +496,7 @@ class OStatus $orig_uri = $xpath->query('activity:object/atom:id', $entry)->item(0)->nodeValue; Logger::notice('Favorite', ['uri' => $orig_uri, 'item' => $item]); - $item['verb'] = Activity::LIKE; + $item['body'] = $item['verb'] = Activity::LIKE; $item['thr-parent'] = $orig_uri; $item['gravity'] = Item::GRAVITY_ACTIVITY; $item['object-type'] = Activity\ObjectType::NOTE; diff --git a/static/dbstructure.config.php b/static/dbstructure.config.php index 9179921d05..cc420a449b 100644 --- a/static/dbstructure.config.php +++ b/static/dbstructure.config.php @@ -399,23 +399,6 @@ return [ "uid_uri-id" => ["uid", "uri-id"], ] ], - "addon" => [ - "comment" => "registered addons", - "fields" => [ - "id" => ["type" => "int unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1", "comment" => ""], - "name" => ["type" => "varchar(50)", "not null" => "1", "default" => "", "comment" => "addon base (file)name"], - "version" => ["type" => "varchar(50)", "not null" => "1", "default" => "", "comment" => "currently unused"], - "installed" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "currently always 1"], - "hidden" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "currently unused"], - "timestamp" => ["type" => "int unsigned", "not null" => "1", "default" => "0", "comment" => "file timestamp to check for reloads"], - "plugin_admin" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "1 = has admin config, 0 = has no admin config"], - ], - "indexes" => [ - "PRIMARY" => ["id"], - "installed_name" => ["installed", "name"], - "name" => ["UNIQUE", "name"], - ] - ], "apcontact" => [ "comment" => "ActivityPub compatible contacts - used in the ActivityPub implementation", "fields" => [ diff --git a/static/routes.config.php b/static/routes.config.php index 58fce72777..0ff13224c6 100644 --- a/static/routes.config.php +++ b/static/routes.config.php @@ -80,6 +80,9 @@ $apiRoutes = [ '/friendica' => [ '/activity/{verb:attendmaybe|attendno|attendyes|dislike|like|unattendmaybe|unattendno|unattendyes|undislike|unlike}[.{extension:json|xml|rss|atom}]' => [Module\Api\Friendica\Activity::class, [ R::POST]], + '/statuses/{id:\d+}/dislike' => [Module\Api\Friendica\Statuses\Dislike::class, [ R::POST]], + '/statuses/{id:\d+}/disliked_by' => [Module\Api\Friendica\Statuses\DislikedBy::class, [R::GET ]], + '/statuses/{id:\d+}/undislike' => [Module\Api\Friendica\Statuses\Undislike::class, [ R::POST]], '/notification/seen[.{extension:json|xml|rss|atom}]' => [Module\Api\Friendica\Notification\Seen::class, [ R::POST]], '/notification[.{extension:json|xml|rss|atom}]' => [Module\Api\Friendica\Notification::class, [R::GET ]], '/notifications[.{extension:json|xml|rss|atom}]' => [Module\Api\Friendica\Notification::class, [R::GET ]], @@ -307,7 +310,7 @@ return [ '/trends/tags' => [Module\Api\Mastodon\Trends\Tags::class, [R::GET ]], ], '/v2' => [ - '/instance' => [Module\Api\Mastodon\Unimplemented::class, [R::GET ]], // not supported + '/instance' => [Module\Api\Mastodon\InstanceV2::class, [R::GET ]], // not supported ], '/v{version:\d+}' => [ '/admin/accounts' => [Module\Api\Mastodon\Unimplemented::class, [R::GET ]], // not supported diff --git a/tests/src/Module/Api/ApiTest.php b/tests/src/Module/Api/ApiTest.php index 90dfa30b0a..812993aa98 100644 --- a/tests/src/Module/Api/ApiTest.php +++ b/tests/src/Module/Api/ApiTest.php @@ -24,6 +24,7 @@ namespace Friendica\Test\src\Module\Api; use Friendica\App; use Friendica\Capabilities\ICanCreateResponses; use Friendica\Core\Addon; +use Friendica\Core\Config\Capability\IManageConfigValues; use Friendica\Core\Hook; use Friendica\Database\Database; use Friendica\DI; @@ -212,15 +213,14 @@ abstract class ApiTest extends FixtureTest $func(DI::app()); } - /** @var Database $dba */ - $dba = $this->dice->create(Database::class); + /** @var $config IManageConfigValues */ + $config = $this->dice->create(IManageConfigValues::class); - $dba->insert('addon', [ + $config->set('addons', $addon, [ 'name' => $addon, 'installed' => true, 'timestamp' => $t, 'plugin_admin' => function_exists($addon . '_addon_admin'), - 'hidden' => file_exists('addon/' . $addon . '/.hidden') ]); Addon::loadAddons();