diff --git a/ext/ouroboros_api/main.php b/ext/ouroboros_api/main.php new file mode 100644 index 00000000..a2413e42 --- /dev/null +++ b/ext/ouroboros_api/main.php @@ -0,0 +1,391 @@ + + * Description: Ouroboros-like API for Shimmie + * Documentation: + * Currently working features + * + * Tested to work with CartonBox using "Danbooru 1.18.x" as site type. + * Does not work with Andbooru or Danbooru Gallery for reasons beyond me, took me a while to figure rating "u" is bad... + * Lots of Ouroboros/Danbooru specific values use their defaults (or what I gathered them to be default) + * and tons of stuff not supported directly in Shimmie is botched to work + */ + + +class _SafeOuroborosImage +{ + /** + * Author + */ + + /** + * Post author + * @var string + */ + public $author = ''; + /** + * Post author user ID + * @var integer + */ + public $creator_id = null; + + /** + * Image + */ + + /** + * Image height + * @var integer + */ + public $height = null; + /** + * Image width + * @var integer + */ + public $width = null; + /** + * File Size in bytes + * @var integer + */ + public $file_size = null; + /** + * URL to the static file + * @var string + */ + public $file_url = ''; + /** + * File MD5 hash + * @var string + */ + public $md5 = ''; + + /** + * Post Meta + */ + + /** + * (Unknown) Change + * @var integer + */ + public $change = null; + /** + * Timestamp for post creation + * @var integer + */ + public $created_at = null; + /** + * Post ID + * @var integer + */ + public $id = null; + /** + * Parent post ID + * @var integer + */ + public $parent_id = null; + /** + * Post content rating + * @var string + */ + public $rating = 'q'; + /** + * Post score + * @var integer + */ + public $score = 1; + /** + * Post source + * @var string + */ + public $source = ''; + /** + * Post status + * @var string + */ + public $status = ''; + /** + * Post tags + * @var string + */ + public $tags = ''; + /** + * Flag if the post has child posts + * @var bool + */ + public $has_children = false; + /** + * Flag if the post has comments + * @var bool + */ + public $has_comments = false; + /** + * Flag if the post has notes + * @var bool + */ + public $has_notes = false; + /** + * Post description + * @var string + */ + public $description = ''; + + /** + * Thumbnail + */ + + /** + * Thumbnail Height + * @var integer + */ + public $preview_height = null; + /** + * Thumbnail URL + * @var string + */ + public $preview_url = ''; + /** + * Thumbnail Width + * @var integer + */ + public $preview_width = null; + + /** + * Downscaled Image + */ + + /** + * Downscaled image height + * @var integer + */ + public $sample_height = null; + /** + * Downscaled image + * @var string + */ + public $sample_url = ''; + /** + * Downscaled image + * @var integer + */ + public $sample_width = null; + + /** + * Constructor + * @param Image $img + */ + function __construct(Image $img) + { + global $config; + // author + $author = $img->get_owner(); + $this->author = $author->name; + $this->creator_id = intval($author->id); + + // file + $this->height = intval($img->height); + $this->width = intval($img->width); + $this->file_ext = $img->ext; + $this->file_size = intval($img->filesize); + $this->file_url = make_http($img->get_image_link()); + $this->md5 = $img->hash; + + // meta + $this->change = intval($img->id); //DaFug is this even supposed to do? ChangeID? + // Should be JSON specific, just strip this when converting to XML + $this->created_at = array('n' => 123456789, 's' => $img->posted_timestamp, 'json_class' => 'Time'); + $this->id = intval($img->id); + $this->parent_id = null; + if (defined('ENABLED_EXTS')) { + if (strstr(ENABLED_EXTS, 'rating') !== false) { + //$this->rating = $img->rating; + } + if (strstr(ENABLED_EXTS, 'numeric_score') !== false) { + $this->score = $img->numeric_score; + } + } + $this->source = $img->source; + $this->status = 'active'; //not supported in Shimmie... yet + $this->tags = $img->get_tag_list(); + $this->has_children = false; + $this->has_comments = false; + $this->has_notes = false; + + // thumb + $this->preview_height = $config->get_int('thumb_height'); + $this->preview_width = $config->get_int('thumb_width'); + $this->preview_url = make_http($img->get_thumb_link()); + + // sample (use the full image here) + $this->sample_height = intval($img->height); + $this->sample_width = intval($img->width); + $this->sample_url = make_http($img->get_image_link()); + } +} +class _SafeOuroborosTag +{ + public $ambiguous = false; + public $count = 0; + public $id = 0; + public $name = ''; + public $type = 0; + + function __construct(array $tag) + { + $this->count = $tag['count']; + $this->id = $tag['id']; + $this->name = $tag['tag']; + } +} +class OuroborosAPI extends Extension +{ + private $event; + const ERROR_HTTP_200 = 'Request was successful'; + const ERROR_HTTP_403 = 'Access denied'; + const ERROR_HTTP_404 = 'Not found'; + const ERROR_HTTP_420 = 'Record could not be saved'; + const ERROR_HTTP_421 = 'User is throttled, try again later'; + const ERROR_HTTP_422 = 'The resource is locked and cannot be modified'; + const ERROR_HTTP_423 = 'Resource already exists'; + const ERROR_HTTP_424 = 'The given parameters were invalid'; + const ERROR_HTTP_500 = 'Some unknown error occurred on the server'; + const ERROR_HTTP_503 = 'Server cannot currently handle the request, try again later'; + + const ERROR_POST_CREATE_MD5 = 'MD5 mismatch'; + const ERROR_POST_CREATE_DUPE = 'Duplicate'; + + public function onPageRequest(PageRequestEvent $event) + { + global $database, $page, $config, $user; + + if (preg_match("%\.(xml|json)$%", implode('/', $event->args), $matches) === 1) { + $this->event = $event; + $type = $matches[1]; + if ($type == 'json') { + $page->set_type('application/json; charset=utf-8'); + } + elseif ($type == 'xml') { + $page->set_type('text/xml'); + } + $page->set_mode('data'); + + if ($event->page_matches('post')) { + if ($this->match('create')) { + // Create + $post = array( + 'tags' => !empty($_REQUEST['post']['tags']) ? filter_var($_REQUEST['post']['tags'], FILTER_SANITIZE_STRING) : 'tagme', + 'file' => !empty($_REQUEST['post']['file']) ? filter_var($_REQUEST['post']['file'], FILTER_UNSAFE_RAW) : null, + 'rating' => !empty($_REQUEST['post']['rating']) ? filter_var($_REQUEST['post']['rating'], FILTER_SANITIZE_NUMBER_INT) : null, + 'source' => !empty($_REQUEST['post']['source']) ? filter_var($_REQUEST['post']['source'], FILTER_SANITIZE_URL) : null, + 'sourceurl' => !empty($_REQUEST['post']['sourceurl']) ? filter_var($_REQUEST['post']['sourceurl'], FILTER_SANITIZE_URL) : '', + 'description' => !empty($_REQUEST['post']['description']) ? filter_var($_REQUEST['post']['description'], FILTER_SANITIZE_STRING) : '', + 'is_rating_locked' => !empty($_REQUEST['post']['is_rating_locked']) ? filter_var($_REQUEST['post']['is_rating_locked'], FILTER_SANITIZE_NUMBER_INT) : false, + 'is_note_locked' => !empty($_REQUEST['post']['is_note_locked']) ? filter_var($_REQUEST['post']['is_note_locked'], FILTER_SANITIZE_NUMBER_INT) : false, + 'parent_id' => !empty($_REQUEST['post']['parent_id']) ? filter_var($_REQUEST['post']['parent_id'], FILTER_SANITIZE_NUMBER_INT) : null, + ); + $md5 = !empty($_REQUEST['md5']) ? filter_var($_REQUEST['md5'], FILTER_SANITIZE_STRING) : null; + + } + elseif ($this->match('update')) { + // Update + } + elseif ($this->match('show')) { + // Show + if (isset($_REQUEST['id'])) { + $id = $_REQUEST['id']; + $posts = array(); + $posts[] = new _SafeOuroborosImage(Image::by_id($id)); + $page->set_data(json_encode($posts)); + } + else { + $page->set_data(json_encode(array())); + } + } + elseif ($this->match('index') || $this->match('list')) { + // List + $limit = !empty($_REQUEST['limit']) ? intval(filter_var($_REQUEST['limit'], FILTER_SANITIZE_NUMBER_INT)) : 45; + $p = !empty($_REQUEST['page']) ? intval(filter_var($_REQUEST['page'], FILTER_SANITIZE_NUMBER_INT)) : 1; + $tags = !empty($_REQUEST['tags']) ? filter_var($_REQUEST['tags'], FILTER_SANITIZE_STRING) : array(); + if (!empty($tags)) { + $tags = Tag::explode($tags); + } + $start = ( $p - 1 ) * $limit; + //var_dump($limit, $p, $tags, $start);die(); + $results = Image::find_images(max($start, 0), min($limit, 100), $tags); + $posts = array(); + foreach ($results as $img) { + if (!is_object($img)) { + continue; + } + $posts[] = new _SafeOuroborosImage($img); + } + $page->set_data(json_encode($posts)); + } + } + elseif ($event->page_matches('tag')) { + if ($this->match('index') || $this->match('list')) { + $limit = !empty($_REQUEST['limit']) ? intval(filter_var($_REQUEST['limit'], FILTER_SANITIZE_NUMBER_INT)) : 50; + $p = !empty($_REQUEST['page']) ? intval(filter_var($_REQUEST['page'], FILTER_SANITIZE_NUMBER_INT)) : 1; + $order = (!empty($_REQUEST['order']) && ($_REQUEST['order'] == 'date' || $_REQUEST['order'] == 'count' || $_REQUEST['order'] == 'name')) ? filter_var($_REQUEST['order'], FILTER_SANITIZE_STRING) : 'date'; + $id = !empty($_REQUEST['id']) ? intval(filter_var($_REQUEST['id'], FILTER_SANITIZE_NUMBER_INT)) : null; + $after_id = !empty($_REQUEST['after_id']) ? intval(filter_var($_REQUEST['after_id'], FILTER_SANITIZE_NUMBER_INT)) : null; + $name = !empty($_REQUEST['name']) ? filter_var($_REQUEST['name'], FILTER_SANITIZE_STRING) : ''; + $name_pattern = !empty($_REQUEST['name_pattern']) ? filter_var($_REQUEST['name_pattern'], FILTER_SANITIZE_STRING) : ''; + $start = ( $p - 1 ) * $limit; + $tag_data = array(); + switch ($order) { + case 'name': + $tag_data = $database->get_col($database->scoreql_to_sql(" + SELECT DISTINCT + id, SCORE_STRNORM(substr(tag, 1, 1)), count + FROM tags + WHERE count >= :tags_min + ORDER BY SCORE_STRNORM(substr(tag, 1, 1)) LIMIT :start, :max_items + "), array("tags_min" => $config->get_int('tags_min'), 'start' => $start, 'max_items' => $limit)); + break; + case 'count': + $tag_data = $database->get_all(" + SELECT id, tag, count + FROM tags + WHERE count >= :tags_min + ORDER BY count DESC, tag ASC LIMIT :start, :max_items + ", array("tags_min" => $config->get_int('tags_min'), 'start' => $start, 'max_items' => $limit)); + break; + case 'date': + $tag_data = $database->get_all(" + SELECT id, tag, count + FROM tags + WHERE count >= :tags_min + ORDER BY count DESC, tag ASC LIMIT :start, :max_items + ", array("tags_min" => $config->get_int('tags_min'), 'start' => $start, 'max_items' => $limit)); + break; + } + $tags = array(); + foreach ($tag_data as $tag) { + if (!is_array($tag)) { + continue; + } + $tags[] = new _SafeOuroborosTag($tag); + } + $page->set_data(json_encode($tags)); + } + } + } + } + + private function match($page) { + return (preg_match("%{$page}\.(xml|json)$%", implode('/', $this->event->args), $matches) === 1); + } +}