diff --git a/src/DI.php b/src/DI.php index 39e892adcb..cb6166692f 100644 --- a/src/DI.php +++ b/src/DI.php @@ -279,6 +279,14 @@ abstract class DI return self::$dice->create(Factory\Api\Mastodon\Relationship::class); } + /** + * @return Factory\Api\Mastodon\Status + */ + public static function mstdnStatus() + { + return self::$dice->create(Factory\Api\Mastodon\Status::class); + } + /** * @return Factory\Api\Twitter\User */ diff --git a/src/Factory/Api/Mastodon/Status.php b/src/Factory/Api/Mastodon/Status.php new file mode 100644 index 0000000000..9a7589a6e7 --- /dev/null +++ b/src/Factory/Api/Mastodon/Status.php @@ -0,0 +1,64 @@ +. + * + */ + +namespace Friendica\Factory\Api\Mastodon; + +use Friendica\App\BaseURL; +use Friendica\BaseFactory; +use Friendica\DI; +use Friendica\Model\Item; +use Friendica\Network\HTTPException; +use Friendica\Repository\ProfileField; +use Psr\Log\LoggerInterface; + +class Status extends BaseFactory +{ + /** @var BaseURL */ + protected $baseUrl; + /** @var ProfileField */ + protected $profileField; + /** @var Field */ + protected $mstdnField; + + public function __construct(LoggerInterface $logger, BaseURL $baseURL, ProfileField $profileField, Field $mstdnField) + { + parent::__construct($logger); + + $this->baseUrl = $baseURL; + $this->profileField = $profileField; + $this->mstdnField = $mstdnField; + } + + /** + * @param int $uriId Uri-ID of the item + * @param int $uid Item user + * @return \Friendica\Object\Api\Mastodon\Status + * @throws HTTPException\InternalServerErrorException + * @throws \ImagickException + */ + public function createFromUriId(int $uriId, $uid = 0) + { + $item = Item::selectFirst([], ['uri-id' => $uriId, 'uid' => $uid]); + $account = DI::mstdnAccount()->createFromContactId($item['author-id']); + + return new \Friendica\Object\Api\Mastodon\Status($item, $account); + } +} diff --git a/src/Model/Item.php b/src/Model/Item.php index baf113e17d..724f61e25d 100644 --- a/src/Model/Item.php +++ b/src/Model/Item.php @@ -685,7 +685,7 @@ class Item 'writable', 'self', 'id' => 'cid', 'alias', 'uid' => 'contact-uid', 'photo', 'name-date', 'uri-date', 'avatar-date', 'thumb', 'dfrn-id']; - $fields['parent-item'] = ['guid' => 'parent-guid', 'network' => 'parent-network']; + $fields['parent-item'] = ['guid' => 'parent-guid', 'network' => 'parent-network', 'author-id' => 'parent-author-id']; $fields['parent-item-author'] = ['url' => 'parent-author-link', 'name' => 'parent-author-name', 'network' => 'parent-author-network']; diff --git a/src/Module/Api/Mastodon/Timelines/PublicTimeline.php b/src/Module/Api/Mastodon/Timelines/PublicTimeline.php new file mode 100644 index 0000000000..d97d8e4f5c --- /dev/null +++ b/src/Module/Api/Mastodon/Timelines/PublicTimeline.php @@ -0,0 +1,81 @@ +. + * + */ + +namespace Friendica\Module\Api\Mastodon\Timelines; + +use Friendica\Core\System; +use Friendica\Database\DBA; +use Friendica\DI; +use Friendica\Model\Item; +use Friendica\Module\BaseApi; +use Friendica\Network\HTTPException; + +/** + * @see https://docs.joinmastodon.org/methods/timelines/ + */ +class PublicTimeline extends BaseApi +{ + /** + * @param array $parameters + * @throws HTTPException\InternalServerErrorException + */ + public static function rawContent(array $parameters = []) + { + // Show only local statuses? Defaults to false. + $local = (bool)!isset($_REQUEST['local']) ? false : ($_REQUEST['local'] == 'true'); + // Show only remote statuses? Defaults to false. + $remote = (bool)!isset($_REQUEST['remote']) ? false : ($_REQUEST['remote'] == 'true'); // Currently not supported + // Show only statuses with media attached? Defaults to false. + $only_media = (bool)!isset($_REQUEST['only_media']) ? false : ($_REQUEST['only_media'] == 'true'); // Currently not supported + // 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']; // Currently not supported + // Maximum number of results to return. Defaults to 20. + $limit = (int)!isset($_REQUEST['limit']) ? 20 : $_REQUEST['limit']; + + $condition = ['gravity' => [GRAVITY_PARENT, GRAVITY_COMMENT], 'private' => Item::PUBLIC]; + if ($local) { + $condition['origin'] = true; + } else { + $condition['uid'] = 0; + } + + if (!empty($max_id)) { + $condition = DBA::mergeConditions($condition, ["`id` <= ?", $max_id]); + } + + if (!empty($since_id)) { + $condition = DBA::mergeConditions($condition, ["`id` >= ?", $since_id]); + } + + $items = Item::selectForUser(0, ['uri-id', 'uid'], $condition, ['order' => ['id' => true], 'limit' => $limit]); + + $statuses = []; + foreach ($items as $item) { + $statuses[] = DI::mstdnStatus()->createFromUriId($item['uri-id'], $item['uid']); + } + + System::jsonExit($statuses); + } +} diff --git a/src/Object/Api/Mastodon/Status.php b/src/Object/Api/Mastodon/Status.php new file mode 100644 index 0000000000..5ebddf4691 --- /dev/null +++ b/src/Object/Api/Mastodon/Status.php @@ -0,0 +1,136 @@ +. + * + */ + +namespace Friendica\Object\Api\Mastodon; + +use Friendica\BaseEntity; +use Friendica\Content\Text\BBCode; +use Friendica\Util\DateTimeFormat; + +/** + * Class Status + * + * @see https://docs.joinmastodon.org/entities/status + */ +class Status extends BaseEntity +{ + /** @var string */ + protected $id; + /** @var string (Datetime) */ + protected $created_at; + /** @var string|null */ + protected $in_reply_to_id = null; + /** @var string|null */ + protected $in_reply_to_account_id = null; + /** @var bool */ + protected $sensitive = false; + /** @var string */ + protected $spoiler_text = ""; + /** @var string (Enum of public, unlisted, private, direct)*/ + protected $visibility; + /** @var string|null */ + protected $language = null; + /** @var string */ + protected $uri; + /** @var string|null (URL)*/ + protected $url = null; + /** @var int */ + protected $replies_count = 0; + /** @var int */ + protected $reblogs_count = 0; + /** @var int */ + protected $favourites_count = 0; + /** @var bool */ + protected $favourited = false; + /** @var bool */ + protected $reblogged = false; + /** @var bool */ + protected $muted = false; + /** @var bool */ + protected $bookmarked = false; + /** @var bool */ + protected $pinned = false; + /** @var string */ + protected $content; + /** @var Status|null */ + protected $reblog = null; + /** @var Application */ + protected $application = null; + /** @var Account */ + protected $account; + /** @var Attachment */ + protected $media_attachments = []; + /** @var Mention */ + protected $mentions = []; + /** @var Tag */ + protected $tags = []; + /** @var Emoji[] */ + protected $emojis = []; + /** @var Card|null */ + protected $card = null; + /** @var Poll|null */ + protected $poll = null; + + /** + * Creates a status record from an item record. + * + * @param array $item + * @throws \Friendica\Network\HTTPException\InternalServerErrorException + */ + public function __construct(array $item, Account $account) + { + $this->id = (string)$item['uri-id']; + $this->created_at = DateTimeFormat::utc($item['created'], DateTimeFormat::ATOM); + + if ($item['gravity'] == GRAVITY_COMMENT) { + $this->in_reply_to_id = (string)$item['thr-parent-id']; + $this->in_reply_to_account_id = (string)$item['parent-author-id']; + } + + $this->sensitive = false; + $this->spoiler_text = $item['title']; + + $visibility = ['public', 'private', 'unlisted']; + $this->visibility = $visibility[$item['private']]; + + $this->language = null; + $this->uri = $item['uri']; + $this->url = $item['plink'] ?? null; + $this->replies_count = 0; + $this->reblogs_count = 0; + $this->favourites_count = 0; + $this->favourited = false; + $this->reblogged = false; + $this->muted = false; + $this->bookmarked = false; + $this->pinned = false; + $this->content = BBCode::convert($item['body'], false); + $this->reblog = null; + $this->application = null; + $this->account = $account->toArray(); + $this->media_attachments = []; + $this->mentions = []; + $this->tags = []; + $this->emojis = []; + $this->card = null; + $this->poll = null; + } +} diff --git a/static/routes.config.php b/static/routes.config.php index 8387366b3d..a9a66bf900 100644 --- a/static/routes.config.php +++ b/static/routes.config.php @@ -48,12 +48,13 @@ return [ '/api' => [ '/v1' => [ - '/custom_emojis' => [Module\Api\Mastodon\CustomEmojis::class, [R::GET ]], - '/directory' => [Module\Api\Mastodon\Directory::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/peers' => [Module\Api\Mastodon\Instance\Peers::class, [R::GET ]], + '/custom_emojis' => [Module\Api\Mastodon\CustomEmojis::class, [R::GET ]], + '/directory' => [Module\Api\Mastodon\Directory::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/peers' => [Module\Api\Mastodon\Instance\Peers::class, [R::GET ]], + '/timelines/public' => [Module\Api\Mastodon\Timelines\PublicTimeline::class, [R::GET ]], ], '/friendica' => [ '/profile/show' => [Module\Api\Friendica\Profile\Show::class , [R::GET ]],