diff --git a/doc/API-Mastodon.md b/doc/API-Mastodon.md index 18338ac16..2efa9a9ff 100644 --- a/doc/API-Mastodon.md +++ b/doc/API-Mastodon.md @@ -48,6 +48,8 @@ These endpoints use the [Mastodon API entities](https://docs.joinmastodon.org/en - [`GET /api/v1/lists/:id`](https://docs.joinmastodon.org/methods/timelines/lists/) - [`GET /api/v1/lists/:id/accounts`](https://docs.joinmastodon.org/methods/timelines/lists/) - [`GET /api/v1/mutes`](https://docs.joinmastodon.org/methods/accounts/mutes/) +- [`GET /api/v1/notifications`](https://docs.joinmastodon.org/methods/notifications/) +- [`GET /api/v1/notifications/:id`](https://docs.joinmastodon.org/methods/notifications/) - [`GET /api/v1/preferences`](https://docs.joinmastodon.org/methods/accounts/preferences/) - [`GET /api/v1/statuses/:id`](https://docs.joinmastodon.org/methods/statuses/) - [`GET /api/v1/statuses/:id/context`](https://docs.joinmastodon.org/methods/statuses/) diff --git a/src/DI.php b/src/DI.php index 045425b88..987cc4fbe 100644 --- a/src/DI.php +++ b/src/DI.php @@ -311,6 +311,14 @@ abstract class DI return self::$dice->create(Factory\Api\Mastodon\Mention::class); } + /** + * @return Factory\Api\Mastodon\Notification + */ + public static function mstdnNotification() + { + return self::$dice->create(Factory\Api\Mastodon\Notification::class); + } + /** * @return Factory\Api\Mastodon\Tag */ diff --git a/src/Factory/Api/Mastodon/Notification.php b/src/Factory/Api/Mastodon/Notification.php new file mode 100644 index 000000000..78b106c04 --- /dev/null +++ b/src/Factory/Api/Mastodon/Notification.php @@ -0,0 +1,89 @@ +. + * + */ + +namespace Friendica\Factory\Api\Mastodon; + +use Friendica\BaseFactory; +use Friendica\Database\DBA; +use Friendica\DI; +use Friendica\Model\Contact; +use Friendica\Model\Notification as ModelNotification; + +class Notification extends BaseFactory +{ + public function create(int $id) + { + $notification = DBA::selectFirst('notify', [], ['id' => $id]); + if (!DBA::isResult($notification)) { + return null; + } + + $cid = Contact::getIdForURL($notification['url'], 0, false); + if (empty($cid)) { + return null; + } + + /* + follow = Someone followed you + follow_request = Someone requested to follow you + mention = Someone mentioned you in their status + reblog = Someone boosted one of your statuses + favourite = Someone favourited one of your statuses + poll = A poll you have voted in or created has ended + status = Someone you enabled notifications for has posted a status + */ + + switch ($notification['type']) { + case ModelNotification\Type::INTRO: + $type = 'follow_request'; + break; + + case ModelNotification\Type::WALL: + case ModelNotification\Type::COMMENT: + case ModelNotification\Type::MAIL: + case ModelNotification\Type::TAG_SELF: + case ModelNotification\Type::POKE: + $type = 'mention'; + break; + + case ModelNotification\Type::SHARE: + $type = 'status'; + break; + + default: + return null; + } + + $account = DI::mstdnAccount()->createFromContactId($cid); + + if (!empty($notification['uri-id'])) { + try { + $status = DI::mstdnStatus()->createFromUriId($notification['uri-id'], $notification['uid']); + } catch (\Throwable $th) { + $status = null; + } + } else { + $status = null; + } + + return new \Friendica\Object\Api\Mastodon\Notification($id, $type, $notification['date'], $account, $status); + } +} diff --git a/src/Module/Api/Mastodon/Notifications.php b/src/Module/Api/Mastodon/Notifications.php new file mode 100644 index 000000000..59bf3e0d3 --- /dev/null +++ b/src/Module/Api/Mastodon/Notifications.php @@ -0,0 +1,124 @@ +. + * + */ + +namespace Friendica\Module\Api\Mastodon; + +use Friendica\Core\Logger; +use Friendica\Core\System; +use Friendica\Database\DBA; +use Friendica\DI; +use Friendica\Model\Contact; +use Friendica\Model\Notification; +use Friendica\Module\BaseApi; + +/** + * @see https://docs.joinmastodon.org/methods/accounts/mutes/ + */ +class Notifications extends BaseApi +{ + /** + * @param array $parameters + * @throws \Friendica\Network\HTTPException\InternalServerErrorException + */ + public static function rawContent(array $parameters = []) + { + self::login(); + $uid = self::getCurrentUserID(); + + if (!empty($parameters['id'])) { + $id = $parameters['id']; + if (!DBA::exists('notify', ['id' => $id, 'uid' => $uid])) { + DI::mstdnError()->RecordNotFound(); + } + System::jsonExit(DI::mstdnNotification()->create($id)); + } + + // Return results older than this ID + $max_id = (int)!isset($_REQUEST['max_id']) ? 0 : $_REQUEST['max_id']; + + // Return results newer than this ID + $since_id = (int)!isset($_REQUEST['since_id']) ? 0 : $_REQUEST['since_id']; + + // Return results immediately newer than this ID + $min_id = (int)!isset($_REQUEST['min_id']) ? 0 : $_REQUEST['min_id']; + + // Maximum number of results to return (default 20) + $limit = (int)!isset($_REQUEST['limit']) ? 20 : $_REQUEST['limit']; + + // Array of types to exclude (follow, favourite, reblog, mention, poll, follow_request) + $exclude_types = !isset($_REQUEST['exclude_types']) ? [] : $_REQUEST['exclude_types']; + + // Return only notifications received from this account + $account_id = (int)!isset($_REQUEST['account_id']) ? 0 : $_REQUEST['account_id']; + + $params = ['order' => ['id' => true], 'limit' => $limit]; + + $condition = ['uid' => $uid, 'seen' => false, 'type' => []]; + + if (!empty($account_id)) { + $contact = Contact::getById($account_id, ['url']); + if (!empty($contact['url'])) { + $condition['url'] = $contact['url']; + } + } + + if (!in_array('follow_request', $exclude_types)) { + $condition['type'] = array_merge($condition['type'], [Notification\Type::INTRO]); + } + + if (!in_array('mention', $exclude_types)) { + $condition['type'] = array_merge($condition['type'], + [Notification\Type::WALL, Notification\Type::COMMENT, Notification\Type::MAIL, + Notification\Type::TAG_SELF, Notification\Type::POKE]); + } + + if (!in_array('status', $exclude_types)) { + $condition['type'] = array_merge($condition['type'], [Notification\Type::SHARE]); + } + + if (!empty($max_id)) { + $condition = DBA::mergeConditions($condition, ["`id` < ?", $max_id]); + } + + if (!empty($since_id)) { + $condition = DBA::mergeConditions($condition, ["`id` > ?", $since_id]); + } + + if (!empty($min_id)) { + $condition = DBA::mergeConditions($condition, ["`id` > ?", $min_id]); + + $params['order'] = ['id']; + } + + $notifications = []; + + $notify = DBA::select('notify', ['id'], $condition, $params); + while ($notification = DBA::fetch($notify)) { + $notifications[] = DI::mstdnNotification()->create($notification['id']); + } + + if (!empty($min_id)) { + array_reverse($notifications); + } + + System::jsonExit($notifications); + } +} diff --git a/src/Object/Api/Mastodon/Notification.php b/src/Object/Api/Mastodon/Notification.php new file mode 100644 index 000000000..ee4930e3c --- /dev/null +++ b/src/Object/Api/Mastodon/Notification.php @@ -0,0 +1,78 @@ +. + * + */ + +namespace Friendica\Object\Api\Mastodon; + +use Friendica\BaseDataTransferObject; +use Friendica\Util\DateTimeFormat; + +/** + * Class Notification + * + * @see https://docs.joinmastodon.org/entities/notification/ + */ +class Notification extends BaseDataTransferObject +{ + /** @var string */ + protected $id; + /** @var string (Enumerable oneOf) */ + protected $type; + /** @var string (Datetime) */ + protected $created_at; + /** @var Account */ + protected $account; + /** @var Status|null */ + protected $status = null; + + /** + * Creates a notification record + * + * @param array $item + * @throws \Friendica\Network\HTTPException\InternalServerErrorException + */ + public function __construct(int $id, string $type, string $created_at, Account $account = null, Status $status = null) + { + $this->id = (string)$id; + $this->type = $type; + $this->created_at = DateTimeFormat::utc($created_at, DateTimeFormat::ATOM); + $this->account = $account->toArray(); + + if (!empty($status)) { + $this->status = $status->toArray(); + } + } + + /** + * Returns the current entity as an array + * + * @return array + */ + public function toArray(): array + { + $notification = parent::toArray(); + + if (!$notification['status']) { + unset($notification['status']); + } + + return $notification; + } +} diff --git a/static/routes.config.php b/static/routes.config.php index c786ca2fd..e00a7f0e3 100644 --- a/static/routes.config.php +++ b/static/routes.config.php @@ -115,8 +115,8 @@ return [ '/media' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], '/media/{id:\d+}' => [Module\Api\Mastodon\Unimplemented::class, [R::GET, R::PUT]], // @todo '/mutes' => [Module\Api\Mastodon\Mutes::class, [R::GET ]], - '/notifications' => [Module\Api\Mastodon\Unimplemented::class, [R::GET ]], // @todo - '/notifications/{id:\d+}' => [Module\Api\Mastodon\Unimplemented::class, [R::GET ]], // @todo + '/notifications' => [Module\Api\Mastodon\Notifications::class, [R::GET ]], + '/notifications/{id:\d+}' => [Module\Api\Mastodon\Notifications::class, [R::GET ]], '/notifications/clear' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], '/notifications/{id:\d+}/dismiss' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], '/polls/{id:\d+}' => [Module\Api\Mastodon\Unimplemented::class, [R::GET ]], // not implemented