diff --git a/weasyl/search.py b/weasyl/search.py index 396620546..624f7b12e 100644 --- a/weasyl/search.py +++ b/weasyl/search.py @@ -41,8 +41,9 @@ def __init__(self): self.required_user_excludes = set() self.ratings = set() self.find = None + self.sha256: str | None = None - def add_criterion(self, criterion): + def add_criterion(self, criterion: str): def add_nonempty(s, item): if item: s.add(item) @@ -65,6 +66,9 @@ def add_nonempty(s, item): elif criterion.startswith("-user:"): user = d.get_sysname(criterion.split(":", 1)[1]) add_nonempty(self.required_user_excludes, user) + elif criterion.startswith("sha256:"): + sha256 = criterion.split(":", 1)[1] + self.sha256 = sha256 or None elif criterion.startswith("+"): tag = d.get_search_tag(criterion[1:]) add_nonempty(self.required_includes, tag) @@ -85,6 +89,7 @@ def __bool__(self): self.required_excludes or self.required_user_includes or self.required_user_excludes or + self.sha256 or self.ratings) @classmethod @@ -135,7 +140,7 @@ def select_users(q): def _find_without_media(userid, rating, limit, - search, within, cat, subcat, backid, nextid): + search: Query, within, cat, subcat, backid, nextid): type_code, type_letter, table, select, subtype = _TABLE_INFORMATION[search.find] # Begin statement @@ -163,6 +168,12 @@ def _find_without_media(userid, rating, limit, elif cat: statement_where.append("AND content.subtype >= %(category)s AND content.subtype < %(category)s + 1000") + if search.find == "submit" and search.sha256: + statement_from.append("INNER JOIN submission_media_links ON submission_media_links.submitid = content.{select}") + statement_from.append("INNER JOIN media ON media.mediaid = submission_media_links.mediaid") + statement_where.append("AND submission_media_links.link_type = 'submission'") + statement_where.append("AND media.sha256 = %(sha256)s") + if userid: if within == "notify": # Search within notifications @@ -265,6 +276,7 @@ def make_statement(statement_select, statement_additional_where, statement_order ]).format( table=table, find=search.find, + sha256=search.sha256, select=select, subtype=subtype, title_field="char_name" if search.find == "char" else "title", @@ -302,6 +314,7 @@ def get_ids(names): "required_excludes": get_ids(search.required_excludes), "required_user_includes": list(search.required_user_includes), "required_user_excludes": list(search.required_user_excludes), + "sha256": search.sha256, "type": type_letter, "userid": userid, "rating": rating, diff --git a/weasyl/templates/help/searching.html b/weasyl/templates/help/searching.html index 62fbebb4f..2ac7bfafe 100644 --- a/weasyl/templates/help/searching.html +++ b/weasyl/templates/help/searching.html @@ -46,6 +46,15 @@

Search modifiers (can only be used with at least one search term above)

Limits results to the content level specified. Use as many of these per search as you wish. + +

Advanced search types

+ + + + + + +
sha256:8c474ca2ebfa54b1439a5c6f025f58ab443e6e8ba8a0ed885046d6375fbf4e8a
Search for a submission whose media matches the given SHA-256 hash. Given a piece of artwork, this can be used to find the original submission on Weasyl, in a process known as reverse image search.
diff --git a/weasyl/test/test_search.py b/weasyl/test/test_search.py index 54a23d386..2576fb4ed 100644 --- a/weasyl/test/test_search.py +++ b/weasyl/test/test_search.py @@ -3,7 +3,7 @@ import pytest from libweasyl.models.helpers import CharSettings -from libweasyl import ratings +from libweasyl import media, ratings from weasyl.test import db_utils from weasyl import search @@ -217,3 +217,50 @@ def _select(userid: int): assert len(_select(user1_id)) == 1 assert len(_select(user1_id)) == 1 + + +def test_search_sha256(db): + userid = db_utils.create_user() + tagid = db_utils.create_tag('test') + + def _select(term: str): + results, _, _ = search.select( + search=search.Query.parse(term, 'submit'), + userid=userid, rating=ratings.EXPLICIT.code, limit=100, + cat=None, subcat=None, within='', backid=None, nextid=None, + ) + + return results + + submitid_foo = db_utils.create_submission(userid, 'Foo') + db_utils.create_submission_tag(tagid, submitid_foo) + with open('libweasyl/test/data/1x70.jpg', 'rb') as file_foo: + data_foo = file_foo.read() + media_foo = media.MediaItem.fetch_or_create(data_foo, file_type='jpg') + media.SubmissionMediaLink.make_or_replace_link(submitid_foo, 'submission', media_foo) + + submitid_bar = db_utils.create_submission(userid, 'Bar') + db_utils.create_submission_tag(tagid, submitid_bar) + with open('libweasyl/test/data/2x233.gif', 'rb') as file_bar: + data_bar = file_bar.read() + media_bar = media.MediaItem.fetch_or_create(data_bar, file_type='gif') + media.SubmissionMediaLink.make_or_replace_link(submitid_bar, 'submission', media_bar) + + submitid_baz = db_utils.create_submission(userid, 'Baz') + db_utils.create_submission_tag(tagid, submitid_baz) + with open('libweasyl/test/data/2x233.gif', 'rb') as file_baz: + data_baz = file_baz.read() + media_baz = media.MediaItem.fetch_or_create(data_baz, file_type='gif') + media.SubmissionMediaLink.make_or_replace_link(submitid_baz, 'submission', media_baz) + + results_foo = _select('sha256:a4e6ff301f671e8afb2397b4967c6a7004657447a5877a08d2dc29af58fccf3b') + results_bar = _select('sha256:fa6bd1dacf7b3d351172051826bb6d123113cfb7314322732a3ee1a917531672') + results_unknown = _select('sha256:0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f') + + assert len(results_foo) == 1 + assert results_foo[0]['title'] == 'Foo' + + assert len(results_bar) == 2 + assert sorted(map(lambda x: x['title'], results_bar)) == ['Bar', 'Baz'] + + assert len(results_unknown) == 0