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