Files
searxng/searx/engines/500px.py
Markus Heiser 280aceb1ae [mod] add new category 'stock_images' - a specialization of 'images' (#6703)
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>
2026-09-18 16:13:42 +02:00

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