.oO SearXNG Developer Documentation Oo.
Loading...
Searching...
No Matches
piped.py
Go to the documentation of this file.
1# SPDX-License-Identifier: AGPL-3.0-or-later
2"""An alternative privacy-friendly YouTube frontend which is efficient by
3design. `Piped’s architecture`_ consists of 3 components:
4
5- :py:obj:`backend <backend_url>`
6- :py:obj:`frontend <frontend_url>`
7- proxy
8
9.. _Piped’s architecture: https://docs.piped.video/docs/architecture/
10
11Configuration
12=============
13
14The :py:obj:`backend_url` and :py:obj:`frontend_url` has to be set in the engine
15named `piped` and are used by all ``piped`` engines (unless an individual values
16for ``backend_url`` and ``frontend_url`` are configured for the engine).
17
18
19.. code:: yaml
20
21 - name: piped
22 engine: piped
23 piped_filter: videos
24 ...
25 frontend_url: https://..
26 backend_url:
27 - https://..
28 - https://..
29
30 - name: piped.music
31 engine: piped
32 network: piped
33 shortcut: ppdm
34 piped_filter: music_songs
35 ...
36
37Known Quirks
38============
39
40The implementation to support :py:obj:`paging <searx.enginelib.Engine.paging>`
41is based on the *nextpage* method of Piped's REST API / the :py:obj:`frontend
42API <frontend_url>`. This feature is *next page driven* and plays well with the
43:ref:`infinite_scroll <settings ui>` setting in SearXNG but it does not really
44fit into SearXNG's UI to select a page by number.
45
46Implementations
47===============
48
49"""
50
51
52import time
53import random
54from urllib.parse import urlencode
55import datetime
56from dateutil import parser
57
58from searx.utils import humanize_number
59
60# about
61about = {
62 "website": 'https://github.com/TeamPiped/Piped/',
63 "wikidata_id": 'Q107565255',
64 "official_api_documentation": 'https://docs.piped.video/docs/api-documentation/',
65 "use_official_api": True,
66 "require_api_key": False,
67 "results": 'JSON',
68}
69
70# engine dependent config
71categories = []
72paging = True
73
74# search-url
75backend_url: list[str] | str | None = None
76"""Piped-Backend_: The core component behind Piped. The value is an URL or a
77list of URLs. In the latter case instance will be selected randomly. For a
78complete list of official instances see Piped-Instances (`JSON
79<https://piped-instances.kavin.rocks/>`__)
80
81.. _Piped-Instances: https://github.com/TeamPiped/Piped/wiki/Instances
82.. _Piped-Backend: https://github.com/TeamPiped/Piped-Backend
83
84"""
85
86frontend_url: str | None = None
87"""Piped-Frontend_: URL to use as link and for embeds.
88
89.. _Piped-Frontend: https://github.com/TeamPiped/Piped
90"""
91
92piped_filter = 'all'
93"""Content filter ``music_songs`` or ``videos``"""
94
95
96def _backend_url() -> str:
97 from searx.engines import engines # pylint: disable=import-outside-toplevel
98
99 url: list[str] | str = backend_url or engines["piped"].backend_url # type: ignore
100 if isinstance(url, list):
101 url = random.choice(url)
102 return url
103
104
105def _frontend_url() -> str:
106 from searx.engines import engines # pylint: disable=import-outside-toplevel
107
108 return frontend_url or engines["piped"].frontend_url # type: ignore
109
110
111def request(query, params):
112
113 args = {
114 'q': query,
115 'filter': piped_filter,
116 }
117
118 path = "/search"
119 if params['pageno'] > 1:
120 # don't use nextpage when user selected to jump back to page 1
121 nextpage = params['engine_data'].get('nextpage')
122 if nextpage:
123 path = "/nextpage/search"
124 args['nextpage'] = nextpage
125
126 params["url"] = _backend_url() + f"{path}?" + urlencode(args)
127 return params
128
129
130def response(resp):
131 results = []
132
133 json = resp.json()
134
135 for result in json["items"]:
136 # note: piped returns -1 for all upload times when filtering for music
137 uploaded = result.get("uploaded", -1)
138
139 item = {
140 # the api url differs from the frontend, hence use piped.video as default
141 "url": _frontend_url() + result.get("url", ""),
142 "title": result.get("title", ""),
143 "publishedDate": parser.parse(time.ctime(uploaded / 1000)) if uploaded != -1 else None,
144 "iframe_src": _frontend_url() + '/embed' + result.get("url", ""),
145 "views": humanize_number(result["views"]),
146 }
147 length = result.get("duration")
148 if length:
149 item["length"] = datetime.timedelta(seconds=length)
150
151 if piped_filter == 'videos':
152 item["template"] = "videos.html"
153 # if the value of shortDescription set, but is None, return empty string
154 item["content"] = result.get("shortDescription", "") or ""
155 item["thumbnail"] = result.get("thumbnail", "")
156
157 elif piped_filter == 'music_songs':
158 item["template"] = "default.html"
159 item["thumbnail"] = result.get("thumbnail", "")
160 item["content"] = result.get("uploaderName", "") or ""
161
162 results.append(item)
163
164 results.append(
165 {
166 "engine_data": json["nextpage"],
167 "key": "nextpage",
168 }
169 )
170 return results
request(query, params)
Definition piped.py:111
str _frontend_url()
Definition piped.py:105
str _backend_url()
Definition piped.py:96
::1337x
Definition 1337x.py:1