* 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); } }