diff --git a/database.sql b/database.sql index 8ae1b913b5..1c361b6762 100644 --- a/database.sql +++ b/database.sql @@ -1,6 +1,6 @@ -- ------------------------------------------ -- Friendica 2021.09-dev (Siberian Iris) --- DB_UPDATE_VERSION 1430 +-- DB_UPDATE_VERSION 1431 -- ------------------------------------------ @@ -492,6 +492,31 @@ CREATE TABLE IF NOT EXISTS `conversation` ( INDEX `received` (`received`) ) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Raw data and structure information for messages'; +-- +-- TABLE workerqueue +-- +CREATE TABLE IF NOT EXISTS `workerqueue` ( + `id` int unsigned NOT NULL auto_increment COMMENT 'Auto incremented worker task id', + `command` varchar(100) COMMENT 'Task command', + `parameter` mediumtext COMMENT 'Task parameter', + `priority` tinyint unsigned NOT NULL DEFAULT 0 COMMENT 'Task priority', + `created` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'Creation date', + `pid` int unsigned NOT NULL DEFAULT 0 COMMENT 'Process id of the worker', + `executed` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'Execution date', + `next_try` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'Next retrial date', + `retrial` tinyint NOT NULL DEFAULT 0 COMMENT 'Retrial counter', + `done` boolean NOT NULL DEFAULT '0' COMMENT 'Marked 1 when the task was done - will be deleted later', + PRIMARY KEY(`id`), + INDEX `command` (`command`), + INDEX `done_command_parameter` (`done`,`command`,`parameter`(64)), + INDEX `done_executed` (`done`,`executed`), + INDEX `done_priority_retrial_created` (`done`,`priority`,`retrial`,`created`), + INDEX `done_priority_next_try` (`done`,`priority`,`next_try`), + INDEX `done_pid_next_try` (`done`,`pid`,`next_try`), + INDEX `done_pid_retrial` (`done`,`pid`,`retrial`), + INDEX `done_pid_priority_created` (`done`,`pid`,`priority`,`created`) +) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Background tasks queue entries'; + -- -- TABLE delayed-post -- @@ -500,9 +525,12 @@ CREATE TABLE IF NOT EXISTS `delayed-post` ( `uri` varchar(255) COMMENT 'URI of the post that will be distributed later', `uid` mediumint unsigned COMMENT 'Owner User id', `delayed` datetime COMMENT 'delay time', + `wid` int unsigned COMMENT 'Workerqueue id', PRIMARY KEY(`id`), UNIQUE INDEX `uid_uri` (`uid`,`uri`(190)), - FOREIGN KEY (`uid`) REFERENCES `user` (`uid`) ON UPDATE RESTRICT ON DELETE CASCADE + INDEX `wid` (`wid`), + FOREIGN KEY (`uid`) REFERENCES `user` (`uid`) ON UPDATE RESTRICT ON DELETE CASCADE, + FOREIGN KEY (`wid`) REFERENCES `workerqueue` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE ) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Posts that are about to be distributed at a later time'; -- @@ -1474,31 +1502,6 @@ CREATE TABLE IF NOT EXISTS `worker-ipc` ( PRIMARY KEY(`key`) ) ENGINE=MEMORY DEFAULT COLLATE utf8mb4_general_ci COMMENT='Inter process communication between the frontend and the worker'; --- --- TABLE workerqueue --- -CREATE TABLE IF NOT EXISTS `workerqueue` ( - `id` int unsigned NOT NULL auto_increment COMMENT 'Auto incremented worker task id', - `command` varchar(100) COMMENT 'Task command', - `parameter` mediumtext COMMENT 'Task parameter', - `priority` tinyint unsigned NOT NULL DEFAULT 0 COMMENT 'Task priority', - `created` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'Creation date', - `pid` int unsigned NOT NULL DEFAULT 0 COMMENT 'Process id of the worker', - `executed` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'Execution date', - `next_try` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'Next retrial date', - `retrial` tinyint NOT NULL DEFAULT 0 COMMENT 'Retrial counter', - `done` boolean NOT NULL DEFAULT '0' COMMENT 'Marked 1 when the task was done - will be deleted later', - PRIMARY KEY(`id`), - INDEX `command` (`command`), - INDEX `done_command_parameter` (`done`,`command`,`parameter`(64)), - INDEX `done_executed` (`done`,`executed`), - INDEX `done_priority_retrial_created` (`done`,`priority`,`retrial`,`created`), - INDEX `done_priority_next_try` (`done`,`priority`,`next_try`), - INDEX `done_pid_next_try` (`done`,`pid`,`next_try`), - INDEX `done_pid_retrial` (`done`,`pid`,`retrial`), - INDEX `done_pid_priority_created` (`done`,`pid`,`priority`,`created`) -) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Background tasks queue entries'; - -- -- VIEW application-view -- diff --git a/doc/API-Mastodon.md b/doc/API-Mastodon.md index 2eadc2a1bd..006b8da473 100644 --- a/doc/API-Mastodon.md +++ b/doc/API-Mastodon.md @@ -93,6 +93,7 @@ These endpoints use the [Mastodon API entities](https://docs.joinmastodon.org/en - [`POST /api/v1/notifications/clear`](https://docs.joinmastodon.org/methods/notifications/) - [`POST /api/v1/notifications/:id/dismiss`](https://docs.joinmastodon.org/methods/notifications/) - [`GET /api/v1/preferences`](https://docs.joinmastodon.org/methods/accounts/preferences/) +- [`GET /api/v1/scheduled_statuses`](https://docs.joinmastodon.org/methods/statuses/scheduled_statuses/) - [`GET /api/v1/search`](https://docs.joinmastodon.org/methods/search/) - [`POST /api/v1/statuses`](https://docs.joinmastodon.org/methods/statuses/) - [`GET /api/v1/statuses/:id`](https://docs.joinmastodon.org/methods/statuses/) @@ -137,7 +138,6 @@ They refer to features that don't exist in Friendica yet. - [`GET /api/v1/endorsements`](https://docs.joinmastodon.org/methods/accounts/endorsements/) - [`GET /api/v1/filters`](https://docs.joinmastodon.org/methods/accounts/filters/) - [`GET /api/v1/markers`](https://docs.joinmastodon.org/methods/timelines/markers/) -- [`GET /api/v1/scheduled_statuses`](https://docs.joinmastodon.org/methods/statuses/scheduled_statuses/) ## Non supportable endpoints diff --git a/doc/database/db_delayed-post.md b/doc/database/db_delayed-post.md index fe3251f70a..b792f2f005 100644 --- a/doc/database/db_delayed-post.md +++ b/doc/database/db_delayed-post.md @@ -12,6 +12,7 @@ Fields | uri | URI of the post that will be distributed later | varchar(255) | YES | | NULL | | | uid | Owner User id | mediumint unsigned | YES | | NULL | | | delayed | delay time | datetime | YES | | NULL | | +| wid | Workerqueue id | int unsigned | YES | | NULL | | Indexes ------------ @@ -20,6 +21,7 @@ Indexes | ------- | --------------------- | | PRIMARY | id | | uid_uri | UNIQUE, uid, uri(190) | +| wid | wid | Foreign Keys ------------ @@ -27,5 +29,6 @@ Foreign Keys | Field | Target Table | Target Field | |-------|--------------|--------------| | uid | [user](help/database/db_user) | uid | +| wid | [workerqueue](help/database/db_workerqueue) | id | Return to [database documentation](help/database) diff --git a/mod/photos.php b/mod/photos.php index 132222f046..9e1fa9cede 100644 --- a/mod/photos.php +++ b/mod/photos.php @@ -411,7 +411,7 @@ function photos_post(App $a) } if ($item_id) { - $item = Post::selectFirst(['tag', 'inform', 'uri-id'], ['id' => $item_id, 'uid' => $page_owner_uid]); + $item = Post::selectFirst(['inform', 'uri-id'], ['id' => $item_id, 'uid' => $page_owner_uid]); if (DBA::isResult($item)) { $old_inform = $item['inform']; diff --git a/src/Core/Worker.php b/src/Core/Worker.php index 824275fa74..307451c052 100644 --- a/src/Core/Worker.php +++ b/src/Core/Worker.php @@ -1200,7 +1200,7 @@ class Worker * or: Worker::add(PRIORITY_HIGH, "Notifier", Delivery::DELETION, $drop_id); * or: Worker::add(array('priority' => PRIORITY_HIGH, 'dont_fork' => true), "Delivery", $post_id); * - * @return boolean "false" if worker queue entry already existed or there had been an error + * @return int "0" if worker queue entry already existed or there had been an error, otherwise the ID of the worker task * @throws \Friendica\Network\HTTPException\InternalServerErrorException * @note $cmd and string args are surrounded with "" * @@ -1213,14 +1213,14 @@ class Worker $args = func_get_args(); if (!count($args)) { - return false; + return 0; } $arr = ['args' => $args, 'run_cmd' => true]; Hook::callAll("proc_run", $arr); if (!$arr['run_cmd'] || !count($args)) { - return true; + return 1; } $priority = PRIORITY_MEDIUM; @@ -1255,7 +1255,7 @@ class Worker $command = array_shift($args); $parameters = json_encode($args); $found = DBA::exists('workerqueue', ['command' => $command, 'parameter' => $parameters, 'done' => false]); - $added = false; + $added = 0; if (!in_array($priority, PRIORITIES)) { Logger::warning('Invalid priority', ['priority' => $priority, 'command' => $command, 'callstack' => System::callstack(20)]); @@ -1264,15 +1264,15 @@ class Worker // Quit if there was a database error - a precaution for the update process to 3.5.3 if (DBA::errorNo() != 0) { - return false; + return 0; } if (!$found) { - $added = DBA::insert('workerqueue', ['command' => $command, 'parameter' => $parameters, 'created' => $created, - 'priority' => $priority, 'next_try' => $delayed]); - if (!$added) { - return false; + if (!DBA::insert('workerqueue', ['command' => $command, 'parameter' => $parameters, 'created' => $created, + 'priority' => $priority, 'next_try' => $delayed])) { + return 0; } + $added = DBA::lastInsertId(); } elseif ($force_priority) { DBA::update('workerqueue', ['priority' => $priority], ['command' => $command, 'parameter' => $parameters, 'done' => false, 'pid' => 0]); } diff --git a/src/DI.php b/src/DI.php index d8e51b4398..0bfaacf89a 100644 --- a/src/DI.php +++ b/src/DI.php @@ -311,6 +311,14 @@ abstract class DI return self::$dice->create(Factory\Api\Mastodon\Status::class); } + /** + * @return Factory\Api\Mastodon\ScheduledStatus + */ + public static function mstdnScheduledStatus() + { + return self::$dice->create(Factory\Api\Mastodon\ScheduledStatus::class); + } + /** * @return Factory\Api\Mastodon\ListEntity */ diff --git a/src/Factory/Api/Mastodon/ScheduledStatus.php b/src/Factory/Api/Mastodon/ScheduledStatus.php new file mode 100644 index 0000000000..80dcdfc595 --- /dev/null +++ b/src/Factory/Api/Mastodon/ScheduledStatus.php @@ -0,0 +1,80 @@ +. + * + */ + +namespace Friendica\Factory\Api\Mastodon; + +use Friendica\BaseFactory; +use Friendica\Database\Database; +use Friendica\DI; +use Friendica\Model\ItemURI; +use Friendica\Model\Photo; +use Friendica\Model\Post; +use Friendica\Network\HTTPException; +use Psr\Log\LoggerInterface; + +class ScheduledStatus extends BaseFactory +{ + /** @var Database */ + private $dba; + + public function __construct(LoggerInterface $logger, Database $dba) + { + parent::__construct($logger); + $this->dba = $dba; + } + + /** + * @param int $id Id of the delayed post + * @param int $uid Post user + * + * @return \Friendica\Object\Api\Mastodon\ScheduledStatus + * @throws HTTPException\InternalServerErrorException + */ + public function createFromDelayedPostId(int $id, int $uid): \Friendica\Object\Api\Mastodon\ScheduledStatus + { + $delayed_post = $this->dba->selectFirst('delayed-post', [], ['id' => $id, 'uid' => $uid]); + if (empty($delayed_post)) { + throw new HTTPException\NotFoundException('Scheduled status with ID ' . $id . ' not found for user ' . $uid . '.'); + } + + $parameters = Post\Delayed::getParametersForid($delayed_post['id']); + if (empty($parameters)) { + throw new HTTPException\NotFoundException('Scheduled status with ID ' . $id . ' not found for user ' . $uid . '.'); + } + + $media_ids = []; + $media_attachments = []; + foreach ($parameters['attachments'] as $attachment) { + $id = Photo::getIdForName($attachment['url']); + + $media_ids[] = (string)$id; + $media_attachments[] = DI::mstdnAttachment()->createFromPhoto($id); + } + + if (isset($parameters['item']['thr-parent']) && ($parameters['item']['gravity'] ?? GRAVITY_PARENT != GRAVITY_PARENT)) { + $in_reply_to_id = ItemURI::getIdByURI($parameters['item']['thr-parent']); + } else { + $in_reply_to_id = null; + } + + return new \Friendica\Object\Api\Mastodon\ScheduledStatus($delayed_post, $parameters, $media_ids, $media_attachments, $in_reply_to_id); + } +} diff --git a/src/Model/Item.php b/src/Model/Item.php index 7cda359003..798886ad48 100644 --- a/src/Model/Item.php +++ b/src/Model/Item.php @@ -363,7 +363,7 @@ class Item return true; } - private static function guid($item, $notify) + public static function guid($item, $notify) { if (!empty($item['guid'])) { return Strings::escapeTags(trim($item['guid'])); diff --git a/src/Model/Photo.php b/src/Model/Photo.php index 30e6668987..26369a3540 100644 --- a/src/Model/Photo.php +++ b/src/Model/Photo.php @@ -841,13 +841,28 @@ class Photo * @throws \Exception */ public static function isLocal($name) + { + return (bool)self::getIdForName($name); + } + + /** + * Return the id of a local photo + * + * @param string $name Picture link + * @return int + */ + public static function getIdForName($name) { $data = self::getResourceData($name); if (empty($data)) { - return false; + return 0; } - return DBA::exists('photo', ['resource-id' => $data['guid'], 'scale' => $data['scale']]); + $photo = DBA::selectFirst('photo', ['id'], ['resource-id' => $data['guid'], 'scale' => $data['scale']]); + if (!empty($photo['id'])) { + return $photo['id']; + } + return 0; } /** diff --git a/src/Model/Post/Delayed.php b/src/Model/Post/Delayed.php index 2097f0f537..0bae1a8e2e 100644 --- a/src/Model/Post/Delayed.php +++ b/src/Model/Post/Delayed.php @@ -43,13 +43,13 @@ class Delayed * @param string $delayed * @param array $taglist * @param array $attachments - * @return bool insert success + * @return int ID of the created delayed post entry */ public static function add(string $uri, array $item, int $notify = 0, bool $unprepared = false, string $delayed = '', array $taglist = [], array $attachments = []) { if (empty($item['uid']) || self::exists($uri, $item['uid'])) { Logger::notice('No uid or already found'); - return false; + return 0; } if (empty($delayed)) { @@ -64,13 +64,25 @@ class Delayed Logger::notice('Adding post for delayed publishing', ['uid' => $item['uid'], 'delayed' => $delayed, 'uri' => $uri]); - if (!Worker::add(['priority' => PRIORITY_HIGH, 'delayed' => $delayed], 'DelayedPublish', $item, $notify, $taglist, $attachments, $unprepared, $uri)) { - return false; + $wid = Worker::add(['priority' => PRIORITY_HIGH, 'delayed' => $delayed], 'DelayedPublish', $item, $notify, $taglist, $attachments, $unprepared, $uri); + if (!$wid) { + return 0; } DI::pConfig()->set($item['uid'], 'system', 'last_publish', $next_publish); - return DBA::insert('delayed-post', ['uri' => $uri, 'uid' => $item['uid'], 'delayed' => $delayed], Database::INSERT_IGNORE); + $delayed_post = [ + 'uri' => $uri, + 'uid' => $item['uid'], + 'delayed' => $delayed, + 'wid' => $wid, + ]; + + if (DBA::insert('delayed-post', $delayed_post, Database::INSERT_IGNORE)) { + return DBA::lastInsertId(); + } else { + return 0; + } } /** @@ -99,6 +111,46 @@ class Delayed return DBA::exists('delayed-post', ['uri' => $uri, 'uid' => $uid]); } + /** + * Fetch parameters for delayed posts + * + * @param integer $id + * @return array + */ + public static function getParametersForid(int $id) + { + $delayed = DBA::selectFirst('delayed-post', ['id', 'uid', 'wid', 'delayed'], ['id' => $id]); + if (empty($delayed['wid'])) { + return []; + } + + $worker = DBA::selectFirst('workerqueue', ['parameter'], ['id' => $delayed['wid'], 'command' => 'DelayedPublish']); + if (empty($worker)) { + return []; + } + + $parameters = json_decode($worker['parameter'], true); + if (empty($parameters)) { + return []; + } + + // Make sure to only publish the attachments in the dedicated array field + if (empty($parameters[3]) && !empty($parameters[0]['attachments'])) { + $parameters[3] = $parameters[0]['attachments']; + unset($parameters[0]['attachments']); + } + + return [ + 'parameters' => $delayed, + 'item' => $parameters[0], + 'notify' => $parameters[1], + 'taglist' => $parameters[2], + 'attachments' => $parameters[3], + 'unprepared' => $parameters[4], + 'uri' => $parameters[5], + ]; + } + /** * Publish a delayed post * diff --git a/src/Module/Api/Mastodon/Accounts/UpdateCredentials.php b/src/Module/Api/Mastodon/Accounts/UpdateCredentials.php index d609cdaaeb..5051aec4ec 100644 --- a/src/Module/Api/Mastodon/Accounts/UpdateCredentials.php +++ b/src/Module/Api/Mastodon/Accounts/UpdateCredentials.php @@ -21,6 +21,7 @@ namespace Friendica\Module\Api\Mastodon\Accounts; +use Friendica\App\Router; use Friendica\Core\Logger; use Friendica\Module\BaseApi; use Friendica\Util\HTTPInputData; @@ -39,6 +40,6 @@ class UpdateCredentials extends BaseApi Logger::info('Patch data', ['data' => $data]); - self::unsupported('patch'); + self::unsupported(Router::PATCH); } } diff --git a/src/Module/Api/Mastodon/Filters.php b/src/Module/Api/Mastodon/Filters.php index 6c15066478..2b505e0f29 100644 --- a/src/Module/Api/Mastodon/Filters.php +++ b/src/Module/Api/Mastodon/Filters.php @@ -21,6 +21,7 @@ namespace Friendica\Module\Api\Mastodon; +use Friendica\App\Router; use Friendica\Core\System; use Friendica\Module\BaseApi; @@ -33,7 +34,7 @@ class Filters extends BaseApi { self::checkAllowedScope(self::SCOPE_WRITE); - self::unsupported('post'); + self::unsupported(Router::POST); } /** diff --git a/src/Module/Api/Mastodon/Lists/Accounts.php b/src/Module/Api/Mastodon/Lists/Accounts.php index 6e10ad6bfa..013a9f4aa9 100644 --- a/src/Module/Api/Mastodon/Lists/Accounts.php +++ b/src/Module/Api/Mastodon/Lists/Accounts.php @@ -21,6 +21,7 @@ namespace Friendica\Module\Api\Mastodon\Lists; +use Friendica\App\Router; use Friendica\Core\System; use Friendica\Database\DBA; use Friendica\DI; @@ -35,12 +36,12 @@ class Accounts extends BaseApi { public static function delete(array $parameters = []) { - self::unsupported('delete'); + self::unsupported(Router::DELETE); } public static function post(array $parameters = []) { - self::unsupported('post'); + self::unsupported(Router::POST); } /** @@ -74,7 +75,7 @@ class Accounts extends BaseApi if ($request['limit'] != 0) { $params['limit'] = min($request['limit'], 40); } - + $condition = ['gid' => $id]; if (!empty($request['max_id'])) { diff --git a/src/Module/Api/Mastodon/Markers.php b/src/Module/Api/Mastodon/Markers.php index a3a3879e07..50c0864990 100644 --- a/src/Module/Api/Mastodon/Markers.php +++ b/src/Module/Api/Mastodon/Markers.php @@ -21,6 +21,7 @@ namespace Friendica\Module\Api\Mastodon; +use Friendica\App\Router; use Friendica\Core\System; use Friendica\Module\BaseApi; @@ -33,7 +34,7 @@ class Markers extends BaseApi { self::checkAllowedScope(self::SCOPE_WRITE); - self::unsupported('post'); + self::unsupported(Router::POST); } /** diff --git a/src/Module/Api/Mastodon/ScheduledStatuses.php b/src/Module/Api/Mastodon/ScheduledStatuses.php index 28f174da18..d18e51d9d5 100644 --- a/src/Module/Api/Mastodon/ScheduledStatuses.php +++ b/src/Module/Api/Mastodon/ScheduledStatuses.php @@ -21,7 +21,10 @@ namespace Friendica\Module\Api\Mastodon; +use Friendica\App\Router; use Friendica\Core\System; +use Friendica\Database\DBA; +use Friendica\DI; use Friendica\Module\BaseApi; /** @@ -29,12 +32,87 @@ use Friendica\Module\BaseApi; */ class ScheduledStatuses extends BaseApi { + public static function put(array $parameters = []) + { + self::checkAllowedScope(self::SCOPE_WRITE); + $uid = self::getCurrentUserID(); + + self::unsupported(Router::PUT); + } + + public static function delete(array $parameters = []) + { + self::checkAllowedScope(self::SCOPE_WRITE); + $uid = self::getCurrentUserID(); + + if (empty($parameters['id'])) { + DI::mstdnError()->UnprocessableEntity(); + } + + $condtion = ['id' => $parameters['id'], 'uid' => $uid]; + $post = DBA::selectFirst('delayed-post', ['id'], $condtion); + if (empty($post['id'])) { + DI::mstdnError()->RecordNotFound(); + } + + if (!DBA::delete('delayed-post', $condtion)) { + DI::mstdnError()->RecordNotFound(); + } + + System::jsonExit([]); + } + /** * @param array $parameters * @throws \Friendica\Network\HTTPException\InternalServerErrorException */ public static function rawContent(array $parameters = []) { - System::jsonExit([]); + self::checkAllowedScope(self::SCOPE_READ); + $uid = self::getCurrentUserID(); + + if (isset($parameters['id'])) { + System::jsonExit(DI::mstdnScheduledStatus()->createFromDelayedPostId($parameters['id'], $uid)->toArray()); + } + + $request = self::getRequest([ + 'limit' => 20, // Max number of results to return. Defaults to 20. + 'max_id' => 0, // Return results older than ID + 'since_id' => 0, // Return results newer than ID + 'min_id' => 0, // Return results immediately newer than ID + ]); + + $params = ['order' => ['id' => true], 'limit' => $request['limit']]; + + $condition = ["`uid` = ? AND NOT `wid` IS NULL", $uid]; + + if (!empty($request['max_id'])) { + $condition = DBA::mergeConditions($condition, ["`uri-id` < ?", $request['max_id']]); + } + + if (!empty($request['since_id'])) { + $condition = DBA::mergeConditions($condition, ["`uri-id` > ?", $request['since_id']]); + } + + if (!empty($request['min_id'])) { + $condition = DBA::mergeConditions($condition, ["`uri-id` > ?", $request['min_id']]); + $params['order'] = ['uri-id']; + } + + $posts = DBA::select('delayed-post', ['id'], $condition, $params); + + $statuses = []; + while ($post = DBA::fetch($posts)) { + self::setBoundaries($post['id']); + $statuses[] = DI::mstdnScheduledStatus()->createFromDelayedPostId($post['id'], $uid); + } + DBA::close($posts); + + if (!empty($request['min_id'])) { + array_reverse($statuses); + } + + self::setLinkHeader(); + System::jsonExit($statuses); } } diff --git a/src/Module/Api/Mastodon/Statuses.php b/src/Module/Api/Mastodon/Statuses.php index 1007bea0be..cd6bbda3f8 100644 --- a/src/Module/Api/Mastodon/Statuses.php +++ b/src/Module/Api/Mastodon/Statuses.php @@ -104,7 +104,7 @@ class Statuses extends BaseApi $item['deny_gid'] = $owner['deny_gid']; } else { $item['allow_cid'] = ''; - $item['allow_gid'] = [Group::FOLLOWERS]; + $item['allow_gid'] = '<' . Group::FOLLOWERS . '>'; $item['deny_cid'] = ''; $item['deny_gid'] = ''; } @@ -183,6 +183,16 @@ class Statuses extends BaseApi } } + if (!empty($request['scheduled_at'])) { + $item['guid'] = Item::guid($item, true); + $item['uri'] = Item::newURI($item['uid'], $item['guid']); + $id = Post\Delayed::add($item['uri'], $item, PRIORITY_HIGH, false, $request['scheduled_at']); + if (empty($id)) { + DI::mstdnError()->InternalError(); + } + System::jsonExit(DI::mstdnScheduledStatus()->createFromDelayedPostId($id, $uid)->toArray()); + } + $id = Item::insert($item, true); if (!empty($id)) { $item = Post::selectFirst(['uri-id'], ['id' => $id]); diff --git a/src/Module/Api/Mastodon/Unimplemented.php b/src/Module/Api/Mastodon/Unimplemented.php index fa9618818a..035b6d2c95 100644 --- a/src/Module/Api/Mastodon/Unimplemented.php +++ b/src/Module/Api/Mastodon/Unimplemented.php @@ -21,6 +21,7 @@ namespace Friendica\Module\Api\Mastodon; +use Friendica\App\Router; use Friendica\Module\BaseApi; /** @@ -34,7 +35,7 @@ class Unimplemented extends BaseApi */ public static function delete(array $parameters = []) { - self::unsupported('delete'); + self::unsupported(Router::DELETE); } /** @@ -43,7 +44,7 @@ class Unimplemented extends BaseApi */ public static function patch(array $parameters = []) { - self::unsupported('patch'); + self::unsupported(Router::PATCH); } /** @@ -52,7 +53,7 @@ class Unimplemented extends BaseApi */ public static function post(array $parameters = []) { - self::unsupported('post'); + self::unsupported(Router::POST); } /** @@ -61,7 +62,7 @@ class Unimplemented extends BaseApi */ public static function put(array $parameters = []) { - self::unsupported('put'); + self::unsupported(Router::PUT); } /** @@ -70,6 +71,6 @@ class Unimplemented extends BaseApi */ public static function rawContent(array $parameters = []) { - self::unsupported('get'); + self::unsupported(Router::GET); } } diff --git a/src/Object/Api/Mastodon/ScheduledStatus.php b/src/Object/Api/Mastodon/ScheduledStatus.php new file mode 100644 index 0000000000..4df92cc367 --- /dev/null +++ b/src/Object/Api/Mastodon/ScheduledStatus.php @@ -0,0 +1,84 @@ +. + * + */ + +namespace Friendica\Object\Api\Mastodon; + +use Friendica\BaseDataTransferObject; +use Friendica\Content\Text\BBCode; +use Friendica\Util\DateTimeFormat; + +/** + * Class ScheduledStatus + * + * @see https://docs.joinmastodon.org/entities/scheduledstatus + */ +class ScheduledStatus extends BaseDataTransferObject +{ + /** @var string */ + protected $id; + /** @var string (Datetime) */ + protected $scheduled_at; + /** @var array */ + protected $params = [ + 'text' => '', + 'media_ids' => null, + 'sensitive' => null, + 'spoiler_text' => null, + 'visibility' => '', + 'scheduled_at' => null, + 'poll' => null, + 'idempotency' => null, + 'in_reply_to_id' => null, + 'application_id' => '' + ]; + /** @var Attachment */ + protected $media_attachments = []; + + /** + * Creates a status record from a delayed-post record. + * + * @param array $delayed_post Record with the delayed post + * @param array $parameters Parameters for the workerqueue entry for the delayed post + * @throws \Friendica\Network\HTTPException\InternalServerErrorException + */ + public function __construct(array $delayed_post, array $parameters, array $media_ids = null, array $media_attachments = [], int $in_reply_to_id = null) + { + $visibility = ['public', 'private', 'unlisted']; + + $this->id = (string)$delayed_post['id']; + $this->scheduled_at = DateTimeFormat::utc($delayed_post['delayed'], DateTimeFormat::JSON); + + $this->params = [ + 'text' => BBCode::convert(BBCode::setMentionsToNicknames($parameters['item']['body'] ?? ''), false, BBCode::API), + 'media_ids' => $media_ids, + 'sensitive' => null, + 'spoiler_text' => $parameters['item']['title'] ?? '', + 'visibility' => $visibility[$parameters['item']['private']], + 'scheduled_at' => $this->scheduled_at, + 'poll' => null, + 'idempotency' => null, + 'in_reply_to_id' => $in_reply_to_id, + 'application_id' => '' + ]; + + $this->media_attachments = $media_attachments; + } +} diff --git a/static/dbstructure.config.php b/static/dbstructure.config.php index ded539af50..d1cc42033a 100644 --- a/static/dbstructure.config.php +++ b/static/dbstructure.config.php @@ -55,7 +55,7 @@ use Friendica\Database\DBA; if (!defined('DB_UPDATE_VERSION')) { - define('DB_UPDATE_VERSION', 1430); + define('DB_UPDATE_VERSION', 1431); } return [ @@ -554,6 +554,32 @@ return [ "received" => ["received"], ] ], + "workerqueue" => [ + "comment" => "Background tasks queue entries", + "fields" => [ + "id" => ["type" => "int unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1", "comment" => "Auto incremented worker task id"], + "command" => ["type" => "varchar(100)", "comment" => "Task command"], + "parameter" => ["type" => "mediumtext", "comment" => "Task parameter"], + "priority" => ["type" => "tinyint unsigned", "not null" => "1", "default" => "0", "comment" => "Task priority"], + "created" => ["type" => "datetime", "not null" => "1", "default" => DBA::NULL_DATETIME, "comment" => "Creation date"], + "pid" => ["type" => "int unsigned", "not null" => "1", "default" => "0", "comment" => "Process id of the worker"], + "executed" => ["type" => "datetime", "not null" => "1", "default" => DBA::NULL_DATETIME, "comment" => "Execution date"], + "next_try" => ["type" => "datetime", "not null" => "1", "default" => DBA::NULL_DATETIME, "comment" => "Next retrial date"], + "retrial" => ["type" => "tinyint", "not null" => "1", "default" => "0", "comment" => "Retrial counter"], + "done" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "Marked 1 when the task was done - will be deleted later"], + ], + "indexes" => [ + "PRIMARY" => ["id"], + "command" => ["command"], + "done_command_parameter" => ["done", "command", "parameter(64)"], + "done_executed" => ["done", "executed"], + "done_priority_retrial_created" => ["done", "priority", "retrial", "created"], + "done_priority_next_try" => ["done", "priority", "next_try"], + "done_pid_next_try" => ["done", "pid", "next_try"], + "done_pid_retrial" => ["done", "pid", "retrial"], + "done_pid_priority_created" => ["done", "pid", "priority", "created"] + ] + ], "delayed-post" => [ "comment" => "Posts that are about to be distributed at a later time", "fields" => [ @@ -561,10 +587,12 @@ return [ "uri" => ["type" => "varchar(255)", "comment" => "URI of the post that will be distributed later"], "uid" => ["type" => "mediumint unsigned", "foreign" => ["user" => "uid"], "comment" => "Owner User id"], "delayed" => ["type" => "datetime", "comment" => "delay time"], + "wid" => ["type" => "int unsigned", "foreign" => ["workerqueue" => "id"], "comment" => "Workerqueue id"], ], "indexes" => [ "PRIMARY" => ["id"], "uid_uri" => ["UNIQUE", "uid", "uri(190)"], + "wid" => ["wid"], ] ], "diaspora-interaction" => [ @@ -1496,30 +1524,4 @@ return [ ], "engine" => "MEMORY", ], - "workerqueue" => [ - "comment" => "Background tasks queue entries", - "fields" => [ - "id" => ["type" => "int unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1", "comment" => "Auto incremented worker task id"], - "command" => ["type" => "varchar(100)", "comment" => "Task command"], - "parameter" => ["type" => "mediumtext", "comment" => "Task parameter"], - "priority" => ["type" => "tinyint unsigned", "not null" => "1", "default" => "0", "comment" => "Task priority"], - "created" => ["type" => "datetime", "not null" => "1", "default" => DBA::NULL_DATETIME, "comment" => "Creation date"], - "pid" => ["type" => "int unsigned", "not null" => "1", "default" => "0", "comment" => "Process id of the worker"], - "executed" => ["type" => "datetime", "not null" => "1", "default" => DBA::NULL_DATETIME, "comment" => "Execution date"], - "next_try" => ["type" => "datetime", "not null" => "1", "default" => DBA::NULL_DATETIME, "comment" => "Next retrial date"], - "retrial" => ["type" => "tinyint", "not null" => "1", "default" => "0", "comment" => "Retrial counter"], - "done" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "Marked 1 when the task was done - will be deleted later"], - ], - "indexes" => [ - "PRIMARY" => ["id"], - "command" => ["command"], - "done_command_parameter" => ["done", "command", "parameter(64)"], - "done_executed" => ["done", "executed"], - "done_priority_retrial_created" => ["done", "priority", "retrial", "created"], - "done_priority_next_try" => ["done", "priority", "next_try"], - "done_pid_next_try" => ["done", "pid", "next_try"], - "done_pid_retrial" => ["done", "pid", "retrial"], - "done_pid_priority_created" => ["done", "pid", "priority", "created"] - ] - ], ]; diff --git a/static/routes.config.php b/static/routes.config.php index 74cc59df81..40739de7e2 100644 --- a/static/routes.config.php +++ b/static/routes.config.php @@ -127,8 +127,8 @@ return [ '/preferences' => [Module\Api\Mastodon\Preferences::class, [R::GET ]], '/push/subscription' => [Module\Api\Mastodon\Unimplemented::class, [R::GET, R::POST, R::PUT, R::DELETE]], // not supported '/reports' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], // not supported - '/scheduled_statuses' => [Module\Api\Mastodon\ScheduledStatuses::class, [R::GET ]], // Dummy, not supported - '/scheduled_statuses/{id:\d+}' => [Module\Api\Mastodon\Unimplemented::class, [R::GET, R::PUT, R::DELETE]], // not supported + '/scheduled_statuses' => [Module\Api\Mastodon\ScheduledStatuses::class, [R::GET ]], + '/scheduled_statuses/{id:\d+}' => [Module\Api\Mastodon\ScheduledStatuses::class, [R::GET, R::PUT, R::DELETE]], '/statuses' => [Module\Api\Mastodon\Statuses::class, [ R::POST]], '/statuses/{id:\d+}' => [Module\Api\Mastodon\Statuses::class, [R::GET, R::DELETE]], '/statuses/{id:\d+}/card' => [Module\Api\Mastodon\Statuses\Card::class, [R::GET ]],