From a7a5cc81805a77e3a28bff41352ae5dde71ebf6a Mon Sep 17 00:00:00 2001
From: rr- <rr-@sakuya.pl>
Date: Mon, 26 Dec 2016 14:21:26 +0100
Subject: [PATCH] server/posts: expose reverse image search

---
 API.md                            | 52 +++++++++++++++++++++++++++++++
 config.yaml.dist                  |  1 +
 server/szurubooru/api/post_api.py | 15 +++++++++
 3 files changed, 68 insertions(+)

diff --git a/API.md b/API.md
index 33139d59..ad626e5b 100644
--- a/API.md
+++ b/API.md
@@ -42,6 +42,7 @@
         - [Removing post from favorites](#removing-post-from-favorites)
         - [Getting featured post](#getting-featured-post)
         - [Featuring post](#featuring-post)
+        - [Reverse image search](#reverse-image-search)
     - Comments
         - [Listing comments](#listing-comments)
         - [Creating comment](#creating-comment)
@@ -76,6 +77,7 @@
    - [Snapshot](#snapshot)
    - [Unpaged search result](#unpaged-search-result)
    - [Paged search result](#paged-search-result)
+   - [Image search result](#image-search-result)
 
 4. [Search](#search)
 
@@ -1057,6 +1059,28 @@ data.
 
     Features a post on the main page in web client.
 
+## Reverse image search
+- **Request**
+
+    `POST /posts/reverse-search`
+
+- **Files**
+
+    - `content` - the image to search for.
+
+- **Output**
+
+    A list of [image search results](#image-search-result).
+
+- **Errors**
+
+    - privileges are too low
+
+- **Description**
+
+    Retrieves posts that look like the input image. Works only on images and
+    animations, i.e. does not work for videos and Flash movies.
+
 ## Listing comments
 - **Request**
 
@@ -2118,6 +2142,34 @@ A result of search operation that involves paging.
   details on this field, check the documentation for given API call.
 
 
+## Image search result
+**Description**
+
+A result of reverse image search operation.
+
+**Structure**
+
+```json5
+{
+    "results": [
+        {
+            "dist": <distance>,
+            "post": <post>
+        },
+        {
+            "dist": <distance>,
+            "post": <post>
+        },
+        ...
+    ]
+}
+```
+
+**Field meaning**
+- `<dist>`: distance from the original image (0..1). The lower this value is, the more similar the
+post is.
+- `<post>`: a [post resource](#post).
+
 # Search
 
 Search queries are built of tokens that are separated by spaces. Each token can
diff --git a/config.yaml.dist b/config.yaml.dist
index e59ab4fb..90c48752 100644
--- a/config.yaml.dist
+++ b/config.yaml.dist
@@ -73,6 +73,7 @@ privileges:
     'posts:create:anonymous':       regular
     'posts:create:identified':      regular
     'posts:list':                   anonymous
+    'posts:reverse_search':         regular
     'posts:view':                   anonymous
     'posts:edit:content':           power
     'posts:edit:flags':             regular
diff --git a/server/szurubooru/api/post_api.py b/server/szurubooru/api/post_api.py
index 142cca87..abc832e5 100644
--- a/server/szurubooru/api/post_api.py
+++ b/server/szurubooru/api/post_api.py
@@ -205,3 +205,18 @@ def get_posts_around(ctx, params):
     _search_executor.config.user = ctx.user
     return _search_executor.get_around_and_serialize(
         ctx, params['post_id'], lambda post: _serialize_post(ctx, post))
+
+
+@routes.post('/posts/reverse-search/?')
+def get_posts_by_image(ctx, _params=None):
+    auth.verify_privilege(ctx.user, 'posts:reverse_search')
+    content = ctx.get_file('content', required=True)
+    return {
+        'results': [
+            {
+                'dist': item['dist'],
+                'post': _serialize_post(ctx, item['post']),
+            }
+            for item in posts.search_by_image(content)
+        ],
+    }