mirror of
https://github.com/searxng/searxng.git
synced 2026-09-23 14:46:04 +00:00
Engines like Pexels provide images that one would more likely expect in a category named ``stock_images``. By classifying engines like Pexels more precisely, we enable a more specific handling of them, which also helps to avoid mixing them with the more general image search on the internet. [1] We leave these engines *enabled* by default, take them out of ``images`` and move them into the specialization ``stock_images``. This makes it possible, on the one hand, for the admin to configure a tab in the UI, and on the other hand, the user can always select the group directly using the search syntax ``!stock_images ...``. What we need to keep in mind: 1. Technically speaking, there are no subcategories in the strict sense. Organizationally, the *subcategories* are derived from the `categories_as_tabs`. 2. From an admin’s perspective, `categories_as_tabs` is the tool for structuring their content. 3. In the context of “enabled/disabled by default”: We should avoid having the admin have to activate many individual engines when they want to structure their content (UI tabs). So if we leave an engine enabled by default and **move** it from `images` to a new category, the admin only needs to add the new category to `categories_as_tabs` to restructure their content. If the new category is also part of the translations (see `searx/searxng.msg` in this PR), this UI structure would also be available internationally. [1] https://github.com/searxng/searxng/pull/6690#issuecomment-5646291679 Signed-off-by: Markus Heiser <markus.heiser@darmarit.de>
153 lines
3.7 KiB
Python
153 lines
3.7 KiB
Python
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
# pylint: disable=invalid-name
|
|
"""500px_ is a online network for photographers with millions of members
|
|
worldwide. Photographers come to 500px to discover and share incredible photos,
|
|
gain meaningful exposure, compete in photo contests, and license their photos
|
|
through our exclusive distribution partners.
|
|
|
|
.. _500px: https://500px.com
|
|
|
|
"""
|
|
|
|
import typing as t
|
|
|
|
import codecs
|
|
|
|
from searx.result_types import EngineResults
|
|
|
|
if t.TYPE_CHECKING:
|
|
from searx.extended_types import SXNG_Response
|
|
from searx.search.processors import OnlineParams
|
|
|
|
|
|
# about
|
|
about = {
|
|
"website": "https://500px.com",
|
|
"wikidata_id": "Q354894",
|
|
"official_api_documentation": None,
|
|
"use_official_api": False,
|
|
"require_api_key": False,
|
|
"results": "JSON",
|
|
}
|
|
|
|
base_url = "https://500px.com"
|
|
api_url = "https://api-neo.500px.com"
|
|
|
|
categories = ["stock_images"]
|
|
paging = True
|
|
|
|
results_per_page = 30
|
|
"""Number of results to return in the request.
|
|
|
|
The default was taken from the WEB UI, where the GraphQL query sets the value to
|
|
*static*: ``first: 30``.
|
|
"""
|
|
|
|
|
|
SXNG_query = """
|
|
query searchResource(
|
|
$keyword: String!
|
|
$type: SearchType = TEXT
|
|
$first: Int!
|
|
$after: String
|
|
$resourceTypes: [SearchResourceType!]
|
|
$categories: [String!]
|
|
$equipments: [String!]
|
|
$downloadable: PhotoDownloadable
|
|
$sort: SearchSortOption = RELEVANCE
|
|
$excludeNsfw: Boolean
|
|
) {
|
|
searchResource(
|
|
keyword: $keyword
|
|
type: $type
|
|
first: $first
|
|
after: $after
|
|
resourceTypes: $resourceTypes
|
|
categories: $categories
|
|
equipments: $equipments
|
|
downloadable: $downloadable
|
|
sort: $sort
|
|
excludeNsfw: $excludeNsfw
|
|
) {
|
|
edges {
|
|
cursor
|
|
node {
|
|
__typename
|
|
... on Photo {
|
|
id
|
|
title
|
|
description
|
|
licensing {
|
|
status
|
|
__typename
|
|
}
|
|
urls {
|
|
size_600
|
|
size_1024
|
|
size_2048
|
|
size_4k
|
|
__typename
|
|
}
|
|
uploader {
|
|
displayName
|
|
__typename
|
|
}
|
|
isNsfw
|
|
width
|
|
height
|
|
dominantColorLight
|
|
dominantColorDark
|
|
uploadedAt
|
|
__typename
|
|
}
|
|
}
|
|
__typename
|
|
}
|
|
__typename
|
|
}
|
|
}
|
|
"""
|
|
|
|
|
|
def request(query: str, params: "OnlineParams") -> None:
|
|
# cursor is the base64 hash of the string "pos-<offset-1>", e.g. "pos-29" -> "cG9zLTI5"
|
|
offset = ((params["pageno"] - 1) * results_per_page) - 1
|
|
cursor = codecs.encode(f"pos-{offset}".encode("utf-8"), "base64").decode("utf-8")
|
|
|
|
params["url"] = f"{api_url}/graphql"
|
|
params["method"] = "POST"
|
|
params["json"] = {
|
|
"operationName": "searchResource",
|
|
"variables": {
|
|
"after": cursor,
|
|
"first": results_per_page,
|
|
"keyword": query,
|
|
"resourceTypes": ["PHOTO"],
|
|
"sort": "RELEVANCE",
|
|
"type": "TEXT",
|
|
},
|
|
"query": SXNG_query,
|
|
}
|
|
|
|
|
|
def response(resp: "SXNG_Response"):
|
|
res = EngineResults()
|
|
json_data = resp.json()["data"]["searchResource"]
|
|
|
|
for edge in json_data["edges"]:
|
|
node = edge["node"] # pyright: ignore[reportAny]
|
|
image_urls = [url for (resolution, url) in node["urls"].items() if resolution.startswith("size_")]
|
|
res.add(
|
|
res.types.Image(
|
|
url=f"{base_url}/photo/{node['id']}",
|
|
thumbnail_src=image_urls[0],
|
|
img_src=image_urls[-1],
|
|
title=node["title"],
|
|
content=node["description"] or "",
|
|
author=node["uploader"]["displayName"],
|
|
resolution=f"{node['width']}x{node['height']}",
|
|
)
|
|
)
|
|
|
|
return res
|