From 98aed4dde31234883bb3eb5f77143159641a8ea5 Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 28 Feb 2023 06:19:19 +0000 Subject: [PATCH 1/3] Adding and removing of pictures via API is now possible --- src/Factory/Api/Mastodon/StatusSource.php | 2 +- src/Model/Post/Media.php | 14 ++- src/Module/Api/Mastodon/Statuses.php | 122 +++++++++++++++------- 3 files changed, 101 insertions(+), 37 deletions(-) diff --git a/src/Factory/Api/Mastodon/StatusSource.php b/src/Factory/Api/Mastodon/StatusSource.php index 87d836cd6..13d6145ca 100644 --- a/src/Factory/Api/Mastodon/StatusSource.php +++ b/src/Factory/Api/Mastodon/StatusSource.php @@ -41,7 +41,7 @@ class StatusSource extends BaseFactory $post = Post::selectFirst(['uri-id', 'raw-body', 'body', 'title'], ['uri-id' => $uriId, 'uid' => [0, $uid]]); $spoiler_text = $post['title'] ?: BBCode::toPlaintext(BBCode::getAbstract($post['body'], Protocol::ACTIVITYPUB)); - $body = BBCode::toMarkdown($post['body']); + $body = BBCode::toMarkdown(Post\Media::removeFromEndOfBody($post['body'])); return new \Friendica\Object\Api\Mastodon\StatusSource($post['uri-id'], $body, $spoiler_text); } diff --git a/src/Model/Post/Media.php b/src/Model/Post/Media.php index 653bdba98..6454cefda 100644 --- a/src/Model/Post/Media.php +++ b/src/Model/Post/Media.php @@ -721,7 +721,7 @@ class Media * * @param int $uri_id URI id * @param array $types Media types - * @return bool Whether media attachment exists + * @return bool result of deletion * @throws \Exception */ public static function deleteByURIId(int $uri_id, array $types = []): bool @@ -735,6 +735,18 @@ class Media return DBA::delete('post-media', $condition); } + /** + * Delete media by id + * + * @param int $id media id + * @return bool result of deletion + * @throws \Exception + */ + public static function deleteById(int $id): bool + { + return DBA::delete('post-media', ['id' => $id]); + } + /** * Split the attachment media in the three segments "visual", "link" and "additional" * diff --git a/src/Module/Api/Mastodon/Statuses.php b/src/Module/Api/Mastodon/Statuses.php index 01856b2d8..943fc7514 100644 --- a/src/Module/Api/Mastodon/Statuses.php +++ b/src/Module/Api/Mastodon/Statuses.php @@ -50,6 +50,7 @@ class Statuses extends BaseApi $request = $this->getRequest([ 'status' => '', // Text content of the status. If media_ids is provided, this becomes optional. Attaching a poll is optional while status is provided. + 'media_ids' => [], // Array of Attachment ids to be attached as media. If provided, status becomes optional, and poll cannot be used. 'in_reply_to_id' => 0, // ID of the status being replied to, if status is a reply 'spoiler_text' => '', // Text to be shown as a warning or subject before the actual content. Statuses are generally collapsed behind this field. 'language' => '', // ISO 639 language code for this status. @@ -65,7 +66,7 @@ class Statuses extends BaseApi 'origin' => true, ]; - $post = Post::selectFirst(['uri-id', 'id'], $condition); + $post = Post::selectFirst(['uri-id', 'id', 'uid', 'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid'], $condition); if (empty($post['id'])) { throw new HTTPException\NotFoundException('Item with URI ID ' . $this->parameters['id'] . ' not found for user ' . $uid . '.'); } @@ -85,6 +86,44 @@ class Statuses extends BaseApi } } + if (!empty($request['media_ids'])) { + /* + The provided ids in the request value consists of these two sources: + - The id in the "photo" table for newly uploaded media + - The id in the "post-media" table for already attached media + + Because of this we have to add all media that isn't already attached. + Also we have to delete all media that isn't provided anymore. + + There is a possible situation where the newly uploaded media + could have the same id as an existing, but deleted media. + + We can't do anything about this, but the probability for this is extremely low. + */ + $media_ids = []; + $existing_media = array_column(Post\Media::getByURIId($post['uri-id'], [Post\Media::AUDIO, Post\Media::VIDEO, Post\Media::IMAGE]), 'id'); + + foreach ($request['media_ids'] as $media) { + if (!in_array($media, $existing_media)) { + $media_ids[] = $media; + } + } + + foreach ($existing_media as $media) { + if (!in_array($media, $request['media_ids'])) { + Post\Media::deleteById($media); + } + } + + $item = $this->storeMediaIds($media_ids, array_merge($post, $item)); + + foreach ($item['attachments'] as $attachment) { + $attachment['uri-id'] = $post['uri-id']; + Post\Media::insert($attachment); + } + unset($item['attachments']); + } + Item::update($item, ['id' => $post['id']]); Item::updateDisplayCache($post['uri-id']); @@ -222,40 +261,7 @@ class Statuses extends BaseApi $item = DI::contentItem()->expandTags($item, $request['visibility'] == 'direct'); if (!empty($request['media_ids'])) { - $item['object-type'] = Activity\ObjectType::IMAGE; - $item['post-type'] = Item::PT_IMAGE; - $item['attachments'] = []; - - foreach ($request['media_ids'] as $id) { - $media = DBA::toArray(DBA::p("SELECT `resource-id`, `scale`, `type`, `desc`, `filename`, `datasize`, `width`, `height` FROM `photo` - WHERE `resource-id` IN (SELECT `resource-id` FROM `photo` WHERE `id` = ?) AND `photo`.`uid` = ? - ORDER BY `photo`.`width` DESC LIMIT 2", $id, $uid)); - - if (empty($media)) { - continue; - } - - Photo::setPermissionForRessource($media[0]['resource-id'], $uid, $item['allow_cid'], $item['allow_gid'], $item['deny_cid'], $item['deny_gid']); - - $ressources[] = $media[0]['resource-id']; - $phototypes = Images::supportedTypes(); - $ext = $phototypes[$media[0]['type']]; - - $attachment = ['type' => Post\Media::IMAGE, 'mimetype' => $media[0]['type'], - 'url' => DI::baseUrl() . '/photo/' . $media[0]['resource-id'] . '-' . $media[0]['scale'] . '.' . $ext, - 'size' => $media[0]['datasize'], - 'name' => $media[0]['filename'] ?: $media[0]['resource-id'], - 'description' => $media[0]['desc'] ?? '', - 'width' => $media[0]['width'], - 'height' => $media[0]['height']]; - - if (count($media) > 1) { - $attachment['preview'] = DI::baseUrl() . '/photo/' . $media[1]['resource-id'] . '-' . $media[1]['scale'] . '.' . $ext; - $attachment['preview-width'] = $media[1]['width']; - $attachment['preview-height'] = $media[1]['height']; - } - $item['attachments'][] = $attachment; - } + $item = $this->storeMediaIds($request['media_ids'], $item); } if (!empty($request['scheduled_at'])) { @@ -322,4 +328,50 @@ class Statuses extends BaseApi return 'API'; } } + + /** + * Store provided media ids in the item array and adjust permissions + * + * @param array $media_ids + * @param array $item + * @return array + */ + private function storeMediaIds(array $media_ids, array $item): array + { + $item['object-type'] = Activity\ObjectType::IMAGE; + $item['post-type'] = Item::PT_IMAGE; + $item['attachments'] = []; + + foreach ($media_ids as $id) { + $media = DBA::toArray(DBA::p("SELECT `resource-id`, `scale`, `type`, `desc`, `filename`, `datasize`, `width`, `height` FROM `photo` + WHERE `resource-id` IN (SELECT `resource-id` FROM `photo` WHERE `id` = ?) AND `photo`.`uid` = ? + ORDER BY `photo`.`width` DESC LIMIT 2", $id, $item['uid'])); + + if (empty($media)) { + continue; + } + + Photo::setPermissionForRessource($media[0]['resource-id'], $item['uid'], $item['allow_cid'], $item['allow_gid'], $item['deny_cid'], $item['deny_gid']); + + $ressources[] = $media[0]['resource-id']; + $phototypes = Images::supportedTypes(); + $ext = $phototypes[$media[0]['type']]; + + $attachment = ['type' => Post\Media::IMAGE, 'mimetype' => $media[0]['type'], + 'url' => DI::baseUrl() . '/photo/' . $media[0]['resource-id'] . '-' . $media[0]['scale'] . '.' . $ext, + 'size' => $media[0]['datasize'], + 'name' => $media[0]['filename'] ?: $media[0]['resource-id'], + 'description' => $media[0]['desc'] ?? '', + 'width' => $media[0]['width'], + 'height' => $media[0]['height']]; + + if (count($media) > 1) { + $attachment['preview'] = DI::baseUrl() . '/photo/' . $media[1]['resource-id'] . '-' . $media[1]['scale'] . '.' . $ext; + $attachment['preview-width'] = $media[1]['width']; + $attachment['preview-height'] = $media[1]['height']; + } + $item['attachments'][] = $attachment; + } + return $item; + } } From 3a0b0ffce5f26f9de8ae0f716d0c964f93662479 Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 28 Feb 2023 08:06:49 +0000 Subject: [PATCH 2/3] Editing of media descriptions is now possible as well --- src/Factory/Api/Mastodon/Attachment.php | 84 ++++++++++++++++--------- src/Model/Post/Media.php | 24 +++++++ src/Module/Api/Mastodon/Media.php | 11 +++- 3 files changed, 88 insertions(+), 31 deletions(-) diff --git a/src/Factory/Api/Mastodon/Attachment.php b/src/Factory/Api/Mastodon/Attachment.php index 197ade0fe..2b53ae67d 100644 --- a/src/Factory/Api/Mastodon/Attachment.php +++ b/src/Factory/Api/Mastodon/Attachment.php @@ -51,41 +51,65 @@ class Attachment extends BaseFactory { $attachments = []; foreach (Post\Media::getByURIId($uriId, [Post\Media::AUDIO, Post\Media::VIDEO, Post\Media::IMAGE]) as $attachment) { - $filetype = !empty($attachment['mimetype']) ? strtolower(substr($attachment['mimetype'], 0, strpos($attachment['mimetype'], '/'))) : ''; - - if (($filetype == 'audio') || ($attachment['type'] == Post\Media::AUDIO)) { - $type = 'audio'; - } elseif (($filetype == 'video') || ($attachment['type'] == Post\Media::VIDEO)) { - $type = 'video'; - } elseif ($attachment['mimetype'] == 'image/gif') { - $type = 'gifv'; - } elseif (($filetype == 'image') || ($attachment['type'] == Post\Media::IMAGE)) { - $type = 'image'; - } else { - $type = 'unknown'; - } - - $remote = $attachment['url']; - if ($type == 'image') { - $url = Post\Media::getPreviewUrlForId($attachment['id']); - $preview = Post\Media::getPreviewUrlForId($attachment['id'], Proxy::SIZE_SMALL); - } else { - $url = $attachment['url']; - - if (!empty($attachment['preview'])) { - $preview = Post\Media::getPreviewUrlForId($attachment['id'], Proxy::SIZE_SMALL); - } else { - $preview = ''; - } - } - - $object = new \Friendica\Object\Api\Mastodon\Attachment($attachment, $type, $url, $preview, $remote); - $attachments[] = $object->toArray(); + $attachments[] = $this->createFromMediaArray($attachment); } return $attachments; } + /** + * @param int $id id of the media + * @return array + * @throws HTTPException\InternalServerErrorException + */ + public function createFromId(int $id): array + { + $attachment = Post\Media::getById($id); + if (empty($attachment)) { + return []; + } + return $this->createFromMediaArray($attachment); + } + + /** + * @param array $attachment + * @return array + * @throws HTTPException\InternalServerErrorException + */ + private function createFromMediaArray(array $attachment): array + { + $filetype = !empty($attachment['mimetype']) ? strtolower(substr($attachment['mimetype'], 0, strpos($attachment['mimetype'], '/'))) : ''; + + if (($filetype == 'audio') || ($attachment['type'] == Post\Media::AUDIO)) { + $type = 'audio'; + } elseif (($filetype == 'video') || ($attachment['type'] == Post\Media::VIDEO)) { + $type = 'video'; + } elseif ($attachment['mimetype'] == 'image/gif') { + $type = 'gifv'; + } elseif (($filetype == 'image') || ($attachment['type'] == Post\Media::IMAGE)) { + $type = 'image'; + } else { + $type = 'unknown'; + } + + $remote = $attachment['url']; + if ($type == 'image') { + $url = Post\Media::getPreviewUrlForId($attachment['id']); + $preview = Post\Media::getPreviewUrlForId($attachment['id'], Proxy::SIZE_SMALL); + } else { + $url = $attachment['url']; + + if (!empty($attachment['preview'])) { + $preview = Post\Media::getPreviewUrlForId($attachment['id'], Proxy::SIZE_SMALL); + } else { + $preview = ''; + } + } + + $object = new \Friendica\Object\Api\Mastodon\Attachment($attachment, $type, $url, $preview, $remote); + return $object->toArray(); + } + /** * @param int $id id of the photo * diff --git a/src/Model/Post/Media.php b/src/Model/Post/Media.php index 6454cefda..8545760be 100644 --- a/src/Model/Post/Media.php +++ b/src/Model/Post/Media.php @@ -697,6 +697,30 @@ class Media return DBA::selectToArray('post-media', [], $condition, ['order' => ['id']]); } + /** + * Retrieves the media attachment with the provided media id. + * + * @param int $id id + * @return array|bool Array on success, false on error + * @throws \Exception + */ + public static function getById(int $id) + { + return DBA::selectFirst('post-media', [], ['id' => $id]); + } + + /** + * Update post-media entries + * + * @param array $fields + * @param int $id + * @return bool + */ + public static function updateById(array $fields, int $id): bool + { + return DBA::update('post-media', $fields, ['id' => $id]); + } + /** * Checks if media attachments are associated with the provided item ID. * diff --git a/src/Module/Api/Mastodon/Media.php b/src/Module/Api/Mastodon/Media.php index 2f71d53c2..e26637601 100644 --- a/src/Module/Api/Mastodon/Media.php +++ b/src/Module/Api/Mastodon/Media.php @@ -25,6 +25,7 @@ use Friendica\Core\Logger; use Friendica\Core\System; use Friendica\DI; use Friendica\Model\Photo; +use Friendica\Model\Post; use Friendica\Module\BaseApi; /** @@ -71,7 +72,15 @@ class Media extends BaseApi $photo = Photo::selectFirst(['resource-id'], ['id' => $this->parameters['id'], 'uid' => $uid]); if (empty($photo['resource-id'])) { - DI::mstdnError()->RecordNotFound(); + $media = Post\Media::getById($this->parameters['id']); + if (empty($media['uri-id'])) { + DI::mstdnError()->RecordNotFound(); + } + if (!Post::exists(['uri-id' => $media['uri-id'], 'uid' => $uid, 'origin' => true])) { + DI::mstdnError()->RecordNotFound(); + } + Post\Media::updateById(['description' => $request['description']], $this->parameters['id']); + System::jsonExit(DI::mstdnAttachment()->createFromId($this->parameters['id'])); } Photo::update(['desc' => $request['description']], ['resource-id' => $photo['resource-id']]); From 7dd9972b8515a895bc289a8c8d64bdf2d41d5358 Mon Sep 17 00:00:00 2001 From: Michael Date: Wed, 15 Mar 2023 03:55:39 +0000 Subject: [PATCH 3/3] Changes because of code review --- src/Factory/Api/Mastodon/Attachment.php | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Factory/Api/Mastodon/Attachment.php b/src/Factory/Api/Mastodon/Attachment.php index 2b53ae67d..afc2f7c27 100644 --- a/src/Factory/Api/Mastodon/Attachment.php +++ b/src/Factory/Api/Mastodon/Attachment.php @@ -59,10 +59,10 @@ class Attachment extends BaseFactory /** * @param int $id id of the media - * @return array + * @return \Friendica\Object\Api\Mastodon\Attachment * @throws HTTPException\InternalServerErrorException */ - public function createFromId(int $id): array + public function createFromId(int $id): \Friendica\Object\Api\Mastodon\Attachment { $attachment = Post\Media::getById($id); if (empty($attachment)) { @@ -73,10 +73,10 @@ class Attachment extends BaseFactory /** * @param array $attachment - * @return array + * @return \Friendica\Object\Api\Mastodon\Attachment * @throws HTTPException\InternalServerErrorException */ - private function createFromMediaArray(array $attachment): array + private function createFromMediaArray(array $attachment): \Friendica\Object\Api\Mastodon\Attachment { $filetype = !empty($attachment['mimetype']) ? strtolower(substr($attachment['mimetype'], 0, strpos($attachment['mimetype'], '/'))) : ''; @@ -106,8 +106,7 @@ class Attachment extends BaseFactory } } - $object = new \Friendica\Object\Api\Mastodon\Attachment($attachment, $type, $url, $preview, $remote); - return $object->toArray(); + return new \Friendica\Object\Api\Mastodon\Attachment($attachment, $type, $url, $preview, $remote); } /**