summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew Opalach <andrew@akon.city> 2025-05-18 12:00:19 -0400
committerAndrew Opalach <andrew@akon.city> 2025-05-18 12:00:19 -0400
commit01f852081b1291da7fc342976bf5228ffb618736 (patch)
tree9701e47aaa34e1fd777cea4371d17103f5d90932
parentcb342b234defc1205de5d8b10ddf79deae0a551a (diff)
downloadcamu-01f852081b1291da7fc342976bf5228ffb618736.tar.gz
camu-01f852081b1291da7fc342976bf5228ffb618736.tar.bz2
camu-01f852081b1291da7fc342976bf5228ffb618736.zip
Portal update
Signed-off-by: Andrew Opalach <andrew@akon.city>
-rw-r--r--flake.lock18
-rw-r--r--src/portal/cpy/portal.pyx2
-rw-r--r--src/portal/patches/instagrapi_no_android.diff17
-rw-r--r--src/portal/patches/pixivpy_missing_is_followed.diff13
-rw-r--r--src/portal/patches/pixivpy_reauth.diff83
-rw-r--r--src/portal/patches/searxng_disable_engines_default.diff3675
-rw-r--r--src/portal/patches/snscrape_auth.diff43
-rw-r--r--src/portal/py/config.def.py4
-rw-r--r--src/portal/py/modules/__init__.py12
-rw-r--r--src/portal/py/modules/fanbox.py10
-rw-r--r--src/portal/py/modules/instagram.py18
-rw-r--r--src/portal/py/modules/patreon.py8
-rw-r--r--src/portal/py/modules/pixiv_app.py151
-rw-r--r--src/portal/py/modules/pixiv_web.py78
-rw-r--r--src/portal/py/modules/twitter.py61
-rw-r--r--src/portal/py/post.py30
-rw-r--r--src/portal/py/query_parser.py4
-rw-r--r--src/portal/py/test.py4
-rw-r--r--src/portal/py/tests/archive_query.py11
-rw-r--r--src/portal/py/tests/old_twitter_api.py4
-rw-r--r--src/portal/py/tests/rewrite_post.py24
-rw-r--r--src/portal/py/tests/test.py109
-rw-r--r--src/portal/py/tests/touch_all.py79
-rw-r--r--src/portal/py/tlds.py1445
-rw-r--r--src/portal/requirements.txt1
-rwxr-xr-xsrc/portal/scripts/create_vendor.sh4
26 files changed, 1818 insertions, 4090 deletions
diff --git a/flake.lock b/flake.lock
index 7c734e5..58e0db0 100644
--- a/flake.lock
+++ b/flake.lock
@@ -23,11 +23,11 @@
]
},
"locked": {
- "lastModified": 1746632058,
- "narHash": "sha256-Mp5Bbvb+YlFEZ76C/0wFS6C1lRfH3D60u465wFNlnS0=",
+ "lastModified": 1747279714,
+ "narHash": "sha256-UdxlE8yyrKiGq3bgGyJ78AdFwh+fuRAruKtyFY5Zq5I=",
"owner": "nix-community",
"repo": "home-manager",
- "rev": "708074ae6db9e0468e4f48477f856e8c2d059795",
+ "rev": "954615c510c9faa3ee7fb6607ff72e55905e69f2",
"type": "github"
},
"original": {
@@ -39,11 +39,11 @@
},
"nixos-hardware": {
"locked": {
- "lastModified": 1746621361,
- "narHash": "sha256-T9vOxEqI1j1RYugV0b9dgy0AreiZ9yBDKZJYyclF0og=",
+ "lastModified": 1747129300,
+ "narHash": "sha256-L3clA5YGeYCF47ghsI7Tcex+DnaaN/BbQ4dR2wzoiKg=",
"owner": "NixOS",
"repo": "nixos-hardware",
- "rev": "2ea3ad8a1f26a76f8a8e23fc4f7757c46ef30ee5",
+ "rev": "e81fd167b33121269149c57806599045fd33eeed",
"type": "github"
},
"original": {
@@ -91,11 +91,11 @@
},
"nixpkgs_2": {
"locked": {
- "lastModified": 1746461020,
- "narHash": "sha256-7+pG1I9jvxNlmln4YgnlW4o+w0TZX24k688mibiFDUE=",
+ "lastModified": 1747179050,
+ "narHash": "sha256-qhFMmDkeJX9KJwr5H32f1r7Prs7XbQWtO0h3V0a0rFY=",
"owner": "NixOS",
"repo": "nixpkgs",
- "rev": "3730d8a308f94996a9ba7c7138ede69c1b9ac4ae",
+ "rev": "adaa24fbf46737f3f1b5497bf64bae750f82942e",
"type": "github"
},
"original": {
diff --git a/src/portal/cpy/portal.pyx b/src/portal/cpy/portal.pyx
index b9b6e8d..6307a2d 100644
--- a/src/portal/cpy/portal.pyx
+++ b/src/portal/cpy/portal.pyx
@@ -69,7 +69,7 @@ class Portal():
def print_modules(self):
for name in ALL_MODULES.keys():
- log.info('Registered module \'{}\'.'.format(name))
+ log.info(f'Registered module \'{name}\'.')
def new_id(self):
while True:
diff --git a/src/portal/patches/instagrapi_no_android.diff b/src/portal/patches/instagrapi_no_android.diff
deleted file mode 100644
index 6f4fd34..0000000
--- a/src/portal/patches/instagrapi_no_android.diff
+++ /dev/null
@@ -1,17 +0,0 @@
-diff --git a/instagrapi/mixins/private.py b/instagrapi/mixins/private.py
-index 9db04b0..2b17d75 100644
---- a/instagrapi/mixins/private.py
-+++ b/instagrapi/mixins/private.py
-@@ -129,9 +129,9 @@ class PrivateRequestMixin:
- # X-IG-WWW-Claim: hmac.AR3zruvyGTlwHvVd2ACpGCWLluOppXX4NAVDV-iYslo9CaDd
- "X-Bloks-Is-Layout-RTL": "false",
- "X-Bloks-Is-Panorama-Enabled": "true",
-- "X-IG-Device-ID": self.uuid,
-- "X-IG-Family-Device-ID": self.phone_id,
-- "X-IG-Android-ID": self.android_device_id,
-+ #"X-IG-Device-ID": self.uuid,
-+ #"X-IG-Family-Device-ID": self.phone_id,
-+ #"X-IG-Android-ID": self.android_device_id,
- "X-IG-Timezone-Offset": str(self.timezone_offset),
- "X-IG-Connection-Type": "WIFI",
- "X-IG-Capabilities": "3brTvx0=", # "3brTvwE=" in instabot
diff --git a/src/portal/patches/pixivpy_missing_is_followed.diff b/src/portal/patches/pixivpy_missing_is_followed.diff
new file mode 100644
index 0000000..0d9674b
--- /dev/null
+++ b/src/portal/patches/pixivpy_missing_is_followed.diff
@@ -0,0 +1,13 @@
+diff --git a/pixivpy3/models.py b/pixivpy3/models.py
+index fa9a950..b2d81a0 100644
+--- a/pixivpy3/models.py
++++ b/pixivpy3/models.py
+@@ -113,7 +113,7 @@ class UserInfo(BasePixivpyModel):
+ account: str
+ profile_image_urls: ProfileImageUrls
+ comment: Optional[str] = None # present only on `user_detail` endpoint
+- is_followed: Optional[bool]
++ is_followed: Optional[bool] = None
+ is_access_blocking_user: Optional[bool] = None
+
+
diff --git a/src/portal/patches/pixivpy_reauth.diff b/src/portal/patches/pixivpy_reauth.diff
index d9ce0d6..386adb3 100644
--- a/src/portal/patches/pixivpy_reauth.diff
+++ b/src/portal/patches/pixivpy_reauth.diff
@@ -1,30 +1,8 @@
-diff --git a/pixivpy3/aapi.py b/pixivpy3/aapi.py
-index 1492385..ffc88c0 100644
---- a/pixivpy3/aapi.py
-+++ b/pixivpy3/aapi.py
-@@ -76,12 +76,11 @@ class AppPixivAPI(BasePixivAPI):
- ) -> Response:
- headers_ = CaseInsensitiveDict(headers or {})
- if self.hosts != "https://app-api.pixiv.net":
-- headers_["host"] = "app-api.pixiv.net"
-- if "user-agent" not in headers_:
-- # Set User-Agent if not provided
-- headers_["app-os"] = "ios"
-- headers_["app-os-version"] = "14.6"
-- headers_["user-agent"] = "PixivIOSApp/7.13.3 (iOS 14.6; iPhone13,2)"
-+ headers_["Host"] = "app-api.pixiv.net"
-+ headers_["App-OS"] = "ios"
-+ headers_["App-OS-Version"] = "16.7.2"
-+ headers_["App-Version"] = "7.19.6"
-+ headers_["User-Agent"] = "PixivIOSApp/7.19.6 (iOS 16.7.2; iPhone13,2)"
-
- if not req_auth:
- return self.requests_call(method, url, headers_, params, data)
diff --git a/pixivpy3/api.py b/pixivpy3/api.py
-index c26dda2..d0e1c0f 100644
+index 8a246b6..473c620 100644
--- a/pixivpy3/api.py
+++ b/pixivpy3/api.py
-@@ -26,6 +26,7 @@ class BasePixivAPI:
+@@ -24,6 +24,7 @@ class BasePixivAPI:
self.user_id: int | str = 0
self.access_token: str | None = None
self.refresh_token: str | None = None
@@ -32,65 +10,48 @@ index c26dda2..d0e1c0f 100644
self.hosts = "https://app-api.pixiv.net"
# self.requests = requests.Session()
-@@ -62,6 +63,11 @@ class BasePixivAPI:
+@@ -63,6 +64,11 @@ class BasePixivAPI:
stream: bool = False,
) -> Response:
- """requests http/https call for Pixiv API"""
-+ if self.token_expiration is not None and datetime.utcnow().timestamp() >= self.token_expiration:
-+ # Token expired refresh auth
-+ print('Token expired, refreshing auth')
+ """Requests http/https call for Pixiv API"""
++ if self.token_expiration and datetime.now().timestamp() >= self.token_expiration:
++ # Token expired refresh auth.
++ print("Token expired, refreshing auth.")
+ self.token_expiration = None
+ self.auth()
merged_headers = self.additional_headers.copy()
if headers:
# Use the headers in the parameter to override the
-@@ -118,23 +124,22 @@ class BasePixivAPI:
+@@ -123,11 +129,12 @@ class BasePixivAPI:
headers: ParamDict = None,
) -> ParsedJson:
"""Login with password, or use the refresh_token to acquire a new bearer token"""
-- local_time = datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S+00:00")
-+ local_time = datetime.utcnow()
+- local_time = datetime.now().strftime("%Y-%m-%dT%H:%M:%S+00:00")
++ local_time = datetime.now()
+ local_time_str = local_time.strftime("%Y-%m-%dT%H:%M:%S+00:00")
headers_ = CaseInsensitiveDict(headers or {})
- headers_["x-client-time"] = local_time
-- headers_["x-client-hash"] = hashlib.md5((local_time + self.hash_secret).encode("utf-8")).hexdigest()
-- # Allow mock UA due to #171: https://github.com/upbit/pixivpy/issues/171
-- if "user-agent" not in headers_:
-- headers_["app-os"] = "ios"
-- headers_["app-os-version"] = "14.6"
-- headers_["user-agent"] = "PixivIOSApp/7.13.3 (iOS 14.6; iPhone13,2)"
--
-+ headers_["X-Client-Time"] = local_time_str
-+ headers_["X-Client-Hash"] = hashlib.md5((local_time_str + self.hash_secret).encode("utf-8")).hexdigest()
-+ headers_["App-OS"] = "ios"
-+ headers_["App-OS-Version"] = "16.7.2"
-+ headers_["App-Version"] = "7.19.6"
-+ headers_["User-Agent"] = "PixivIOSApp/7.19.6 (iOS 16.7.2; iPhone13,2)"
- # noinspection PyUnresolvedReferences
- if not hasattr(self, "hosts") or self.hosts == "https://app-api.pixiv.net":
- auth_hosts = "https://oauth.secure.pixiv.net"
- else:
- # noinspection PyUnresolvedReferences
- auth_hosts = self.hosts # BAPI解析成IP的场景
-- headers_["host"] = "oauth.secure.pixiv.net"
-+ headers_["Host"] = "oauth.secure.pixiv.net"
- url = "%s/auth/token" % auth_hosts
- data = {
- "get_secure_url": 1,
-@@ -149,6 +154,8 @@ class BasePixivAPI:
++ headers_["x-client-time"] = local_time_str
+ headers_["x-client-hash"] = hashlib.md5(
+- (local_time + self.hash_secret).encode("utf-8")
++ (local_time_str + self.hash_secret).encode("utf-8")
+ ).hexdigest()
+ # Allow mock UA due to #171: https://github.com/upbit/pixivpy/issues/171
+ if "user-agent" not in headers_:
+@@ -156,6 +163,8 @@ class BasePixivAPI:
elif refresh_token or self.refresh_token:
data["grant_type"] = "refresh_token"
data["refresh_token"] = refresh_token or self.refresh_token
+ if self.access_token:
+ data["access_token"] = self.access_token
else:
- raise PixivError("[ERROR] auth() but no password or refresh_token is set.")
-
-@@ -174,6 +181,7 @@ class BasePixivAPI:
+ msg = "[ERROR] auth() but no password or refresh_token is set."
+ raise PixivError(msg)
+@@ -183,6 +192,7 @@ class BasePixivAPI:
self.user_id = token.response.user.id
self.access_token = token.response.access_token
self.refresh_token = token.response.refresh_token
+ self.token_expiration = local_time.timestamp() + token.response.expires_in
except json.JSONDecodeError:
+ msg = f"Get access_token error! Response: {token}"
raise PixivError(
- "Get access_token error! Response: %s" % token,
diff --git a/src/portal/patches/searxng_disable_engines_default.diff b/src/portal/patches/searxng_disable_engines_default.diff
deleted file mode 100644
index 923519c..0000000
--- a/src/portal/patches/searxng_disable_engines_default.diff
+++ /dev/null
@@ -1,3675 +0,0 @@
-diff --git a/searx/settings.yml b/searx/settings.yml
-index 6946b89d..92378207 100644
---- a/searx/settings.yml
-+++ b/searx/settings.yml
-@@ -294,468 +294,6 @@ categories_as_tabs:
- social media:
-
- engines:
-- - name: 9gag
-- engine: 9gag
-- shortcut: 9g
-- disabled: true
--
-- - name: annas archive
-- engine: annas_archive
-- disabled: true
-- shortcut: aa
--
-- # - name: annas articles
-- # engine: annas_archive
-- # shortcut: aaa
-- # # https://docs.searxng.org/dev/engines/online/annas_archive.html
-- # aa_content: 'journal_article' # book_any .. magazine, standards_document
-- # aa_ext: 'pdf' # pdf, epub, ..
-- # aa_sort: 'newest' # newest, oldest, largest, smallest
--
-- - name: apk mirror
-- engine: apkmirror
-- timeout: 4.0
-- shortcut: apkm
-- disabled: true
--
-- - name: apple app store
-- engine: apple_app_store
-- shortcut: aps
-- disabled: true
--
-- # Requires Tor
-- - name: ahmia
-- engine: ahmia
-- categories: onions
-- enable_http: true
-- shortcut: ah
--
-- - name: anaconda
-- engine: xpath
-- paging: true
-- first_page_num: 0
-- search_url: https://anaconda.org/search?q={query}&page={pageno}
-- results_xpath: //tbody/tr
-- url_xpath: ./td/h5/a[last()]/@href
-- title_xpath: ./td/h5
-- content_xpath: ./td[h5]/text()
-- categories: it
-- timeout: 6.0
-- shortcut: conda
-- disabled: true
--
-- - name: arch linux wiki
-- engine: archlinux
-- shortcut: al
--
-- - name: artic
-- engine: artic
-- shortcut: arc
-- timeout: 4.0
--
-- - name: arxiv
-- engine: arxiv
-- shortcut: arx
-- timeout: 4.0
--
-- # tmp suspended: dh key too small
-- # - name: base
-- # engine: base
-- # shortcut: bs
--
-- - name: bandcamp
-- engine: bandcamp
-- shortcut: bc
-- categories: music
--
-- - name: wikipedia
-- engine: wikipedia
-- shortcut: wp
-- # add "list" to the array to get results in the results list
-- display_type: ["infobox"]
-- base_url: 'https://{language}.wikipedia.org/'
-- categories: [general]
--
-- - name: bilibili
-- engine: bilibili
-- shortcut: bil
-- disabled: true
--
-- - name: bing
-- engine: bing
-- shortcut: bi
-- disabled: true
--
-- - name: bing images
-- engine: bing_images
-- shortcut: bii
--
-- - name: bing news
-- engine: bing_news
-- shortcut: bin
--
-- - name: bing videos
-- engine: bing_videos
-- shortcut: biv
--
-- - name: bitbucket
-- engine: xpath
-- paging: true
-- search_url: https://bitbucket.org/repo/all/{pageno}?name={query}
-- url_xpath: //article[@class="repo-summary"]//a[@class="repo-link"]/@href
-- title_xpath: //article[@class="repo-summary"]//a[@class="repo-link"]
-- content_xpath: //article[@class="repo-summary"]/p
-- categories: [it, repos]
-- timeout: 4.0
-- disabled: true
-- shortcut: bb
-- about:
-- website: https://bitbucket.org/
-- wikidata_id: Q2493781
-- official_api_documentation: https://developer.atlassian.com/bitbucket
-- use_official_api: false
-- require_api_key: false
-- results: HTML
--
-- - name: btdigg
-- engine: btdigg
-- shortcut: bt
-- disabled: true
--
-- - name: ccc-tv
-- engine: xpath
-- paging: false
-- search_url: https://media.ccc.de/search/?q={query}
-- url_xpath: //div[@class="caption"]/h3/a/@href
-- title_xpath: //div[@class="caption"]/h3/a/text()
-- content_xpath: //div[@class="caption"]/h4/@title
-- categories: videos
-- disabled: true
-- shortcut: c3tv
-- about:
-- website: https://media.ccc.de/
-- wikidata_id: Q80729951
-- official_api_documentation: https://github.com/voc/voctoweb
-- use_official_api: false
-- require_api_key: false
-- results: HTML
-- # We don't set language: de here because media.ccc.de is not just
-- # for a German audience. It contains many English videos and many
-- # German videos have English subtitles.
--
-- - name: openverse
-- engine: openverse
-- categories: images
-- shortcut: opv
--
-- - name: chefkoch
-- engine: chefkoch
-- shortcut: chef
-- # to show premium or plus results too:
-- # skip_premium: false
--
-- # - name: core.ac.uk
-- # engine: core
-- # categories: science
-- # shortcut: cor
-- # # get your API key from: https://core.ac.uk/api-keys/register/
-- # api_key: 'unset'
--
-- - name: crossref
-- engine: crossref
-- shortcut: cr
-- timeout: 30
-- disabled: true
--
-- - name: crowdview
-- engine: json_engine
-- shortcut: cv
-- categories: general
-- paging: false
-- search_url: https://crowdview-next-js.onrender.com/api/search-v3?query={query}
-- results_query: results
-- url_query: link
-- title_query: title
-- content_query: snippet
-- disabled: true
-- about:
-- website: https://crowdview.ai/
--
-- - name: yep
-- engine: json_engine
-- shortcut: yep
-- categories: general
-- disabled: true
-- paging: false
-- content_html_to_text: true
-- title_html_to_text: true
-- search_url: https://api.yep.com/fs/1/?type=web&q={query}&no_correct=false&limit=100
-- results_query: 1/results
-- title_query: title
-- url_query: url
-- content_query: snippet
-- about:
-- website: https://yep.com
-- use_official_api: false
-- require_api_key: false
-- results: JSON
--
-- - name: curlie
-- engine: xpath
-- shortcut: cl
-- categories: general
-- disabled: true
-- paging: true
-- lang_all: ''
-- search_url: https://curlie.org/search?q={query}&lang={lang}&start={pageno}&stime=92452189
-- page_size: 20
-- results_xpath: //div[@id="site-list-content"]/div[@class="site-item"]
-- url_xpath: ./div[@class="title-and-desc"]/a/@href
-- title_xpath: ./div[@class="title-and-desc"]/a/div
-- content_xpath: ./div[@class="title-and-desc"]/div[@class="site-descr"]
-- about:
-- website: https://curlie.org/
-- wikidata_id: Q60715723
-- use_official_api: false
-- require_api_key: false
-- results: HTML
--
-- - name: currency
-- engine: currency_convert
-- categories: general
-- shortcut: cc
--
-- - name: deezer
-- engine: deezer
-- shortcut: dz
-- disabled: true
--
-- - name: deviantart
-- engine: deviantart
-- shortcut: da
-- timeout: 3.0
--
-- - name: ddg definitions
-- engine: duckduckgo_definitions
-- shortcut: ddd
-- weight: 2
-- disabled: true
-- tests: *tests_infobox
--
-- # cloudflare protected
-- # - name: digbt
-- # engine: digbt
-- # shortcut: dbt
-- # timeout: 6.0
-- # disabled: true
--
-- - name: docker hub
-- engine: docker_hub
-- shortcut: dh
-- categories: [it, packages]
--
-- - name: erowid
-- engine: xpath
-- paging: true
-- first_page_num: 0
-- page_size: 30
-- search_url: https://www.erowid.org/search.php?q={query}&s={pageno}
-- url_xpath: //dl[@class="results-list"]/dt[@class="result-title"]/a/@href
-- title_xpath: //dl[@class="results-list"]/dt[@class="result-title"]/a/text()
-- content_xpath: //dl[@class="results-list"]/dd[@class="result-details"]
-- categories: []
-- shortcut: ew
-- disabled: true
-- about:
-- website: https://www.erowid.org/
-- wikidata_id: Q1430691
-- official_api_documentation:
-- use_official_api: false
-- require_api_key: false
-- results: HTML
--
-- # - name: elasticsearch
-- # shortcut: es
-- # engine: elasticsearch
-- # base_url: http://localhost:9200
-- # username: elastic
-- # password: changeme
-- # index: my-index
-- # # available options: match, simple_query_string, term, terms, custom
-- # query_type: match
-- # # if query_type is set to custom, provide your query here
-- # #custom_query_json: {"query":{"match_all": {}}}
-- # #show_metadata: false
-- # disabled: true
--
-- - name: wikidata
-- engine: wikidata
-- shortcut: wd
-- timeout: 3.0
-- weight: 2
-- # add "list" to the array to get results in the results list
-- display_type: ["infobox"]
-- tests: *tests_infobox
-- categories: [general]
--
-- - name: duckduckgo
-- engine: duckduckgo
-- shortcut: ddg
--
-- - name: duckduckgo images
-- engine: duckduckgo_images
-- shortcut: ddi
-- timeout: 3.0
-- disabled: true
--
-- - name: duckduckgo weather
-- engine: duckduckgo_weather
-- shortcut: ddw
-- disabled: true
--
-- - name: apple maps
-- engine: apple_maps
-- shortcut: apm
-- disabled: true
-- timeout: 5.0
--
-- - name: emojipedia
-- engine: emojipedia
-- timeout: 4.0
-- shortcut: em
-- disabled: true
--
-- - name: tineye
-- engine: tineye
-- shortcut: tin
-- timeout: 9.0
-- disabled: true
--
-- - name: etymonline
-- engine: xpath
-- paging: true
-- search_url: https://etymonline.com/search?page={pageno}&q={query}
-- url_xpath: //a[contains(@class, "word__name--")]/@href
-- title_xpath: //a[contains(@class, "word__name--")]
-- content_xpath: //section[contains(@class, "word__defination")]
-- first_page_num: 1
-- shortcut: et
-- categories: [dictionaries]
-- about:
-- website: https://www.etymonline.com/
-- wikidata_id: Q1188617
-- official_api_documentation:
-- use_official_api: false
-- require_api_key: false
-- results: HTML
--
-- # - name: ebay
-- # engine: ebay
-- # shortcut: eb
-- # base_url: 'https://www.ebay.com'
-- # disabled: true
-- # timeout: 5
--
-- - name: 1x
-- engine: www1x
-- shortcut: 1x
-- timeout: 3.0
-- disabled: true
--
-- - name: fdroid
-- engine: fdroid
-- shortcut: fd
-- disabled: true
--
-- - name: flickr
-- categories: images
-- shortcut: fl
-- # You can use the engine using the official stable API, but you need an API
-- # key, see: https://www.flickr.com/services/apps/create/
-- # engine: flickr
-- # api_key: 'apikey' # required!
-- # Or you can use the html non-stable engine, activated by default
-- engine: flickr_noapi
--
-- - name: free software directory
-- engine: mediawiki
-- shortcut: fsd
-- categories: [it, software wikis]
-- base_url: https://directory.fsf.org/
-- search_type: title
-- timeout: 5.0
-- disabled: true
-- about:
-- website: https://directory.fsf.org/
-- wikidata_id: Q2470288
--
-- # - name: freesound
-- # engine: freesound
-- # shortcut: fnd
-- # disabled: true
-- # timeout: 15.0
-- # API key required, see: https://freesound.org/docs/api/overview.html
-- # api_key: MyAPIkey
--
-- - name: frinkiac
-- engine: frinkiac
-- shortcut: frk
-- disabled: true
--
-- - name: genius
-- engine: genius
-- shortcut: gen
--
-- - name: gentoo
-- engine: gentoo
-- shortcut: ge
-- timeout: 10.0
--
-- - name: gitlab
-- engine: json_engine
-- paging: true
-- search_url: https://gitlab.com/api/v4/projects?search={query}&page={pageno}
-- url_query: web_url
-- title_query: name_with_namespace
-- content_query: description
-- page_size: 20
-- categories: [it, repos]
-- shortcut: gl
-- timeout: 10.0
-- disabled: true
-- about:
-- website: https://about.gitlab.com/
-- wikidata_id: Q16639197
-- official_api_documentation: https://docs.gitlab.com/ee/api/
-- use_official_api: false
-- require_api_key: false
-- results: JSON
--
-- - name: github
-- engine: github
-- shortcut: gh
--
-- # This a Gitea service. If you would like to use a different instance,
-- # change codeberg.org to URL of the desired Gitea host. Or you can create a
-- # new engine by copying this and changing the name, shortcut and search_url.
--
-- - name: codeberg
-- engine: json_engine
-- search_url: https://codeberg.org/api/v1/repos/search?q={query}&limit=10
-- url_query: html_url
-- title_query: name
-- content_query: description
-- categories: [it, repos]
-- shortcut: cb
-- disabled: true
-- about:
-- website: https://codeberg.org/
-- wikidata_id:
-- official_api_documentation: https://try.gitea.io/api/swagger
-- use_official_api: false
-- require_api_key: false
-- results: JSON
--
- - name: google
- engine: google
- shortcut: go
-@@ -774,1372 +312,1837 @@ engines:
- # result_container:
- # - ['one_title_contains', 'Salvador']
-
-- - name: google news
-- engine: google_news
-- shortcut: gon
-- # additional_tests:
-- # android: *test_android
--
-- - name: google videos
-- engine: google_videos
-- shortcut: gov
-- # additional_tests:
-- # android: *test_android
--
-- - name: google scholar
-- engine: google_scholar
-- shortcut: gos
--
-- - name: google play apps
-- engine: google_play
-- categories: [files, apps]
-- shortcut: gpa
-- play_categ: apps
-- disabled: true
--
-- - name: google play movies
-- engine: google_play
-- categories: videos
-- shortcut: gpm
-- play_categ: movies
-- disabled: true
--
-- - name: material icons
-- engine: material_icons
-- categories: images
-- shortcut: mi
-- disabled: true
--
-- - name: gpodder
-- engine: json_engine
-- shortcut: gpod
-- timeout: 4.0
-- paging: false
-- search_url: https://gpodder.net/search.json?q={query}
-- url_query: url
-- title_query: title
-- content_query: description
-- page_size: 19
-- categories: music
-- disabled: true
-- about:
-- website: https://gpodder.net
-- wikidata_id: Q3093354
-- official_api_documentation: https://gpoddernet.readthedocs.io/en/latest/api/
-- use_official_api: false
-- requires_api_key: false
-- results: JSON
--
-- - name: habrahabr
-- engine: xpath
-- paging: true
-- search_url: https://habr.com/en/search/page{pageno}/?q={query}
-- results_xpath: //article[contains(@class, "tm-articles-list__item")]
-- url_xpath: .//a[@class="tm-title__link"]/@href
-- title_xpath: .//a[@class="tm-title__link"]
-- content_xpath: .//div[contains(@class, "article-formatted-body")]
-- categories: it
-- timeout: 4.0
-- disabled: true
-- shortcut: habr
-- about:
-- website: https://habr.com/
-- wikidata_id: Q4494434
-- official_api_documentation: https://habr.com/en/docs/help/api/
-- use_official_api: false
-- require_api_key: false
-- results: HTML
--
-- - name: hoogle
-- engine: xpath
-- paging: true
-- search_url: https://hoogle.haskell.org/?hoogle={query}&start={pageno}
-- results_xpath: '//div[@class="result"]'
-- title_xpath: './/div[@class="ans"]//a'
-- url_xpath: './/div[@class="ans"]//a/@href'
-- content_xpath: './/div[@class="from"]'
-- page_size: 20
-- categories: [it, packages]
-- shortcut: ho
-- about:
-- website: https://hoogle.haskell.org/
-- wikidata_id: Q34010
-- official_api_documentation: https://hackage.haskell.org/api
-- use_official_api: false
-- require_api_key: false
-- results: JSON
--
-- - name: imdb
-- engine: imdb
-- shortcut: imdb
-- timeout: 6.0
-- disabled: true
--
-- - name: ina
-- engine: ina
-- shortcut: in
-- timeout: 6.0
-- disabled: true
--
-- - name: invidious
-- engine: invidious
-- # Instanes will be selected randomly, see https://api.invidious.io/ for
-- # instances that are stable (good uptime) and close to you.
-- base_url:
-- - https://invidious.io.lol
-- - https://invidious.fdn.fr
-- - https://yt.artemislena.eu
-- - https://invidious.tiekoetter.com
-- - https://invidious.flokinet.to
-- - https://vid.puffyan.us
-- - https://invidious.privacydev.net
-- - https://inv.tux.pizza
-- shortcut: iv
-- timeout: 3.0
-- disabled: true
--
-- - name: jisho
-- engine: jisho
-- shortcut: js
-- timeout: 3.0
-- disabled: true
--
-- - name: kickass
-- engine: kickass
-- shortcut: kc
-- timeout: 4.0
-- disabled: true
--
-- - name: lemmy communities
-- engine: lemmy
-- lemmy_type: Communities
-- shortcut: leco
--
-- - name: lemmy users
-- engine: lemmy
-- network: lemmy communities
-- lemmy_type: Users
-- shortcut: leus
--
-- - name: lemmy posts
-- engine: lemmy
-- network: lemmy communities
-- lemmy_type: Posts
-- shortcut: lepo
--
-- - name: lemmy comments
-- engine: lemmy
-- network: lemmy communities
-- lemmy_type: Comments
-- shortcut: lecom
--
-- - name: library genesis
-- engine: xpath
-- search_url: https://libgen.fun/search.php?req={query}
-- url_xpath: //a[contains(@href,"get.php?md5")]/@href
-- title_xpath: //a[contains(@href,"book/")]/text()[1]
-- content_xpath: //td/a[1][contains(@href,"=author")]/text()
-- categories: files
-- timeout: 7.0
-- disabled: true
-- shortcut: lg
-- about:
-- website: https://libgen.fun/
-- wikidata_id: Q22017206
-- official_api_documentation:
-- use_official_api: false
-- require_api_key: false
-- results: HTML
--
-- - name: z-library
-- engine: zlibrary
-- shortcut: zlib
-- categories: files
-- timeout: 7.0
--
-- - name: library of congress
-- engine: loc
-- shortcut: loc
-- categories: images
--
-- - name: lingva
-- engine: lingva
-- shortcut: lv
-- # set lingva instance in url, by default it will use the official instance
-- # url: https://lingva.ml
--
-- - name: lobste.rs
-- engine: xpath
-- search_url: https://lobste.rs/search?utf8=%E2%9C%93&q={query}&what=stories&order=relevance
-- results_xpath: //li[contains(@class, "story")]
-- url_xpath: .//a[@class="u-url"]/@href
-- title_xpath: .//a[@class="u-url"]
-- content_xpath: .//a[@class="domain"]
-- categories: it
-- shortcut: lo
-- timeout: 5.0
-- disabled: true
-- about:
-- website: https://lobste.rs/
-- wikidata_id: Q60762874
-- official_api_documentation:
-- use_official_api: false
-- require_api_key: false
-- results: HTML
--
-- - name: azlyrics
-- shortcut: lyrics
-- engine: xpath
-- timeout: 4.0
-- disabled: true
-- categories: [music, lyrics]
-- paging: true
-- search_url: https://search.azlyrics.com/search.php?q={query}&w=lyrics&p={pageno}
-- url_xpath: //td[@class="text-left visitedlyr"]/a/@href
-- title_xpath: //span/b/text()
-- content_xpath: //td[@class="text-left visitedlyr"]/a/small
-- about:
-- website: https://azlyrics.com
-- wikidata_id: Q66372542
-- official_api_documentation:
-- use_official_api: false
-- require_api_key: false
-- results: HTML
--
-- - name: metacpan
-- engine: metacpan
-- shortcut: cpan
-- disabled: true
-- number_of_results: 20
--
-- # - name: meilisearch
-- # engine: meilisearch
-- # shortcut: mes
-- # enable_http: true
-- # base_url: http://localhost:7700
-- # index: my-index
--
-- - name: mixcloud
-- engine: mixcloud
-- shortcut: mc
--
-- # MongoDB engine
-- # Required dependency: pymongo
-- # - name: mymongo
-- # engine: mongodb
-- # shortcut: md
-- # exact_match_only: false
-- # host: '127.0.0.1'
-- # port: 27017
-- # enable_http: true
-- # results_per_page: 20
-- # database: 'business'
-- # collection: 'reviews' # name of the db collection
-- # key: 'name' # key in the collection to search for
--
-- - name: mwmbl
-- engine: mwmbl
-- # api_url: https://api.mwmbl.org
-- shortcut: mwm
-- disabled: true
--
-- - name: npm
-- engine: json_engine
-- paging: true
-- first_page_num: 0
-- search_url: https://api.npms.io/v2/search?q={query}&size=25&from={pageno}
-- results_query: results
-- url_query: package/links/npm
-- title_query: package/name
-- content_query: package/description
-- page_size: 25
-- categories: [it, packages]
-- disabled: true
-- timeout: 5.0
-- shortcut: npm
-- about:
-- website: https://npms.io/
-- wikidata_id: Q7067518
-- official_api_documentation: https://api-docs.npms.io/
-- use_official_api: false
-- require_api_key: false
-- results: JSON
--
-- - name: nyaa
-- engine: nyaa
-- shortcut: nt
-- disabled: true
--
-- - name: mankier
-- engine: json_engine
-- search_url: https://www.mankier.com/api/v2/mans/?q={query}
-- results_query: results
-- url_query: url
-- title_query: name
-- content_query: description
-- categories: it
-- shortcut: man
-- about:
-- website: https://www.mankier.com/
-- official_api_documentation: https://www.mankier.com/api
-- use_official_api: true
-- require_api_key: false
-- results: JSON
--
-- - name: odysee
-- engine: odysee
-- shortcut: od
-- disabled: true
--
-- - name: openairedatasets
-- engine: json_engine
-- paging: true
-- search_url: https://api.openaire.eu/search/datasets?format=json&page={pageno}&size=10&title={query}
-- results_query: response/results/result
-- url_query: metadata/oaf:entity/oaf:result/children/instance/webresource/url/$
-- title_query: metadata/oaf:entity/oaf:result/title/$
-- content_query: metadata/oaf:entity/oaf:result/description/$
-- content_html_to_text: true
-- categories: "science"
-- shortcut: oad
-- timeout: 5.0
-- about:
-- website: https://www.openaire.eu/
-- wikidata_id: Q25106053
-- official_api_documentation: https://api.openaire.eu/
-- use_official_api: false
-- require_api_key: false
-- results: JSON
--
-- - name: openairepublications
-- engine: json_engine
-- paging: true
-- search_url: https://api.openaire.eu/search/publications?format=json&page={pageno}&size=10&title={query}
-- results_query: response/results/result
-- url_query: metadata/oaf:entity/oaf:result/children/instance/webresource/url/$
-- title_query: metadata/oaf:entity/oaf:result/title/$
-- content_query: metadata/oaf:entity/oaf:result/description/$
-- content_html_to_text: true
-- categories: science
-- shortcut: oap
-- timeout: 5.0
-- about:
-- website: https://www.openaire.eu/
-- wikidata_id: Q25106053
-- official_api_documentation: https://api.openaire.eu/
-- use_official_api: false
-- require_api_key: false
-- results: JSON
--
-- # - name: opensemanticsearch
-- # engine: opensemantic
-- # shortcut: oss
-- # base_url: 'http://localhost:8983/solr/opensemanticsearch/'
--
-- - name: openstreetmap
-- engine: openstreetmap
-- shortcut: osm
--
-- - name: openrepos
-- engine: xpath
-- paging: true
-- search_url: https://openrepos.net/search/node/{query}?page={pageno}
-- url_xpath: //li[@class="search-result"]//h3[@class="title"]/a/@href
-- title_xpath: //li[@class="search-result"]//h3[@class="title"]/a
-- content_xpath: //li[@class="search-result"]//div[@class="search-snippet-info"]//p[@class="search-snippet"]
-- categories: files
-- timeout: 4.0
-- disabled: true
-- shortcut: or
-- about:
-- website: https://openrepos.net/
-- wikidata_id:
-- official_api_documentation:
-- use_official_api: false
-- require_api_key: false
-- results: HTML
--
-- - name: packagist
-- engine: json_engine
-- paging: true
-- search_url: https://packagist.org/search.json?q={query}&page={pageno}
-- results_query: results
-- url_query: url
-- title_query: name
-- content_query: description
-- categories: [it, packages]
-- disabled: true
-- timeout: 5.0
-- shortcut: pack
-- about:
-- website: https://packagist.org
-- wikidata_id: Q108311377
-- official_api_documentation: https://packagist.org/apidoc
-- use_official_api: true
-- require_api_key: false
-- results: JSON
--
-- - name: pdbe
-- engine: pdbe
-- shortcut: pdb
-- # Hide obsolete PDB entries. Default is not to hide obsolete structures
-- # hide_obsolete: false
--
-- - name: photon
-- engine: photon
-- shortcut: ph
--
-- - name: piped
-- engine: piped
-- shortcut: ppd
-- categories: videos
-- piped_filter: videos
-- timeout: 3.0
--
-- # URL to use as link and for embeds
-- frontend_url: https://srv.piped.video
-- # Instance will be selected randomly, for more see https://piped-instances.kavin.rocks/
-- backend_url:
-- - https://pipedapi.kavin.rocks
-- - https://pipedapi-libre.kavin.rocks
-- - https://pipedapi.adminforge.de
--
-- - name: piped.music
-- engine: piped
-- network: piped
-- shortcut: ppdm
-- categories: music
-- piped_filter: music_songs
-- timeout: 3.0
--
-- - name: piratebay
-- engine: piratebay
-- shortcut: tpb
-- # You may need to change this URL to a proxy if piratebay is blocked in your
-- # country
-- url: https://thepiratebay.org/
-- timeout: 3.0
--
-- # Required dependency: psychopg2
-- # - name: postgresql
-- # engine: postgresql
-- # database: postgres
-- # username: postgres
-- # password: postgres
-- # limit: 10
-- # query_str: 'SELECT * from my_table WHERE my_column = %(query)s'
-- # shortcut : psql
--
-- - name: pub.dev
-- engine: xpath
-- shortcut: pd
-- search_url: https://pub.dev/packages?q={query}&page={pageno}
-- paging: true
-- results_xpath: //div[contains(@class,"packages-item")]
-- url_xpath: ./div/h3/a/@href
-- title_xpath: ./div/h3/a
-- content_xpath: ./div/div/div[contains(@class,"packages-description")]/span
-- categories: [packages, it]
-- timeout: 3.0
-- disabled: true
-- first_page_num: 1
-- about:
-- website: https://pub.dev/
-- official_api_documentation: https://pub.dev/help/api
-- use_official_api: false
-- require_api_key: false
-- results: HTML
--
-- - name: pubmed
-- engine: pubmed
-- shortcut: pub
-- timeout: 3.0
--
-- - name: pypi
-- shortcut: pypi
-- engine: xpath
-- paging: true
-- search_url: https://pypi.org/search/?q={query}&page={pageno}
-- results_xpath: /html/body/main/div/div/div/form/div/ul/li/a[@class="package-snippet"]
-- url_xpath: ./@href
-- title_xpath: ./h3/span[@class="package-snippet__name"]
-- content_xpath: ./p
-- suggestion_xpath: /html/body/main/div/div/div/form/div/div[@class="callout-block"]/p/span/a[@class="link"]
-- first_page_num: 1
-- categories: [it, packages]
-- about:
-- website: https://pypi.org
-- wikidata_id: Q2984686
-- official_api_documentation: https://warehouse.readthedocs.io/api-reference/index.html
-- use_official_api: false
-- require_api_key: false
-- results: HTML
--
-- - name: qwant
-- qwant_categ: web
-- engine: qwant
-- shortcut: qw
-- categories: [general, web]
-- additional_tests:
-- rosebud: *test_rosebud
--
-- - name: qwant news
-- qwant_categ: news
-- engine: qwant
-- shortcut: qwn
-- categories: news
-- network: qwant
--
-- - name: qwant images
-- qwant_categ: images
-- engine: qwant
-- shortcut: qwi
-- categories: [images, web]
-- network: qwant
--
-- - name: qwant videos
-- qwant_categ: videos
-- engine: qwant
-- shortcut: qwv
-- categories: [videos, web]
-- network: qwant
--
-- # - name: library
-- # engine: recoll
-- # shortcut: lib
-- # base_url: 'https://recoll.example.org/'
-- # search_dir: ''
-- # mount_prefix: /export
-- # dl_prefix: 'https://download.example.org'
-- # timeout: 30.0
-- # categories: files
-- # disabled: true
--
-- # - name: recoll library reference
-- # engine: recoll
-- # base_url: 'https://recoll.example.org/'
-- # search_dir: reference
-- # mount_prefix: /export
-- # dl_prefix: 'https://download.example.org'
-- # shortcut: libr
-- # timeout: 30.0
-- # categories: files
-- # disabled: true
--
-- - name: reddit
-- engine: reddit
-- shortcut: re
-- page_size: 25
--
-- # Required dependency: redis
-- # - name: myredis
-- # shortcut : rds
-- # engine: redis_server
-- # exact_match_only: false
-- # host: '127.0.0.1'
-- # port: 6379
-- # enable_http: true
-- # password: ''
-- # db: 0
-+ - name: bing images
-+ engine: bing_images
-+ shortcut: bii
-
-- # tmp suspended: bad certificate
-- # - name: scanr structures
-- # shortcut: scs
-- # engine: scanr_structures
-+ # - name: 9gag
-+ # engine: 9gag
-+ # shortcut: 9g
- # disabled: true
--
-- - name: sepiasearch
-- engine: sepiasearch
-- shortcut: sep
--
-- - name: soundcloud
-- engine: soundcloud
-- shortcut: sc
--
-- - name: stackoverflow
-- engine: stackexchange
-- shortcut: st
-- api_site: 'stackoverflow'
-- categories: [it, q&a]
--
-- - name: askubuntu
-- engine: stackexchange
-- shortcut: ubuntu
-- api_site: 'askubuntu'
-- categories: [it, q&a]
--
-- - name: internetarchivescholar
-- engine: internet_archive_scholar
-- shortcut: ias
-- timeout: 5.0
--
-- - name: superuser
-- engine: stackexchange
-- shortcut: su
-- api_site: 'superuser'
-- categories: [it, q&a]
--
-- - name: searchcode code
-- engine: searchcode_code
-- shortcut: scc
-- disabled: true
--
-- - name: framalibre
-- engine: framalibre
-- shortcut: frl
-- disabled: true
--
-- # - name: searx
-- # engine: searx_engine
-- # shortcut: se
-- # instance_urls :
-- # - http://127.0.0.1:8888/
-- # - ...
-- # disabled: true
--
-- - name: semantic scholar
-- engine: semantic_scholar
-- disabled: true
-- shortcut: se
--
-- # Spotify needs API credentials
-- # - name: spotify
-- # engine: spotify
-- # shortcut: stf
-- # api_client_id: *******
-- # api_client_secret: *******
--
-- # - name: solr
-- # engine: solr
-- # shortcut: slr
-- # base_url: http://localhost:8983
-- # collection: collection_name
-- # sort: '' # sorting: asc or desc
-- # field_list: '' # comma separated list of field names to display on the UI
-- # default_fields: '' # default field to query
-- # query_fields: '' # query fields
-- # enable_http: true
--
-- # - name: springer nature
-- # engine: springer
-- # # get your API key from: https://dev.springernature.com/signup
-- # # working API key, for test & debug: "a69685087d07eca9f13db62f65b8f601"
-- # api_key: 'unset'
-- # shortcut: springer
-- # timeout: 15.0
--
-- - name: startpage
-- engine: startpage
-- shortcut: sp
-- timeout: 6.0
-- disabled: true
-- additional_tests:
-- rosebud: *test_rosebud
--
-- - name: tokyotoshokan
-- engine: tokyotoshokan
-- shortcut: tt
-- timeout: 6.0
-- disabled: true
--
-- - name: solidtorrents
-- engine: solidtorrents
-- shortcut: solid
-- timeout: 4.0
-- base_url:
-- - https://solidtorrents.to
-- - https://bitsearch.to
--
-- # For this demo of the sqlite engine download:
-- # https://liste.mediathekview.de/filmliste-v2.db.bz2
-- # and unpack into searx/data/filmliste-v2.db
-- # Query to test: "!demo concert"
-- #
-- # - name: demo
-- # engine: sqlite
-- # shortcut: demo
-- # categories: general
-- # result_template: default.html
-- # database: searx/data/filmliste-v2.db
-- # query_str: >-
-- # SELECT title || ' (' || time(duration, 'unixepoch') || ')' AS title,
-- # COALESCE( NULLIF(url_video_hd,''), NULLIF(url_video_sd,''), url_video) AS url,
-- # description AS content
-- # FROM film
-- # WHERE title LIKE :wildcard OR description LIKE :wildcard
-- # ORDER BY duration DESC
--
-- - name: tagesschau
-- engine: tagesschau
-- shortcut: ts
-- disabled: true
--
-- - name: tmdb
-- engine: xpath
-- paging: true
-- search_url: https://www.themoviedb.org/search?page={pageno}&query={query}
-- results_xpath: //div[contains(@class,"movie") or contains(@class,"tv")]//div[contains(@class,"card")]
-- url_xpath: .//div[contains(@class,"poster")]/a/@href
-- thumbnail_xpath: .//img/@src
-- title_xpath: .//div[contains(@class,"title")]//h2
-- content_xpath: .//div[contains(@class,"overview")]
-- shortcut: tm
-- disabled: true
--
-- # Requires Tor
-- - name: torch
-- engine: xpath
-- paging: true
-- search_url:
-- http://xmh57jrknzkhv6y3ls3ubitzfqnkrwxhopf5aygthi7d6rplyvk3noyd.onion/cgi-bin/omega/omega?P={query}&DEFAULTOP=and
-- results_xpath: //table//tr
-- url_xpath: ./td[2]/a
-- title_xpath: ./td[2]/b
-- content_xpath: ./td[2]/small
-- categories: onions
-- enable_http: true
-- shortcut: tch
--
-- # torznab engine lets you query any torznab compatible indexer. Using this
-- # engine in combination with Jackett opens the possibility to query a lot of
-- # public and private indexers directly from SearXNG. More details at:
-- # https://docs.searxng.org/dev/engines/online/torznab.html
-- #
-- # - name: Torznab EZTV
-- # engine: torznab
-- # shortcut: eztv
-- # base_url: http://localhost:9117/api/v2.0/indexers/eztv/results/torznab
-- # enable_http: true # if using localhost
-- # api_key: xxxxxxxxxxxxxxx
-- # show_magnet_links: true
-- # show_torrent_files: false
-- # # https://github.com/Jackett/Jackett/wiki/Jackett-Categories
-- # torznab_categories: # optional
-- # - 2000
-- # - 5000
--
-- - name: twitter
-- shortcut: tw
-- engine: twitter
-- disabled: true
--
-- # tmp suspended - too slow, too many errors
-- # - name: urbandictionary
-- # engine : xpath
-- # search_url : https://www.urbandictionary.com/define.php?term={query}
-- # url_xpath : //*[@class="word"]/@href
-- # title_xpath : //*[@class="def-header"]
-- # content_xpath: //*[@class="meaning"]
-- # shortcut: ud
--
-- - name: unsplash
-- engine: unsplash
-- shortcut: us
--
-- - name: yahoo
-- engine: yahoo
-- shortcut: yh
-- disabled: true
--
-- - name: yahoo news
-- engine: yahoo_news
-- shortcut: yhn
--
-- - name: youtube
-- shortcut: yt
-- # You can use the engine using the official stable API, but you need an API
-- # key See: https://console.developers.google.com/project
-- #
-- # engine: youtube_api
-- # api_key: 'apikey' # required!
-- #
-- # Or you can use the html non-stable engine, activated by default
-- engine: youtube_noapi
--
-- - name: dailymotion
-- engine: dailymotion
-- shortcut: dm
--
-- - name: vimeo
-- engine: vimeo
-- shortcut: vm
--
-- - name: wiby
-- engine: json_engine
-- paging: true
-- search_url: https://wiby.me/json/?q={query}&p={pageno}
-- url_query: URL
-- title_query: Title
-- content_query: Snippet
-- categories: [general, web]
-- shortcut: wib
-- disabled: true
-- about:
-- website: https://wiby.me/
--
-- - name: alexandria
-- engine: json_engine
-- shortcut: alx
-- categories: general
-- paging: true
-- search_url: https://api.alexandria.org/?a=1&q={query}&p={pageno}
-- results_query: results
-- title_query: title
-- url_query: url
-- content_query: snippet
-- timeout: 1.5
-- disabled: true
-- about:
-- website: https://alexandria.org/
-- official_api_documentation: https://github.com/alexandria-org/alexandria-api/raw/master/README.md
-- use_official_api: true
-- require_api_key: false
-- results: JSON
--
-- - name: wikibooks
-- engine: mediawiki
-- weight: 0.5
-- shortcut: wb
-- categories: [general, wikimedia]
-- base_url: "https://{language}.wikibooks.org/"
-- search_type: text
-- disabled: true
-- about:
-- website: https://www.wikibooks.org/
-- wikidata_id: Q367
--
-- - name: wikinews
-- engine: mediawiki
-- shortcut: wn
-- categories: [news, wikimedia]
-- base_url: "https://{language}.wikinews.org/"
-- search_type: text
-- srsort: create_timestamp_desc
-- about:
-- website: https://www.wikinews.org/
-- wikidata_id: Q964
--
-- - name: wikiquote
-- engine: mediawiki
-- weight: 0.5
-- shortcut: wq
-- categories: [general, wikimedia]
-- base_url: "https://{language}.wikiquote.org/"
-- search_type: text
-- disabled: true
-- additional_tests:
-- rosebud: *test_rosebud
-- about:
-- website: https://www.wikiquote.org/
-- wikidata_id: Q369
--
-- - name: wikisource
-- engine: mediawiki
-- weight: 0.5
-- shortcut: ws
-- categories: [general, wikimedia]
-- base_url: "https://{language}.wikisource.org/"
-- search_type: text
-- disabled: true
-- about:
-- website: https://www.wikisource.org/
-- wikidata_id: Q263
--
-- - name: wikispecies
-- engine: mediawiki
-- shortcut: wsp
-- categories: [general, science, wikimedia]
-- base_url: "https://species.wikimedia.org/"
-- search_type: text
-- disabled: true
-- about:
-- website: https://species.wikimedia.org/
-- wikidata_id: Q13679
--
-- - name: wiktionary
-- engine: mediawiki
-- shortcut: wt
-- categories: [dictionaries, wikimedia]
-- base_url: "https://{language}.wiktionary.org/"
-- search_type: text
-- about:
-- website: https://www.wiktionary.org/
-- wikidata_id: Q151
--
-- - name: wikiversity
-- engine: mediawiki
-- weight: 0.5
-- shortcut: wv
-- categories: [general, wikimedia]
-- base_url: "https://{language}.wikiversity.org/"
-- search_type: text
-- disabled: true
-- about:
-- website: https://www.wikiversity.org/
-- wikidata_id: Q370
--
-- - name: wikivoyage
-- engine: mediawiki
-- weight: 0.5
-- shortcut: wy
-- categories: [general, wikimedia]
-- base_url: "https://{language}.wikivoyage.org/"
-- search_type: text
-- disabled: true
-- about:
-- website: https://www.wikivoyage.org/
-- wikidata_id: Q373
--
-- - name: wikicommons.images
-- engine: wikicommons
-- shortcut: wc
-- categories: images
-- number_of_results: 10
--
-- - name: wolframalpha
-- shortcut: wa
-- # You can use the engine using the official stable API, but you need an API
-- # key. See: https://products.wolframalpha.com/api/
-- #
-- # engine: wolframalpha_api
-- # api_key: ''
-- #
-- # Or you can use the html non-stable engine, activated by default
-- engine: wolframalpha_noapi
-- timeout: 6.0
-- categories: general
-- disabled: true
--
-- - name: dictzone
-- engine: dictzone
-- shortcut: dc
--
-- - name: mymemory translated
-- engine: translated
-- shortcut: tl
-- timeout: 5.0
-- # You can use without an API key, but you are limited to 1000 words/day
-- # See: https://mymemory.translated.net/doc/usagelimits.php
-- # api_key: ''
--
-- # Required dependency: mysql-connector-python
-- # - name: mysql
-- # engine: mysql_server
-- # database: mydatabase
-- # username: user
-- # password: pass
-- # limit: 10
-- # query_str: 'SELECT * from mytable WHERE fieldname=%(query)s'
-- # shortcut: mysql
--
-- - name: 1337x
-- engine: 1337x
-- shortcut: 1337x
-- disabled: true
--
-- - name: duden
-- engine: duden
-- shortcut: du
-- disabled: true
--
-- - name: seznam
-- shortcut: szn
-- engine: seznam
-- disabled: true
--
-- # - name: deepl
-- # engine: deepl
-- # shortcut: dpl
-- # # You can use the engine using the official stable API, but you need an API key
-- # # See: https://www.deepl.com/pro-api?cta=header-pro-api
-- # api_key: '' # required!
-- # timeout: 5.0
-- # disabled: true
--
-- - name: mojeek
-- shortcut: mjk
-- engine: xpath
-- paging: true
-- categories: [general, web]
-- search_url: https://www.mojeek.com/search?q={query}&s={pageno}&lang={lang}&lb={lang}
-- results_xpath: //ul[@class="results-standard"]/li/a[@class="ob"]
-- url_xpath: ./@href
-- title_xpath: ../h2/a
-- content_xpath: ..//p[@class="s"]
-- suggestion_xpath: //div[@class="top-info"]/p[@class="top-info spell"]/em/a
-- first_page_num: 0
-- page_size: 10
-- disabled: true
-- about:
-- website: https://www.mojeek.com/
-- wikidata_id: Q60747299
-- official_api_documentation: https://www.mojeek.com/services/api.html/
-- use_official_api: false
-- require_api_key: false
-- results: HTML
--
-- - name: moviepilot
-- engine: moviepilot
-- shortcut: mp
-- disabled: true
--
-- - name: naver
-- shortcut: nvr
-- categories: [general, web]
-- engine: xpath
-- paging: true
-- search_url: https://search.naver.com/search.naver?where=webkr&sm=osp_hty&ie=UTF-8&query={query}&start={pageno}
-- url_xpath: //a[@class="link_tit"]/@href
-- title_xpath: //a[@class="link_tit"]
-- content_xpath: //a[@class="total_dsc"]/div
-- first_page_num: 1
-- page_size: 10
-- disabled: true
-- about:
-- website: https://www.naver.com/
-- wikidata_id: Q485639
-- official_api_documentation: https://developers.naver.com/docs/nmt/examples/
-- use_official_api: false
-- require_api_key: false
-- results: HTML
-- language: ko
--
-- - name: rubygems
-- shortcut: rbg
-- engine: xpath
-- paging: true
-- search_url: https://rubygems.org/search?page={pageno}&query={query}
-- results_xpath: /html/body/main/div/a[@class="gems__gem"]
-- url_xpath: ./@href
-- title_xpath: ./span/h2
-- content_xpath: ./span/p
-- suggestion_xpath: /html/body/main/div/div[@class="search__suggestions"]/p/a
-- first_page_num: 1
-- categories: [it, packages]
-- disabled: true
-- about:
-- website: https://rubygems.org/
-- wikidata_id: Q1853420
-- official_api_documentation: https://guides.rubygems.org/rubygems-org-api/
-- use_official_api: false
-- require_api_key: false
-- results: HTML
--
-- - name: peertube
-- engine: peertube
-- shortcut: ptb
-- paging: true
-- # alternatives see: https://instances.joinpeertube.org/instances
-- # base_url: https://tube.4aem.com
-- categories: videos
-- disabled: true
-- timeout: 6.0
--
-- - name: mediathekviewweb
-- engine: mediathekviewweb
-- shortcut: mvw
-- disabled: true
--
-- # - name: yacy
-- # engine: yacy
-- # shortcut: ya
-- # base_url: http://localhost:8090
-- # # required if you aren't using HTTPS for your local yacy instance'
-- # enable_http: true
-- # timeout: 3.0
-- # # Yacy search mode. 'global' or 'local'.
-- # search_mode: 'global'
--
-- - name: rumble
-- engine: rumble
-- shortcut: ru
-- base_url: https://rumble.com/
-- paging: true
-- categories: videos
-- disabled: true
--
-- - name: wordnik
-- engine: wordnik
-- shortcut: def
-- base_url: https://www.wordnik.com/
-- categories: [dictionaries]
-- timeout: 5.0
--
-- - name: woxikon.de synonyme
-- engine: xpath
-- shortcut: woxi
-- categories: [dictionaries]
-- timeout: 5.0
-- disabled: true
-- search_url: https://synonyme.woxikon.de/synonyme/{query}.php
-- url_xpath: //div[@class="upper-synonyms"]/a/@href
-- content_xpath: //div[@class="synonyms-list-group"]
-- title_xpath: //div[@class="upper-synonyms"]/a
-- no_result_for_http_status: [404]
-- about:
-- website: https://www.woxikon.de/
-- wikidata_id: # No Wikidata ID
-- use_official_api: false
-- require_api_key: false
-- results: HTML
-- language: de
--
-- - name: seekr news
-- engine: seekr
-- shortcut: senews
-- categories: news
-- seekr_category: news
-- disabled: true
--
-- - name: seekr images
-- engine: seekr
-- network: seekr news
-- shortcut: seimg
-- categories: images
-- seekr_category: images
-- disabled: true
--
-- - name: seekr videos
-- engine: seekr
-- network: seekr news
-- shortcut: sevid
-- categories: videos
-- seekr_category: videos
-- disabled: true
--
-- - name: sjp.pwn
-- engine: sjp
-- shortcut: sjp
-- base_url: https://sjp.pwn.pl/
-- timeout: 5.0
-- disabled: true
--
-- - name: svgrepo
-- engine: svgrepo
-- shortcut: svg
-- timeout: 10.0
-- disabled: true
--
-- - name: wallhaven
-- engine: wallhaven
-- # api_key: abcdefghijklmnopqrstuvwxyz
-- shortcut: wh
--
-- # wikimini: online encyclopedia for children
-- # The fulltext and title parameter is necessary for Wikimini because
-- # sometimes it will not show the results and redirect instead
-- - name: wikimini
-- engine: xpath
-- shortcut: wkmn
-- search_url: https://fr.wikimini.org/w/index.php?search={query}&title=Sp%C3%A9cial%3ASearch&fulltext=Search
-- url_xpath: //li/div[@class="mw-search-result-heading"]/a/@href
-- title_xpath: //li//div[@class="mw-search-result-heading"]/a
-- content_xpath: //li/div[@class="searchresult"]
-- categories: general
-- disabled: true
-- about:
-- website: https://wikimini.org/
-- wikidata_id: Q3568032
-- use_official_api: false
-- require_api_key: false
-- results: HTML
-- language: fr
--
-- - name: wttr.in
-- engine: wttr
-- shortcut: wttr
-- timeout: 9.0
--
-- - name: yummly
-- engine: yummly
-- shortcut: yum
-- disabled: true
--
-- - name: brave
-- engine: brave
-- shortcut: br
-- time_range_support: true
-- paging: true
-- categories: [general, web]
-- brave_category: search
-- # brave_spellcheck: true
--
-- - name: brave.images
-- engine: brave
-- network: brave
-- shortcut: brimg
-- categories: [images, web]
-- brave_category: images
--
-- - name: brave.videos
-- engine: brave
-- network: brave
-- shortcut: brvid
-- categories: [videos, web]
-- brave_category: videos
--
-- - name: brave.news
-- engine: brave
-- network: brave
-- shortcut: brnews
-- categories: news
-- brave_category: news
--
-- - name: lib.rs
-- shortcut: lrs
-- engine: xpath
-- search_url: https://lib.rs/search?q={query}
-- results_xpath: /html/body/main/div/ol/li/a
-- url_xpath: ./@href
-- title_xpath: ./div[@class="h"]/h4
-- content_xpath: ./div[@class="h"]/p
-- categories: [it, packages]
-- disabled: true
-- about:
-- website: https://lib.rs
-- wikidata_id: Q113486010
-- use_official_api: false
-- require_api_key: false
-- results: HTML
--
-- - name: sourcehut
-- shortcut: srht
-- engine: xpath
-- paging: true
-- search_url: https://sr.ht/projects?page={pageno}&search={query}
-- results_xpath: (//div[@class="event-list"])[1]/div[@class="event"]
-- url_xpath: ./h4/a[2]/@href
-- title_xpath: ./h4/a[2]
-- content_xpath: ./p
-- first_page_num: 1
-- categories: [it, repos]
-- disabled: true
-- about:
-- website: https://sr.ht
-- wikidata_id: Q78514485
-- official_api_documentation: https://man.sr.ht/
-- use_official_api: false
-- require_api_key: false
-- results: HTML
--
-- - name: goo
-- shortcut: goo
-- engine: xpath
-- paging: true
-- search_url: https://search.goo.ne.jp/web.jsp?MT={query}&FR={pageno}0
-- url_xpath: //div[@class="result"]/p[@class='title fsL1']/a/@href
-- title_xpath: //div[@class="result"]/p[@class='title fsL1']/a
-- content_xpath: //p[contains(@class,'url fsM')]/following-sibling::p
-- first_page_num: 0
-- categories: [general, web]
-- disabled: true
-- timeout: 4.0
-- about:
-- website: https://search.goo.ne.jp
-- wikidata_id: Q249044
-- use_official_api: false
-- require_api_key: false
-- results: HTML
-- language: ja
--
-- - name: bt4g
-- engine: bt4g
-- shortcut: bt4g
--
-- - name: pkg.go.dev
-- engine: xpath
-- shortcut: pgo
-- search_url: https://pkg.go.dev/search?limit=100&m=package&q={query}
-- results_xpath: /html/body/main/div[contains(@class,"SearchResults")]/div[not(@class)]/div[@class="SearchSnippet"]
-- url_xpath: ./div[@class="SearchSnippet-headerContainer"]/h2/a/@href
-- title_xpath: ./div[@class="SearchSnippet-headerContainer"]/h2/a
-- content_xpath: ./p[@class="SearchSnippet-synopsis"]
-- categories: [packages, it]
-- timeout: 3.0
-- disabled: true
-- about:
-- website: https://pkg.go.dev/
-- use_official_api: false
-- require_api_key: false
-- results: HTML
--
--# Doku engine lets you access to any Doku wiki instance:
--# A public one or a privete/corporate one.
--# - name: ubuntuwiki
--# engine: doku
--# shortcut: uw
--# base_url: 'https://doc.ubuntu-fr.org'
--
--# Be careful when enabling this engine if you are
--# running a public instance. Do not expose any sensitive
--# information. You can restrict access by configuring a list
--# of access tokens under tokens.
--# - name: git grep
--# engine: command
--# command: ['git', 'grep', '{{QUERY}}']
--# shortcut: gg
--# tokens: []
--# disabled: true
--# delimiter:
--# chars: ':'
--# keys: ['filepath', 'code']
--
--# Be careful when enabling this engine if you are
--# running a public instance. Do not expose any sensitive
--# information. You can restrict access by configuring a list
--# of access tokens under tokens.
--# - name: locate
--# engine: command
--# command: ['locate', '{{QUERY}}']
--# shortcut: loc
--# tokens: []
--# disabled: true
--# delimiter:
--# chars: ' '
--# keys: ['line']
--
--# Be careful when enabling this engine if you are
--# running a public instance. Do not expose any sensitive
--# information. You can restrict access by configuring a list
--# of access tokens under tokens.
--# - name: find
--# engine: command
--# command: ['find', '.', '-name', '{{QUERY}}']
--# query_type: path
--# shortcut: fnd
--# tokens: []
--# disabled: true
--# delimiter:
--# chars: ' '
--# keys: ['line']
--
--# Be careful when enabling this engine if you are
--# running a public instance. Do not expose any sensitive
--# information. You can restrict access by configuring a list
--# of access tokens under tokens.
--# - name: pattern search in files
--# engine: command
--# command: ['fgrep', '{{QUERY}}']
--# shortcut: fgr
--# tokens: []
--# disabled: true
--# delimiter:
--# chars: ' '
--# keys: ['line']
--
--# Be careful when enabling this engine if you are
--# running a public instance. Do not expose any sensitive
--# information. You can restrict access by configuring a list
--# of access tokens under tokens.
--# - name: regex search in files
--# engine: command
--# command: ['grep', '{{QUERY}}']
--# shortcut: gr
--# tokens: []
--# disabled: true
--# delimiter:
--# chars: ' '
--# keys: ['line']
-+ #
-+ # - name: annas archive
-+ # engine: annas_archive
-+ # disabled: true
-+ # shortcut: aa
-+ #
-+ # # - name: annas articles
-+ # # engine: annas_archive
-+ # # shortcut: aaa
-+ # # # https://docs.searxng.org/dev/engines/online/annas_archive.html
-+ # # aa_content: 'journal_article' # book_any .. magazine, standards_document
-+ # # aa_ext: 'pdf' # pdf, epub, ..
-+ # # aa_sort: 'newest' # newest, oldest, largest, smallest
-+ #
-+ # - name: apk mirror
-+ # engine: apkmirror
-+ # timeout: 4.0
-+ # shortcut: apkm
-+ # disabled: true
-+ #
-+ # - name: apple app store
-+ # engine: apple_app_store
-+ # shortcut: aps
-+ # disabled: true
-+ #
-+ # # Requires Tor
-+ # - name: ahmia
-+ # engine: ahmia
-+ # categories: onions
-+ # enable_http: true
-+ # shortcut: ah
-+ #
-+ # - name: anaconda
-+ # engine: xpath
-+ # paging: true
-+ # first_page_num: 0
-+ # search_url: https://anaconda.org/search?q={query}&page={pageno}
-+ # results_xpath: //tbody/tr
-+ # url_xpath: ./td/h5/a[last()]/@href
-+ # title_xpath: ./td/h5
-+ # content_xpath: ./td[h5]/text()
-+ # categories: it
-+ # timeout: 6.0
-+ # shortcut: conda
-+ # disabled: true
-+ #
-+ # - name: arch linux wiki
-+ # engine: archlinux
-+ # shortcut: al
-+ #
-+ # - name: artic
-+ # engine: artic
-+ # shortcut: arc
-+ # timeout: 4.0
-+ #
-+ # - name: arxiv
-+ # engine: arxiv
-+ # shortcut: arx
-+ # timeout: 4.0
-+ #
-+ # # tmp suspended: dh key too small
-+ # # - name: base
-+ # # engine: base
-+ # # shortcut: bs
-+ #
-+ # - name: bandcamp
-+ # engine: bandcamp
-+ # shortcut: bc
-+ # categories: music
-+ #
-+ # - name: wikipedia
-+ # engine: wikipedia
-+ # shortcut: wp
-+ # # add "list" to the array to get results in the results list
-+ # display_type: ["infobox"]
-+ # base_url: 'https://{language}.wikipedia.org/'
-+ # categories: [general]
-+ #
-+ # - name: bilibili
-+ # engine: bilibili
-+ # shortcut: bil
-+ # disabled: true
-+ #
-+ # - name: bing
-+ # engine: bing
-+ # shortcut: bi
-+ # disabled: true
-+ #
-+ #
-+ # - name: bing news
-+ # engine: bing_news
-+ # shortcut: bin
-+ #
-+ # - name: bing videos
-+ # engine: bing_videos
-+ # shortcut: biv
-+ #
-+ # - name: bitbucket
-+ # engine: xpath
-+ # paging: true
-+ # search_url: https://bitbucket.org/repo/all/{pageno}?name={query}
-+ # url_xpath: //article[@class="repo-summary"]//a[@class="repo-link"]/@href
-+ # title_xpath: //article[@class="repo-summary"]//a[@class="repo-link"]
-+ # content_xpath: //article[@class="repo-summary"]/p
-+ # categories: [it, repos]
-+ # timeout: 4.0
-+ # disabled: true
-+ # shortcut: bb
-+ # about:
-+ # website: https://bitbucket.org/
-+ # wikidata_id: Q2493781
-+ # official_api_documentation: https://developer.atlassian.com/bitbucket
-+ # use_official_api: false
-+ # require_api_key: false
-+ # results: HTML
-+ #
-+ # - name: btdigg
-+ # engine: btdigg
-+ # shortcut: bt
-+ # disabled: true
-+ #
-+ # - name: ccc-tv
-+ # engine: xpath
-+ # paging: false
-+ # search_url: https://media.ccc.de/search/?q={query}
-+ # url_xpath: //div[@class="caption"]/h3/a/@href
-+ # title_xpath: //div[@class="caption"]/h3/a/text()
-+ # content_xpath: //div[@class="caption"]/h4/@title
-+ # categories: videos
-+ # disabled: true
-+ # shortcut: c3tv
-+ # about:
-+ # website: https://media.ccc.de/
-+ # wikidata_id: Q80729951
-+ # official_api_documentation: https://github.com/voc/voctoweb
-+ # use_official_api: false
-+ # require_api_key: false
-+ # results: HTML
-+ # # We don't set language: de here because media.ccc.de is not just
-+ # # for a German audience. It contains many English videos and many
-+ # # German videos have English subtitles.
-+ #
-+ # - name: openverse
-+ # engine: openverse
-+ # categories: images
-+ # shortcut: opv
-+ #
-+ # - name: chefkoch
-+ # engine: chefkoch
-+ # shortcut: chef
-+ # # to show premium or plus results too:
-+ # # skip_premium: false
-+ #
-+ # # - name: core.ac.uk
-+ # # engine: core
-+ # # categories: science
-+ # # shortcut: cor
-+ # # # get your API key from: https://core.ac.uk/api-keys/register/
-+ # # api_key: 'unset'
-+ #
-+ # - name: crossref
-+ # engine: crossref
-+ # shortcut: cr
-+ # timeout: 30
-+ # disabled: true
-+ #
-+ # - name: crowdview
-+ # engine: json_engine
-+ # shortcut: cv
-+ # categories: general
-+ # paging: false
-+ # search_url: https://crowdview-next-js.onrender.com/api/search-v3?query={query}
-+ # results_query: results
-+ # url_query: link
-+ # title_query: title
-+ # content_query: snippet
-+ # disabled: true
-+ # about:
-+ # website: https://crowdview.ai/
-+ #
-+ # - name: yep
-+ # engine: json_engine
-+ # shortcut: yep
-+ # categories: general
-+ # disabled: true
-+ # paging: false
-+ # content_html_to_text: true
-+ # title_html_to_text: true
-+ # search_url: https://api.yep.com/fs/1/?type=web&q={query}&no_correct=false&limit=100
-+ # results_query: 1/results
-+ # title_query: title
-+ # url_query: url
-+ # content_query: snippet
-+ # about:
-+ # website: https://yep.com
-+ # use_official_api: false
-+ # require_api_key: false
-+ # results: JSON
-+ #
-+ # - name: curlie
-+ # engine: xpath
-+ # shortcut: cl
-+ # categories: general
-+ # disabled: true
-+ # paging: true
-+ # lang_all: ''
-+ # search_url: https://curlie.org/search?q={query}&lang={lang}&start={pageno}&stime=92452189
-+ # page_size: 20
-+ # results_xpath: //div[@id="site-list-content"]/div[@class="site-item"]
-+ # url_xpath: ./div[@class="title-and-desc"]/a/@href
-+ # title_xpath: ./div[@class="title-and-desc"]/a/div
-+ # content_xpath: ./div[@class="title-and-desc"]/div[@class="site-descr"]
-+ # about:
-+ # website: https://curlie.org/
-+ # wikidata_id: Q60715723
-+ # use_official_api: false
-+ # require_api_key: false
-+ # results: HTML
-+ #
-+ # - name: currency
-+ # engine: currency_convert
-+ # categories: general
-+ # shortcut: cc
-+ #
-+ # - name: deezer
-+ # engine: deezer
-+ # shortcut: dz
-+ # disabled: true
-+ #
-+ # - name: deviantart
-+ # engine: deviantart
-+ # shortcut: da
-+ # timeout: 3.0
-+ #
-+ # - name: ddg definitions
-+ # engine: duckduckgo_definitions
-+ # shortcut: ddd
-+ # weight: 2
-+ # disabled: true
-+ # tests: *tests_infobox
-+ #
-+ # # cloudflare protected
-+ # # - name: digbt
-+ # # engine: digbt
-+ # # shortcut: dbt
-+ # # timeout: 6.0
-+ # # disabled: true
-+ #
-+ # - name: docker hub
-+ # engine: docker_hub
-+ # shortcut: dh
-+ # categories: [it, packages]
-+ #
-+ # - name: erowid
-+ # engine: xpath
-+ # paging: true
-+ # first_page_num: 0
-+ # page_size: 30
-+ # search_url: https://www.erowid.org/search.php?q={query}&s={pageno}
-+ # url_xpath: //dl[@class="results-list"]/dt[@class="result-title"]/a/@href
-+ # title_xpath: //dl[@class="results-list"]/dt[@class="result-title"]/a/text()
-+ # content_xpath: //dl[@class="results-list"]/dd[@class="result-details"]
-+ # categories: []
-+ # shortcut: ew
-+ # disabled: true
-+ # about:
-+ # website: https://www.erowid.org/
-+ # wikidata_id: Q1430691
-+ # official_api_documentation:
-+ # use_official_api: false
-+ # require_api_key: false
-+ # results: HTML
-+ #
-+ # # - name: elasticsearch
-+ # # shortcut: es
-+ # # engine: elasticsearch
-+ # # base_url: http://localhost:9200
-+ # # username: elastic
-+ # # password: changeme
-+ # # index: my-index
-+ # # # available options: match, simple_query_string, term, terms, custom
-+ # # query_type: match
-+ # # # if query_type is set to custom, provide your query here
-+ # # #custom_query_json: {"query":{"match_all": {}}}
-+ # # #show_metadata: false
-+ # # disabled: true
-+ #
-+ # - name: wikidata
-+ # engine: wikidata
-+ # shortcut: wd
-+ # timeout: 3.0
-+ # weight: 2
-+ # # add "list" to the array to get results in the results list
-+ # display_type: ["infobox"]
-+ # tests: *tests_infobox
-+ # categories: [general]
-+ #
-+ # - name: duckduckgo
-+ # engine: duckduckgo
-+ # shortcut: ddg
-+ #
-+ # - name: duckduckgo images
-+ # engine: duckduckgo_images
-+ # shortcut: ddi
-+ # timeout: 3.0
-+ # disabled: true
-+ #
-+ # - name: duckduckgo weather
-+ # engine: duckduckgo_weather
-+ # shortcut: ddw
-+ # disabled: true
-+ #
-+ # - name: apple maps
-+ # engine: apple_maps
-+ # shortcut: apm
-+ # disabled: true
-+ # timeout: 5.0
-+ #
-+ # - name: emojipedia
-+ # engine: emojipedia
-+ # timeout: 4.0
-+ # shortcut: em
-+ # disabled: true
-+ #
-+ # - name: tineye
-+ # engine: tineye
-+ # shortcut: tin
-+ # timeout: 9.0
-+ # disabled: true
-+ #
-+ # - name: etymonline
-+ # engine: xpath
-+ # paging: true
-+ # search_url: https://etymonline.com/search?page={pageno}&q={query}
-+ # url_xpath: //a[contains(@class, "word__name--")]/@href
-+ # title_xpath: //a[contains(@class, "word__name--")]
-+ # content_xpath: //section[contains(@class, "word__defination")]
-+ # first_page_num: 1
-+ # shortcut: et
-+ # categories: [dictionaries]
-+ # about:
-+ # website: https://www.etymonline.com/
-+ # wikidata_id: Q1188617
-+ # official_api_documentation:
-+ # use_official_api: false
-+ # require_api_key: false
-+ # results: HTML
-+ #
-+ # # - name: ebay
-+ # # engine: ebay
-+ # # shortcut: eb
-+ # # base_url: 'https://www.ebay.com'
-+ # # disabled: true
-+ # # timeout: 5
-+ #
-+ # - name: 1x
-+ # engine: www1x
-+ # shortcut: 1x
-+ # timeout: 3.0
-+ # disabled: true
-+ #
-+ # - name: fdroid
-+ # engine: fdroid
-+ # shortcut: fd
-+ # disabled: true
-+ #
-+ # - name: flickr
-+ # categories: images
-+ # shortcut: fl
-+ # # You can use the engine using the official stable API, but you need an API
-+ # # key, see: https://www.flickr.com/services/apps/create/
-+ # # engine: flickr
-+ # # api_key: 'apikey' # required!
-+ # # Or you can use the html non-stable engine, activated by default
-+ # engine: flickr_noapi
-+ #
-+ # - name: free software directory
-+ # engine: mediawiki
-+ # shortcut: fsd
-+ # categories: [it, software wikis]
-+ # base_url: https://directory.fsf.org/
-+ # search_type: title
-+ # timeout: 5.0
-+ # disabled: true
-+ # about:
-+ # website: https://directory.fsf.org/
-+ # wikidata_id: Q2470288
-+ #
-+ # # - name: freesound
-+ # # engine: freesound
-+ # # shortcut: fnd
-+ # # disabled: true
-+ # # timeout: 15.0
-+ # # API key required, see: https://freesound.org/docs/api/overview.html
-+ # # api_key: MyAPIkey
-+ #
-+ # - name: frinkiac
-+ # engine: frinkiac
-+ # shortcut: frk
-+ # disabled: true
-+ #
-+ # - name: genius
-+ # engine: genius
-+ # shortcut: gen
-+ #
-+ # - name: gentoo
-+ # engine: gentoo
-+ # shortcut: ge
-+ # timeout: 10.0
-+ #
-+ # - name: gitlab
-+ # engine: json_engine
-+ # paging: true
-+ # search_url: https://gitlab.com/api/v4/projects?search={query}&page={pageno}
-+ # url_query: web_url
-+ # title_query: name_with_namespace
-+ # content_query: description
-+ # page_size: 20
-+ # categories: [it, repos]
-+ # shortcut: gl
-+ # timeout: 10.0
-+ # disabled: true
-+ # about:
-+ # website: https://about.gitlab.com/
-+ # wikidata_id: Q16639197
-+ # official_api_documentation: https://docs.gitlab.com/ee/api/
-+ # use_official_api: false
-+ # require_api_key: false
-+ # results: JSON
-+ #
-+ # - name: github
-+ # engine: github
-+ # shortcut: gh
-+ #
-+ # # This a Gitea service. If you would like to use a different instance,
-+ # # change codeberg.org to URL of the desired Gitea host. Or you can create a
-+ # # new engine by copying this and changing the name, shortcut and search_url.
-+ #
-+ # - name: codeberg
-+ # engine: json_engine
-+ # search_url: https://codeberg.org/api/v1/repos/search?q={query}&limit=10
-+ # url_query: html_url
-+ # title_query: name
-+ # content_query: description
-+ # categories: [it, repos]
-+ # shortcut: cb
-+ # disabled: true
-+ # about:
-+ # website: https://codeberg.org/
-+ # wikidata_id:
-+ # official_api_documentation: https://try.gitea.io/api/swagger
-+ # use_official_api: false
-+ # require_api_key: false
-+ # results: JSON
-+ #
-+ #
-+ #
-+ # - name: google news
-+ # engine: google_news
-+ # shortcut: gon
-+ # # additional_tests:
-+ # # android: *test_android
-+ #
-+ # - name: google videos
-+ # engine: google_videos
-+ # shortcut: gov
-+ # # additional_tests:
-+ # # android: *test_android
-+ #
-+ # - name: google scholar
-+ # engine: google_scholar
-+ # shortcut: gos
-+ #
-+ # - name: google play apps
-+ # engine: google_play
-+ # categories: [files, apps]
-+ # shortcut: gpa
-+ # play_categ: apps
-+ # disabled: true
-+ #
-+ # - name: google play movies
-+ # engine: google_play
-+ # categories: videos
-+ # shortcut: gpm
-+ # play_categ: movies
-+ # disabled: true
-+ #
-+ # - name: material icons
-+ # engine: material_icons
-+ # categories: images
-+ # shortcut: mi
-+ # disabled: true
-+ #
-+ # - name: gpodder
-+ # engine: json_engine
-+ # shortcut: gpod
-+ # timeout: 4.0
-+ # paging: false
-+ # search_url: https://gpodder.net/search.json?q={query}
-+ # url_query: url
-+ # title_query: title
-+ # content_query: description
-+ # page_size: 19
-+ # categories: music
-+ # disabled: true
-+ # about:
-+ # website: https://gpodder.net
-+ # wikidata_id: Q3093354
-+ # official_api_documentation: https://gpoddernet.readthedocs.io/en/latest/api/
-+ # use_official_api: false
-+ # requires_api_key: false
-+ # results: JSON
-+ #
-+ # - name: habrahabr
-+ # engine: xpath
-+ # paging: true
-+ # search_url: https://habr.com/en/search/page{pageno}/?q={query}
-+ # results_xpath: //article[contains(@class, "tm-articles-list__item")]
-+ # url_xpath: .//a[@class="tm-title__link"]/@href
-+ # title_xpath: .//a[@class="tm-title__link"]
-+ # content_xpath: .//div[contains(@class, "article-formatted-body")]
-+ # categories: it
-+ # timeout: 4.0
-+ # disabled: true
-+ # shortcut: habr
-+ # about:
-+ # website: https://habr.com/
-+ # wikidata_id: Q4494434
-+ # official_api_documentation: https://habr.com/en/docs/help/api/
-+ # use_official_api: false
-+ # require_api_key: false
-+ # results: HTML
-+ #
-+ # - name: hoogle
-+ # engine: xpath
-+ # paging: true
-+ # search_url: https://hoogle.haskell.org/?hoogle={query}&start={pageno}
-+ # results_xpath: '//div[@class="result"]'
-+ # title_xpath: './/div[@class="ans"]//a'
-+ # url_xpath: './/div[@class="ans"]//a/@href'
-+ # content_xpath: './/div[@class="from"]'
-+ # page_size: 20
-+ # categories: [it, packages]
-+ # shortcut: ho
-+ # about:
-+ # website: https://hoogle.haskell.org/
-+ # wikidata_id: Q34010
-+ # official_api_documentation: https://hackage.haskell.org/api
-+ # use_official_api: false
-+ # require_api_key: false
-+ # results: JSON
-+ #
-+ # - name: imdb
-+ # engine: imdb
-+ # shortcut: imdb
-+ # timeout: 6.0
-+ # disabled: true
-+ #
-+ # - name: ina
-+ # engine: ina
-+ # shortcut: in
-+ # timeout: 6.0
-+ # disabled: true
-+ #
-+ # - name: invidious
-+ # engine: invidious
-+ # # Instanes will be selected randomly, see https://api.invidious.io/ for
-+ # # instances that are stable (good uptime) and close to you.
-+ # base_url:
-+ # - https://invidious.io.lol
-+ # - https://invidious.fdn.fr
-+ # - https://yt.artemislena.eu
-+ # - https://invidious.tiekoetter.com
-+ # - https://invidious.flokinet.to
-+ # - https://vid.puffyan.us
-+ # - https://invidious.privacydev.net
-+ # - https://inv.tux.pizza
-+ # shortcut: iv
-+ # timeout: 3.0
-+ # disabled: true
-+ #
-+ # - name: jisho
-+ # engine: jisho
-+ # shortcut: js
-+ # timeout: 3.0
-+ # disabled: true
-+ #
-+ # - name: kickass
-+ # engine: kickass
-+ # shortcut: kc
-+ # timeout: 4.0
-+ # disabled: true
-+ #
-+ # - name: lemmy communities
-+ # engine: lemmy
-+ # lemmy_type: Communities
-+ # shortcut: leco
-+ #
-+ # - name: lemmy users
-+ # engine: lemmy
-+ # network: lemmy communities
-+ # lemmy_type: Users
-+ # shortcut: leus
-+ #
-+ # - name: lemmy posts
-+ # engine: lemmy
-+ # network: lemmy communities
-+ # lemmy_type: Posts
-+ # shortcut: lepo
-+ #
-+ # - name: lemmy comments
-+ # engine: lemmy
-+ # network: lemmy communities
-+ # lemmy_type: Comments
-+ # shortcut: lecom
-+ #
-+ # - name: library genesis
-+ # engine: xpath
-+ # search_url: https://libgen.fun/search.php?req={query}
-+ # url_xpath: //a[contains(@href,"get.php?md5")]/@href
-+ # title_xpath: //a[contains(@href,"book/")]/text()[1]
-+ # content_xpath: //td/a[1][contains(@href,"=author")]/text()
-+ # categories: files
-+ # timeout: 7.0
-+ # disabled: true
-+ # shortcut: lg
-+ # about:
-+ # website: https://libgen.fun/
-+ # wikidata_id: Q22017206
-+ # official_api_documentation:
-+ # use_official_api: false
-+ # require_api_key: false
-+ # results: HTML
-+ #
-+ # - name: z-library
-+ # engine: zlibrary
-+ # shortcut: zlib
-+ # categories: files
-+ # timeout: 7.0
-+ #
-+ # - name: library of congress
-+ # engine: loc
-+ # shortcut: loc
-+ # categories: images
-+ #
-+ # - name: lingva
-+ # engine: lingva
-+ # shortcut: lv
-+ # # set lingva instance in url, by default it will use the official instance
-+ # # url: https://lingva.ml
-+ #
-+ # - name: lobste.rs
-+ # engine: xpath
-+ # search_url: https://lobste.rs/search?utf8=%E2%9C%93&q={query}&what=stories&order=relevance
-+ # results_xpath: //li[contains(@class, "story")]
-+ # url_xpath: .//a[@class="u-url"]/@href
-+ # title_xpath: .//a[@class="u-url"]
-+ # content_xpath: .//a[@class="domain"]
-+ # categories: it
-+ # shortcut: lo
-+ # timeout: 5.0
-+ # disabled: true
-+ # about:
-+ # website: https://lobste.rs/
-+ # wikidata_id: Q60762874
-+ # official_api_documentation:
-+ # use_official_api: false
-+ # require_api_key: false
-+ # results: HTML
-+ #
-+ # - name: azlyrics
-+ # shortcut: lyrics
-+ # engine: xpath
-+ # timeout: 4.0
-+ # disabled: true
-+ # categories: [music, lyrics]
-+ # paging: true
-+ # search_url: https://search.azlyrics.com/search.php?q={query}&w=lyrics&p={pageno}
-+ # url_xpath: //td[@class="text-left visitedlyr"]/a/@href
-+ # title_xpath: //span/b/text()
-+ # content_xpath: //td[@class="text-left visitedlyr"]/a/small
-+ # about:
-+ # website: https://azlyrics.com
-+ # wikidata_id: Q66372542
-+ # official_api_documentation:
-+ # use_official_api: false
-+ # require_api_key: false
-+ # results: HTML
-+ #
-+ # - name: metacpan
-+ # engine: metacpan
-+ # shortcut: cpan
-+ # disabled: true
-+ # number_of_results: 20
-+ #
-+ # # - name: meilisearch
-+ # # engine: meilisearch
-+ # # shortcut: mes
-+ # # enable_http: true
-+ # # base_url: http://localhost:7700
-+ # # index: my-index
-+ #
-+ # - name: mixcloud
-+ # engine: mixcloud
-+ # shortcut: mc
-+ #
-+ # # MongoDB engine
-+ # # Required dependency: pymongo
-+ # # - name: mymongo
-+ # # engine: mongodb
-+ # # shortcut: md
-+ # # exact_match_only: false
-+ # # host: '127.0.0.1'
-+ # # port: 27017
-+ # # enable_http: true
-+ # # results_per_page: 20
-+ # # database: 'business'
-+ # # collection: 'reviews' # name of the db collection
-+ # # key: 'name' # key in the collection to search for
-+ #
-+ # - name: mwmbl
-+ # engine: mwmbl
-+ # # api_url: https://api.mwmbl.org
-+ # shortcut: mwm
-+ # disabled: true
-+ #
-+ # - name: npm
-+ # engine: json_engine
-+ # paging: true
-+ # first_page_num: 0
-+ # search_url: https://api.npms.io/v2/search?q={query}&size=25&from={pageno}
-+ # results_query: results
-+ # url_query: package/links/npm
-+ # title_query: package/name
-+ # content_query: package/description
-+ # page_size: 25
-+ # categories: [it, packages]
-+ # disabled: true
-+ # timeout: 5.0
-+ # shortcut: npm
-+ # about:
-+ # website: https://npms.io/
-+ # wikidata_id: Q7067518
-+ # official_api_documentation: https://api-docs.npms.io/
-+ # use_official_api: false
-+ # require_api_key: false
-+ # results: JSON
-+ #
-+ # - name: nyaa
-+ # engine: nyaa
-+ # shortcut: nt
-+ # disabled: true
-+ #
-+ # - name: mankier
-+ # engine: json_engine
-+ # search_url: https://www.mankier.com/api/v2/mans/?q={query}
-+ # results_query: results
-+ # url_query: url
-+ # title_query: name
-+ # content_query: description
-+ # categories: it
-+ # shortcut: man
-+ # about:
-+ # website: https://www.mankier.com/
-+ # official_api_documentation: https://www.mankier.com/api
-+ # use_official_api: true
-+ # require_api_key: false
-+ # results: JSON
-+ #
-+ # - name: odysee
-+ # engine: odysee
-+ # shortcut: od
-+ # disabled: true
-+ #
-+ # - name: openairedatasets
-+ # engine: json_engine
-+ # paging: true
-+ # search_url: https://api.openaire.eu/search/datasets?format=json&page={pageno}&size=10&title={query}
-+ # results_query: response/results/result
-+ # url_query: metadata/oaf:entity/oaf:result/children/instance/webresource/url/$
-+ # title_query: metadata/oaf:entity/oaf:result/title/$
-+ # content_query: metadata/oaf:entity/oaf:result/description/$
-+ # content_html_to_text: true
-+ # categories: "science"
-+ # shortcut: oad
-+ # timeout: 5.0
-+ # about:
-+ # website: https://www.openaire.eu/
-+ # wikidata_id: Q25106053
-+ # official_api_documentation: https://api.openaire.eu/
-+ # use_official_api: false
-+ # require_api_key: false
-+ # results: JSON
-+ #
-+ # - name: openairepublications
-+ # engine: json_engine
-+ # paging: true
-+ # search_url: https://api.openaire.eu/search/publications?format=json&page={pageno}&size=10&title={query}
-+ # results_query: response/results/result
-+ # url_query: metadata/oaf:entity/oaf:result/children/instance/webresource/url/$
-+ # title_query: metadata/oaf:entity/oaf:result/title/$
-+ # content_query: metadata/oaf:entity/oaf:result/description/$
-+ # content_html_to_text: true
-+ # categories: science
-+ # shortcut: oap
-+ # timeout: 5.0
-+ # about:
-+ # website: https://www.openaire.eu/
-+ # wikidata_id: Q25106053
-+ # official_api_documentation: https://api.openaire.eu/
-+ # use_official_api: false
-+ # require_api_key: false
-+ # results: JSON
-+ #
-+ # # - name: opensemanticsearch
-+ # # engine: opensemantic
-+ # # shortcut: oss
-+ # # base_url: 'http://localhost:8983/solr/opensemanticsearch/'
-+ #
-+ # - name: openstreetmap
-+ # engine: openstreetmap
-+ # shortcut: osm
-+ #
-+ # - name: openrepos
-+ # engine: xpath
-+ # paging: true
-+ # search_url: https://openrepos.net/search/node/{query}?page={pageno}
-+ # url_xpath: //li[@class="search-result"]//h3[@class="title"]/a/@href
-+ # title_xpath: //li[@class="search-result"]//h3[@class="title"]/a
-+ # content_xpath: //li[@class="search-result"]//div[@class="search-snippet-info"]//p[@class="search-snippet"]
-+ # categories: files
-+ # timeout: 4.0
-+ # disabled: true
-+ # shortcut: or
-+ # about:
-+ # website: https://openrepos.net/
-+ # wikidata_id:
-+ # official_api_documentation:
-+ # use_official_api: false
-+ # require_api_key: false
-+ # results: HTML
-+ #
-+ # - name: packagist
-+ # engine: json_engine
-+ # paging: true
-+ # search_url: https://packagist.org/search.json?q={query}&page={pageno}
-+ # results_query: results
-+ # url_query: url
-+ # title_query: name
-+ # content_query: description
-+ # categories: [it, packages]
-+ # disabled: true
-+ # timeout: 5.0
-+ # shortcut: pack
-+ # about:
-+ # website: https://packagist.org
-+ # wikidata_id: Q108311377
-+ # official_api_documentation: https://packagist.org/apidoc
-+ # use_official_api: true
-+ # require_api_key: false
-+ # results: JSON
-+ #
-+ # - name: pdbe
-+ # engine: pdbe
-+ # shortcut: pdb
-+ # # Hide obsolete PDB entries. Default is not to hide obsolete structures
-+ # # hide_obsolete: false
-+ #
-+ # - name: photon
-+ # engine: photon
-+ # shortcut: ph
-+ #
-+ # - name: piped
-+ # engine: piped
-+ # shortcut: ppd
-+ # categories: videos
-+ # piped_filter: videos
-+ # timeout: 3.0
-+ #
-+ # # URL to use as link and for embeds
-+ # frontend_url: https://srv.piped.video
-+ # # Instance will be selected randomly, for more see https://piped-instances.kavin.rocks/
-+ # backend_url:
-+ # - https://pipedapi.kavin.rocks
-+ # - https://pipedapi-libre.kavin.rocks
-+ # - https://pipedapi.adminforge.de
-+ #
-+ # - name: piped.music
-+ # engine: piped
-+ # network: piped
-+ # shortcut: ppdm
-+ # categories: music
-+ # piped_filter: music_songs
-+ # timeout: 3.0
-+ #
-+ # - name: piratebay
-+ # engine: piratebay
-+ # shortcut: tpb
-+ # # You may need to change this URL to a proxy if piratebay is blocked in your
-+ # # country
-+ # url: https://thepiratebay.org/
-+ # timeout: 3.0
-+ #
-+ # # Required dependency: psychopg2
-+ # # - name: postgresql
-+ # # engine: postgresql
-+ # # database: postgres
-+ # # username: postgres
-+ # # password: postgres
-+ # # limit: 10
-+ # # query_str: 'SELECT * from my_table WHERE my_column = %(query)s'
-+ # # shortcut : psql
-+ #
-+ # - name: pub.dev
-+ # engine: xpath
-+ # shortcut: pd
-+ # search_url: https://pub.dev/packages?q={query}&page={pageno}
-+ # paging: true
-+ # results_xpath: //div[contains(@class,"packages-item")]
-+ # url_xpath: ./div/h3/a/@href
-+ # title_xpath: ./div/h3/a
-+ # content_xpath: ./div/div/div[contains(@class,"packages-description")]/span
-+ # categories: [packages, it]
-+ # timeout: 3.0
-+ # disabled: true
-+ # first_page_num: 1
-+ # about:
-+ # website: https://pub.dev/
-+ # official_api_documentation: https://pub.dev/help/api
-+ # use_official_api: false
-+ # require_api_key: false
-+ # results: HTML
-+ #
-+ # - name: pubmed
-+ # engine: pubmed
-+ # shortcut: pub
-+ # timeout: 3.0
-+ #
-+ # - name: pypi
-+ # shortcut: pypi
-+ # engine: xpath
-+ # paging: true
-+ # search_url: https://pypi.org/search/?q={query}&page={pageno}
-+ # results_xpath: /html/body/main/div/div/div/form/div/ul/li/a[@class="package-snippet"]
-+ # url_xpath: ./@href
-+ # title_xpath: ./h3/span[@class="package-snippet__name"]
-+ # content_xpath: ./p
-+ # suggestion_xpath: /html/body/main/div/div/div/form/div/div[@class="callout-block"]/p/span/a[@class="link"]
-+ # first_page_num: 1
-+ # categories: [it, packages]
-+ # about:
-+ # website: https://pypi.org
-+ # wikidata_id: Q2984686
-+ # official_api_documentation: https://warehouse.readthedocs.io/api-reference/index.html
-+ # use_official_api: false
-+ # require_api_key: false
-+ # results: HTML
-+ #
-+ # - name: qwant
-+ # qwant_categ: web
-+ # engine: qwant
-+ # shortcut: qw
-+ # categories: [general, web]
-+ # additional_tests:
-+ # rosebud: *test_rosebud
-+ #
-+ # - name: qwant news
-+ # qwant_categ: news
-+ # engine: qwant
-+ # shortcut: qwn
-+ # categories: news
-+ # network: qwant
-+ #
-+ # - name: qwant images
-+ # qwant_categ: images
-+ # engine: qwant
-+ # shortcut: qwi
-+ # categories: [images, web]
-+ # network: qwant
-+ #
-+ # - name: qwant videos
-+ # qwant_categ: videos
-+ # engine: qwant
-+ # shortcut: qwv
-+ # categories: [videos, web]
-+ # network: qwant
-+ #
-+ # # - name: library
-+ # # engine: recoll
-+ # # shortcut: lib
-+ # # base_url: 'https://recoll.example.org/'
-+ # # search_dir: ''
-+ # # mount_prefix: /export
-+ # # dl_prefix: 'https://download.example.org'
-+ # # timeout: 30.0
-+ # # categories: files
-+ # # disabled: true
-+ #
-+ # # - name: recoll library reference
-+ # # engine: recoll
-+ # # base_url: 'https://recoll.example.org/'
-+ # # search_dir: reference
-+ # # mount_prefix: /export
-+ # # dl_prefix: 'https://download.example.org'
-+ # # shortcut: libr
-+ # # timeout: 30.0
-+ # # categories: files
-+ # # disabled: true
-+ #
-+ # - name: reddit
-+ # engine: reddit
-+ # shortcut: re
-+ # page_size: 25
-+ #
-+ # # Required dependency: redis
-+ # # - name: myredis
-+ # # shortcut : rds
-+ # # engine: redis_server
-+ # # exact_match_only: false
-+ # # host: '127.0.0.1'
-+ # # port: 6379
-+ # # enable_http: true
-+ # # password: ''
-+ # # db: 0
-+ #
-+ # # tmp suspended: bad certificate
-+ # # - name: scanr structures
-+ # # shortcut: scs
-+ # # engine: scanr_structures
-+ # # disabled: true
-+ #
-+ # - name: sepiasearch
-+ # engine: sepiasearch
-+ # shortcut: sep
-+ #
-+ # - name: soundcloud
-+ # engine: soundcloud
-+ # shortcut: sc
-+ #
-+ # - name: stackoverflow
-+ # engine: stackexchange
-+ # shortcut: st
-+ # api_site: 'stackoverflow'
-+ # categories: [it, q&a]
-+ #
-+ # - name: askubuntu
-+ # engine: stackexchange
-+ # shortcut: ubuntu
-+ # api_site: 'askubuntu'
-+ # categories: [it, q&a]
-+ #
-+ # - name: internetarchivescholar
-+ # engine: internet_archive_scholar
-+ # shortcut: ias
-+ # timeout: 5.0
-+ #
-+ # - name: superuser
-+ # engine: stackexchange
-+ # shortcut: su
-+ # api_site: 'superuser'
-+ # categories: [it, q&a]
-+ #
-+ # - name: searchcode code
-+ # engine: searchcode_code
-+ # shortcut: scc
-+ # disabled: true
-+ #
-+ # - name: framalibre
-+ # engine: framalibre
-+ # shortcut: frl
-+ # disabled: true
-+ #
-+ # # - name: searx
-+ # # engine: searx_engine
-+ # # shortcut: se
-+ # # instance_urls :
-+ # # - http://127.0.0.1:8888/
-+ # # - ...
-+ # # disabled: true
-+ #
-+ # - name: semantic scholar
-+ # engine: semantic_scholar
-+ # disabled: true
-+ # shortcut: se
-+ #
-+ # # Spotify needs API credentials
-+ # # - name: spotify
-+ # # engine: spotify
-+ # # shortcut: stf
-+ # # api_client_id: *******
-+ # # api_client_secret: *******
-+ #
-+ # # - name: solr
-+ # # engine: solr
-+ # # shortcut: slr
-+ # # base_url: http://localhost:8983
-+ # # collection: collection_name
-+ # # sort: '' # sorting: asc or desc
-+ # # field_list: '' # comma separated list of field names to display on the UI
-+ # # default_fields: '' # default field to query
-+ # # query_fields: '' # query fields
-+ # # enable_http: true
-+ #
-+ # # - name: springer nature
-+ # # engine: springer
-+ # # # get your API key from: https://dev.springernature.com/signup
-+ # # # working API key, for test & debug: "a69685087d07eca9f13db62f65b8f601"
-+ # # api_key: 'unset'
-+ # # shortcut: springer
-+ # # timeout: 15.0
-+ #
-+ # - name: startpage
-+ # engine: startpage
-+ # shortcut: sp
-+ # timeout: 6.0
-+ # disabled: true
-+ # additional_tests:
-+ # rosebud: *test_rosebud
-+ #
-+ # - name: tokyotoshokan
-+ # engine: tokyotoshokan
-+ # shortcut: tt
-+ # timeout: 6.0
-+ # disabled: true
-+ #
-+ # - name: solidtorrents
-+ # engine: solidtorrents
-+ # shortcut: solid
-+ # timeout: 4.0
-+ # base_url:
-+ # - https://solidtorrents.to
-+ # - https://bitsearch.to
-+ #
-+ # # For this demo of the sqlite engine download:
-+ # # https://liste.mediathekview.de/filmliste-v2.db.bz2
-+ # # and unpack into searx/data/filmliste-v2.db
-+ # # Query to test: "!demo concert"
-+ # #
-+ # # - name: demo
-+ # # engine: sqlite
-+ # # shortcut: demo
-+ # # categories: general
-+ # # result_template: default.html
-+ # # database: searx/data/filmliste-v2.db
-+ # # query_str: >-
-+ # # SELECT title || ' (' || time(duration, 'unixepoch') || ')' AS title,
-+ # # COALESCE( NULLIF(url_video_hd,''), NULLIF(url_video_sd,''), url_video) AS url,
-+ # # description AS content
-+ # # FROM film
-+ # # WHERE title LIKE :wildcard OR description LIKE :wildcard
-+ # # ORDER BY duration DESC
-+ #
-+ # - name: tagesschau
-+ # engine: tagesschau
-+ # shortcut: ts
-+ # disabled: true
-+ #
-+ # - name: tmdb
-+ # engine: xpath
-+ # paging: true
-+ # search_url: https://www.themoviedb.org/search?page={pageno}&query={query}
-+ # results_xpath: //div[contains(@class,"movie") or contains(@class,"tv")]//div[contains(@class,"card")]
-+ # url_xpath: .//div[contains(@class,"poster")]/a/@href
-+ # thumbnail_xpath: .//img/@src
-+ # title_xpath: .//div[contains(@class,"title")]//h2
-+ # content_xpath: .//div[contains(@class,"overview")]
-+ # shortcut: tm
-+ # disabled: true
-+ #
-+ # # Requires Tor
-+ # - name: torch
-+ # engine: xpath
-+ # paging: true
-+ # search_url:
-+ # http://xmh57jrknzkhv6y3ls3ubitzfqnkrwxhopf5aygthi7d6rplyvk3noyd.onion/cgi-bin/omega/omega?P={query}&DEFAULTOP=and
-+ # results_xpath: //table//tr
-+ # url_xpath: ./td[2]/a
-+ # title_xpath: ./td[2]/b
-+ # content_xpath: ./td[2]/small
-+ # categories: onions
-+ # enable_http: true
-+ # shortcut: tch
-+ #
-+ # # torznab engine lets you query any torznab compatible indexer. Using this
-+ # # engine in combination with Jackett opens the possibility to query a lot of
-+ # # public and private indexers directly from SearXNG. More details at:
-+ # # https://docs.searxng.org/dev/engines/online/torznab.html
-+ # #
-+ # # - name: Torznab EZTV
-+ # # engine: torznab
-+ # # shortcut: eztv
-+ # # base_url: http://localhost:9117/api/v2.0/indexers/eztv/results/torznab
-+ # # enable_http: true # if using localhost
-+ # # api_key: xxxxxxxxxxxxxxx
-+ # # show_magnet_links: true
-+ # # show_torrent_files: false
-+ # # # https://github.com/Jackett/Jackett/wiki/Jackett-Categories
-+ # # torznab_categories: # optional
-+ # # - 2000
-+ # # - 5000
-+ #
-+ # - name: twitter
-+ # shortcut: tw
-+ # engine: twitter
-+ # disabled: true
-+ #
-+ # # tmp suspended - too slow, too many errors
-+ # # - name: urbandictionary
-+ # # engine : xpath
-+ # # search_url : https://www.urbandictionary.com/define.php?term={query}
-+ # # url_xpath : //*[@class="word"]/@href
-+ # # title_xpath : //*[@class="def-header"]
-+ # # content_xpath: //*[@class="meaning"]
-+ # # shortcut: ud
-+ #
-+ # - name: unsplash
-+ # engine: unsplash
-+ # shortcut: us
-+ #
-+ # - name: yahoo
-+ # engine: yahoo
-+ # shortcut: yh
-+ # disabled: true
-+ #
-+ # - name: yahoo news
-+ # engine: yahoo_news
-+ # shortcut: yhn
-+ #
-+ # - name: youtube
-+ # shortcut: yt
-+ # # You can use the engine using the official stable API, but you need an API
-+ # # key See: https://console.developers.google.com/project
-+ # #
-+ # # engine: youtube_api
-+ # # api_key: 'apikey' # required!
-+ # #
-+ # # Or you can use the html non-stable engine, activated by default
-+ # engine: youtube_noapi
-+ #
-+ # - name: dailymotion
-+ # engine: dailymotion
-+ # shortcut: dm
-+ #
-+ # - name: vimeo
-+ # engine: vimeo
-+ # shortcut: vm
-+ #
-+ # - name: wiby
-+ # engine: json_engine
-+ # paging: true
-+ # search_url: https://wiby.me/json/?q={query}&p={pageno}
-+ # url_query: URL
-+ # title_query: Title
-+ # content_query: Snippet
-+ # categories: [general, web]
-+ # shortcut: wib
-+ # disabled: true
-+ # about:
-+ # website: https://wiby.me/
-+ #
-+ # - name: alexandria
-+ # engine: json_engine
-+ # shortcut: alx
-+ # categories: general
-+ # paging: true
-+ # search_url: https://api.alexandria.org/?a=1&q={query}&p={pageno}
-+ # results_query: results
-+ # title_query: title
-+ # url_query: url
-+ # content_query: snippet
-+ # timeout: 1.5
-+ # disabled: true
-+ # about:
-+ # website: https://alexandria.org/
-+ # official_api_documentation: https://github.com/alexandria-org/alexandria-api/raw/master/README.md
-+ # use_official_api: true
-+ # require_api_key: false
-+ # results: JSON
-+ #
-+ # - name: wikibooks
-+ # engine: mediawiki
-+ # weight: 0.5
-+ # shortcut: wb
-+ # categories: [general, wikimedia]
-+ # base_url: "https://{language}.wikibooks.org/"
-+ # search_type: text
-+ # disabled: true
-+ # about:
-+ # website: https://www.wikibooks.org/
-+ # wikidata_id: Q367
-+ #
-+ # - name: wikinews
-+ # engine: mediawiki
-+ # shortcut: wn
-+ # categories: [news, wikimedia]
-+ # base_url: "https://{language}.wikinews.org/"
-+ # search_type: text
-+ # srsort: create_timestamp_desc
-+ # about:
-+ # website: https://www.wikinews.org/
-+ # wikidata_id: Q964
-+ #
-+ # - name: wikiquote
-+ # engine: mediawiki
-+ # weight: 0.5
-+ # shortcut: wq
-+ # categories: [general, wikimedia]
-+ # base_url: "https://{language}.wikiquote.org/"
-+ # search_type: text
-+ # disabled: true
-+ # additional_tests:
-+ # rosebud: *test_rosebud
-+ # about:
-+ # website: https://www.wikiquote.org/
-+ # wikidata_id: Q369
-+ #
-+ # - name: wikisource
-+ # engine: mediawiki
-+ # weight: 0.5
-+ # shortcut: ws
-+ # categories: [general, wikimedia]
-+ # base_url: "https://{language}.wikisource.org/"
-+ # search_type: text
-+ # disabled: true
-+ # about:
-+ # website: https://www.wikisource.org/
-+ # wikidata_id: Q263
-+ #
-+ # - name: wikispecies
-+ # engine: mediawiki
-+ # shortcut: wsp
-+ # categories: [general, science, wikimedia]
-+ # base_url: "https://species.wikimedia.org/"
-+ # search_type: text
-+ # disabled: true
-+ # about:
-+ # website: https://species.wikimedia.org/
-+ # wikidata_id: Q13679
-+ #
-+ # - name: wiktionary
-+ # engine: mediawiki
-+ # shortcut: wt
-+ # categories: [dictionaries, wikimedia]
-+ # base_url: "https://{language}.wiktionary.org/"
-+ # search_type: text
-+ # about:
-+ # website: https://www.wiktionary.org/
-+ # wikidata_id: Q151
-+ #
-+ # - name: wikiversity
-+ # engine: mediawiki
-+ # weight: 0.5
-+ # shortcut: wv
-+ # categories: [general, wikimedia]
-+ # base_url: "https://{language}.wikiversity.org/"
-+ # search_type: text
-+ # disabled: true
-+ # about:
-+ # website: https://www.wikiversity.org/
-+ # wikidata_id: Q370
-+ #
-+ # - name: wikivoyage
-+ # engine: mediawiki
-+ # weight: 0.5
-+ # shortcut: wy
-+ # categories: [general, wikimedia]
-+ # base_url: "https://{language}.wikivoyage.org/"
-+ # search_type: text
-+ # disabled: true
-+ # about:
-+ # website: https://www.wikivoyage.org/
-+ # wikidata_id: Q373
-+ #
-+ # - name: wikicommons.images
-+ # engine: wikicommons
-+ # shortcut: wc
-+ # categories: images
-+ # number_of_results: 10
-+ #
-+ # - name: wolframalpha
-+ # shortcut: wa
-+ # # You can use the engine using the official stable API, but you need an API
-+ # # key. See: https://products.wolframalpha.com/api/
-+ # #
-+ # # engine: wolframalpha_api
-+ # # api_key: ''
-+ # #
-+ # # Or you can use the html non-stable engine, activated by default
-+ # engine: wolframalpha_noapi
-+ # timeout: 6.0
-+ # categories: general
-+ # disabled: true
-+ #
-+ # - name: dictzone
-+ # engine: dictzone
-+ # shortcut: dc
-+ #
-+ # - name: mymemory translated
-+ # engine: translated
-+ # shortcut: tl
-+ # timeout: 5.0
-+ # # You can use without an API key, but you are limited to 1000 words/day
-+ # # See: https://mymemory.translated.net/doc/usagelimits.php
-+ # # api_key: ''
-+ #
-+ # # Required dependency: mysql-connector-python
-+ # # - name: mysql
-+ # # engine: mysql_server
-+ # # database: mydatabase
-+ # # username: user
-+ # # password: pass
-+ # # limit: 10
-+ # # query_str: 'SELECT * from mytable WHERE fieldname=%(query)s'
-+ # # shortcut: mysql
-+ #
-+ # - name: 1337x
-+ # engine: 1337x
-+ # shortcut: 1337x
-+ # disabled: true
-+ #
-+ # - name: duden
-+ # engine: duden
-+ # shortcut: du
-+ # disabled: true
-+ #
-+ # - name: seznam
-+ # shortcut: szn
-+ # engine: seznam
-+ # disabled: true
-+ #
-+ # # - name: deepl
-+ # # engine: deepl
-+ # # shortcut: dpl
-+ # # # You can use the engine using the official stable API, but you need an API key
-+ # # # See: https://www.deepl.com/pro-api?cta=header-pro-api
-+ # # api_key: '' # required!
-+ # # timeout: 5.0
-+ # # disabled: true
-+ #
-+ # - name: mojeek
-+ # shortcut: mjk
-+ # engine: xpath
-+ # paging: true
-+ # categories: [general, web]
-+ # search_url: https://www.mojeek.com/search?q={query}&s={pageno}&lang={lang}&lb={lang}
-+ # results_xpath: //ul[@class="results-standard"]/li/a[@class="ob"]
-+ # url_xpath: ./@href
-+ # title_xpath: ../h2/a
-+ # content_xpath: ..//p[@class="s"]
-+ # suggestion_xpath: //div[@class="top-info"]/p[@class="top-info spell"]/em/a
-+ # first_page_num: 0
-+ # page_size: 10
-+ # disabled: true
-+ # about:
-+ # website: https://www.mojeek.com/
-+ # wikidata_id: Q60747299
-+ # official_api_documentation: https://www.mojeek.com/services/api.html/
-+ # use_official_api: false
-+ # require_api_key: false
-+ # results: HTML
-+ #
-+ # - name: moviepilot
-+ # engine: moviepilot
-+ # shortcut: mp
-+ # disabled: true
-+ #
-+ # - name: naver
-+ # shortcut: nvr
-+ # categories: [general, web]
-+ # engine: xpath
-+ # paging: true
-+ # search_url: https://search.naver.com/search.naver?where=webkr&sm=osp_hty&ie=UTF-8&query={query}&start={pageno}
-+ # url_xpath: //a[@class="link_tit"]/@href
-+ # title_xpath: //a[@class="link_tit"]
-+ # content_xpath: //a[@class="total_dsc"]/div
-+ # first_page_num: 1
-+ # page_size: 10
-+ # disabled: true
-+ # about:
-+ # website: https://www.naver.com/
-+ # wikidata_id: Q485639
-+ # official_api_documentation: https://developers.naver.com/docs/nmt/examples/
-+ # use_official_api: false
-+ # require_api_key: false
-+ # results: HTML
-+ # language: ko
-+ #
-+ # - name: rubygems
-+ # shortcut: rbg
-+ # engine: xpath
-+ # paging: true
-+ # search_url: https://rubygems.org/search?page={pageno}&query={query}
-+ # results_xpath: /html/body/main/div/a[@class="gems__gem"]
-+ # url_xpath: ./@href
-+ # title_xpath: ./span/h2
-+ # content_xpath: ./span/p
-+ # suggestion_xpath: /html/body/main/div/div[@class="search__suggestions"]/p/a
-+ # first_page_num: 1
-+ # categories: [it, packages]
-+ # disabled: true
-+ # about:
-+ # website: https://rubygems.org/
-+ # wikidata_id: Q1853420
-+ # official_api_documentation: https://guides.rubygems.org/rubygems-org-api/
-+ # use_official_api: false
-+ # require_api_key: false
-+ # results: HTML
-+ #
-+ # - name: peertube
-+ # engine: peertube
-+ # shortcut: ptb
-+ # paging: true
-+ # # alternatives see: https://instances.joinpeertube.org/instances
-+ # # base_url: https://tube.4aem.com
-+ # categories: videos
-+ # disabled: true
-+ # timeout: 6.0
-+ #
-+ # - name: mediathekviewweb
-+ # engine: mediathekviewweb
-+ # shortcut: mvw
-+ # disabled: true
-+ #
-+ # # - name: yacy
-+ # # engine: yacy
-+ # # shortcut: ya
-+ # # base_url: http://localhost:8090
-+ # # # required if you aren't using HTTPS for your local yacy instance'
-+ # # enable_http: true
-+ # # timeout: 3.0
-+ # # # Yacy search mode. 'global' or 'local'.
-+ # # search_mode: 'global'
-+ #
-+ # - name: rumble
-+ # engine: rumble
-+ # shortcut: ru
-+ # base_url: https://rumble.com/
-+ # paging: true
-+ # categories: videos
-+ # disabled: true
-+ #
-+ # - name: wordnik
-+ # engine: wordnik
-+ # shortcut: def
-+ # base_url: https://www.wordnik.com/
-+ # categories: [dictionaries]
-+ # timeout: 5.0
-+ #
-+ # - name: woxikon.de synonyme
-+ # engine: xpath
-+ # shortcut: woxi
-+ # categories: [dictionaries]
-+ # timeout: 5.0
-+ # disabled: true
-+ # search_url: https://synonyme.woxikon.de/synonyme/{query}.php
-+ # url_xpath: //div[@class="upper-synonyms"]/a/@href
-+ # content_xpath: //div[@class="synonyms-list-group"]
-+ # title_xpath: //div[@class="upper-synonyms"]/a
-+ # no_result_for_http_status: [404]
-+ # about:
-+ # website: https://www.woxikon.de/
-+ # wikidata_id: # No Wikidata ID
-+ # use_official_api: false
-+ # require_api_key: false
-+ # results: HTML
-+ # language: de
-+ #
-+ # - name: seekr news
-+ # engine: seekr
-+ # shortcut: senews
-+ # categories: news
-+ # seekr_category: news
-+ # disabled: true
-+ #
-+ # - name: seekr images
-+ # engine: seekr
-+ # network: seekr news
-+ # shortcut: seimg
-+ # categories: images
-+ # seekr_category: images
-+ # disabled: true
-+ #
-+ # - name: seekr videos
-+ # engine: seekr
-+ # network: seekr news
-+ # shortcut: sevid
-+ # categories: videos
-+ # seekr_category: videos
-+ # disabled: true
-+ #
-+ # - name: sjp.pwn
-+ # engine: sjp
-+ # shortcut: sjp
-+ # base_url: https://sjp.pwn.pl/
-+ # timeout: 5.0
-+ # disabled: true
-+ #
-+ # - name: svgrepo
-+ # engine: svgrepo
-+ # shortcut: svg
-+ # timeout: 10.0
-+ # disabled: true
-+ #
-+ # - name: wallhaven
-+ # engine: wallhaven
-+ # # api_key: abcdefghijklmnopqrstuvwxyz
-+ # shortcut: wh
-+ #
-+ # # wikimini: online encyclopedia for children
-+ # # The fulltext and title parameter is necessary for Wikimini because
-+ # # sometimes it will not show the results and redirect instead
-+ # - name: wikimini
-+ # engine: xpath
-+ # shortcut: wkmn
-+ # search_url: https://fr.wikimini.org/w/index.php?search={query}&title=Sp%C3%A9cial%3ASearch&fulltext=Search
-+ # url_xpath: //li/div[@class="mw-search-result-heading"]/a/@href
-+ # title_xpath: //li//div[@class="mw-search-result-heading"]/a
-+ # content_xpath: //li/div[@class="searchresult"]
-+ # categories: general
-+ # disabled: true
-+ # about:
-+ # website: https://wikimini.org/
-+ # wikidata_id: Q3568032
-+ # use_official_api: false
-+ # require_api_key: false
-+ # results: HTML
-+ # language: fr
-+ #
-+ # - name: wttr.in
-+ # engine: wttr
-+ # shortcut: wttr
-+ # timeout: 9.0
-+ #
-+ # - name: yummly
-+ # engine: yummly
-+ # shortcut: yum
-+ # disabled: true
-+ #
-+ # - name: brave
-+ # engine: brave
-+ # shortcut: br
-+ # time_range_support: true
-+ # paging: true
-+ # categories: [general, web]
-+ # brave_category: search
-+ # # brave_spellcheck: true
-+ #
-+ # - name: brave.images
-+ # engine: brave
-+ # network: brave
-+ # shortcut: brimg
-+ # categories: [images, web]
-+ # brave_category: images
-+ #
-+ # - name: brave.videos
-+ # engine: brave
-+ # network: brave
-+ # shortcut: brvid
-+ # categories: [videos, web]
-+ # brave_category: videos
-+ #
-+ # - name: brave.news
-+ # engine: brave
-+ # network: brave
-+ # shortcut: brnews
-+ # categories: news
-+ # brave_category: news
-+ #
-+ # - name: lib.rs
-+ # shortcut: lrs
-+ # engine: xpath
-+ # search_url: https://lib.rs/search?q={query}
-+ # results_xpath: /html/body/main/div/ol/li/a
-+ # url_xpath: ./@href
-+ # title_xpath: ./div[@class="h"]/h4
-+ # content_xpath: ./div[@class="h"]/p
-+ # categories: [it, packages]
-+ # disabled: true
-+ # about:
-+ # website: https://lib.rs
-+ # wikidata_id: Q113486010
-+ # use_official_api: false
-+ # require_api_key: false
-+ # results: HTML
-+ #
-+ # - name: sourcehut
-+ # shortcut: srht
-+ # engine: xpath
-+ # paging: true
-+ # search_url: https://sr.ht/projects?page={pageno}&search={query}
-+ # results_xpath: (//div[@class="event-list"])[1]/div[@class="event"]
-+ # url_xpath: ./h4/a[2]/@href
-+ # title_xpath: ./h4/a[2]
-+ # content_xpath: ./p
-+ # first_page_num: 1
-+ # categories: [it, repos]
-+ # disabled: true
-+ # about:
-+ # website: https://sr.ht
-+ # wikidata_id: Q78514485
-+ # official_api_documentation: https://man.sr.ht/
-+ # use_official_api: false
-+ # require_api_key: false
-+ # results: HTML
-+ #
-+ # - name: goo
-+ # shortcut: goo
-+ # engine: xpath
-+ # paging: true
-+ # search_url: https://search.goo.ne.jp/web.jsp?MT={query}&FR={pageno}0
-+ # url_xpath: //div[@class="result"]/p[@class='title fsL1']/a/@href
-+ # title_xpath: //div[@class="result"]/p[@class='title fsL1']/a
-+ # content_xpath: //p[contains(@class,'url fsM')]/following-sibling::p
-+ # first_page_num: 0
-+ # categories: [general, web]
-+ # disabled: true
-+ # timeout: 4.0
-+ # about:
-+ # website: https://search.goo.ne.jp
-+ # wikidata_id: Q249044
-+ # use_official_api: false
-+ # require_api_key: false
-+ # results: HTML
-+ # language: ja
-+ #
-+ # - name: bt4g
-+ # engine: bt4g
-+ # shortcut: bt4g
-+ #
-+ # - name: pkg.go.dev
-+ # engine: xpath
-+ # shortcut: pgo
-+ # search_url: https://pkg.go.dev/search?limit=100&m=package&q={query}
-+ # results_xpath: /html/body/main/div[contains(@class,"SearchResults")]/div[not(@class)]/div[@class="SearchSnippet"]
-+ # url_xpath: ./div[@class="SearchSnippet-headerContainer"]/h2/a/@href
-+ # title_xpath: ./div[@class="SearchSnippet-headerContainer"]/h2/a
-+ # content_xpath: ./p[@class="SearchSnippet-synopsis"]
-+ # categories: [packages, it]
-+ # timeout: 3.0
-+ # disabled: true
-+ # about:
-+ # website: https://pkg.go.dev/
-+ # use_official_api: false
-+ # require_api_key: false
-+ # results: HTML
-+ #
-+ ## Doku engine lets you access to any Doku wiki instance:
-+ ## A public one or a privete/corporate one.
-+ ## - name: ubuntuwiki
-+ ## engine: doku
-+ ## shortcut: uw
-+ ## base_url: 'https://doc.ubuntu-fr.org'
-+ #
-+ ## Be careful when enabling this engine if you are
-+ ## running a public instance. Do not expose any sensitive
-+ ## information. You can restrict access by configuring a list
-+ ## of access tokens under tokens.
-+ ## - name: git grep
-+ ## engine: command
-+ ## command: ['git', 'grep', '{{QUERY}}']
-+ ## shortcut: gg
-+ ## tokens: []
-+ ## disabled: true
-+ ## delimiter:
-+ ## chars: ':'
-+ ## keys: ['filepath', 'code']
-+ #
-+ ## Be careful when enabling this engine if you are
-+ ## running a public instance. Do not expose any sensitive
-+ ## information. You can restrict access by configuring a list
-+ ## of access tokens under tokens.
-+ ## - name: locate
-+ ## engine: command
-+ ## command: ['locate', '{{QUERY}}']
-+ ## shortcut: loc
-+ ## tokens: []
-+ ## disabled: true
-+ ## delimiter:
-+ ## chars: ' '
-+ ## keys: ['line']
-+ #
-+ ## Be careful when enabling this engine if you are
-+ ## running a public instance. Do not expose any sensitive
-+ ## information. You can restrict access by configuring a list
-+ ## of access tokens under tokens.
-+ ## - name: find
-+ ## engine: command
-+ ## command: ['find', '.', '-name', '{{QUERY}}']
-+ ## query_type: path
-+ ## shortcut: fnd
-+ ## tokens: []
-+ ## disabled: true
-+ ## delimiter:
-+ ## chars: ' '
-+ ## keys: ['line']
-+ #
-+ ## Be careful when enabling this engine if you are
-+ ## running a public instance. Do not expose any sensitive
-+ ## information. You can restrict access by configuring a list
-+ ## of access tokens under tokens.
-+ ## - name: pattern search in files
-+ ## engine: command
-+ ## command: ['fgrep', '{{QUERY}}']
-+ ## shortcut: fgr
-+ ## tokens: []
-+ ## disabled: true
-+ ## delimiter:
-+ ## chars: ' '
-+ ## keys: ['line']
-+ #
-+ ## Be careful when enabling this engine if you are
-+ ## running a public instance. Do not expose any sensitive
-+ ## information. You can restrict access by configuring a list
-+ ## of access tokens under tokens.
-+ ## - name: regex search in files
-+ ## engine: command
-+ ## command: ['grep', '{{QUERY}}']
-+ ## shortcut: gr
-+ ## tokens: []
-+ ## disabled: true
-+ ## delimiter:
-+ ## chars: ' '
-+ ## keys: ['line']
-
- doi_resolvers:
- oadoi.org: 'https://oadoi.org/'
diff --git a/src/portal/patches/snscrape_auth.diff b/src/portal/patches/snscrape_auth.diff
index efdc6e0..c1fe487 100644
--- a/src/portal/patches/snscrape_auth.diff
+++ b/src/portal/patches/snscrape_auth.diff
@@ -1,5 +1,5 @@
diff --git a/snscrape/modules/twitter.py b/snscrape/modules/twitter.py
-index d1719a8..43d9076 100644
+index d1719a8..5892340 100644
--- a/snscrape/modules/twitter.py
+++ b/snscrape/modules/twitter.py
@@ -92,6 +92,7 @@ import typing
@@ -7,16 +7,16 @@ index d1719a8..43d9076 100644
import urllib3.util.ssl_
import warnings
+import hashlib
-
-
+
+
_logger = logging.getLogger(__name__)
@@ -99,10 +100,12 @@ _API_AUTHORIZATION_HEADER = 'Bearer AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOu
_globalGuestTokenManager = None
_GUEST_TOKEN_VALIDITY = 10800
_CIPHERS_CHROME = 'TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA:AES256-SHA'
+_AGE_RESTRICTED_MSG = 'Age-restricted adult content. This content might not be appropriate for people under 18 years old. To view this media, you’ll need to log in to Twitter. Learn more'
-
-
+
+
@dataclasses.dataclass
class Tweet(snscrape.base.Item):
+ rawResponses: typing.Dict[str, str]
@@ -26,11 +26,11 @@ index d1719a8..43d9076 100644
@@ -793,17 +796,23 @@ class _TwitterAPIType(enum.Enum):
V2 = 0 # Introduced with the redesign
GRAPHQL = 1
-
+
+class _TwitterAPIAuthType(enum.Enum):
+ GUEST_TOKEN = 0
+ OAUTH2 = 1
-
+
class _TwitterAPIScraper(snscrape.base.Scraper):
- def __init__(self, baseUrl, *, guestTokenManager = None, maxEmptyPages = 0, **kwargs):
+ def __init__(self, baseUrl, *, guestTokenManager = None, maxEmptyPages = 0, cookies = None, **kwargs):
@@ -58,12 +58,12 @@ index d1719a8..43d9076 100644
+ self._lastResponse = None
+ self._lastResponseHash = None
+ self._lastSentResponseHash = None
-
+
def _check_guest_token_response(self, r):
if r.status_code != 200:
@@ -820,6 +834,8 @@ class _TwitterAPIScraper(snscrape.base.Scraper):
return True, None
-
+
def _ensure_guest_token(self, url = None):
+ if not self._guestTokenManager:
+ return
@@ -72,14 +72,14 @@ index d1719a8..43d9076 100644
r = self._get(self._baseUrl if url is None else url, responseOkCallback = self._check_guest_token_response)
@@ -843,18 +859,42 @@ class _TwitterAPIScraper(snscrape.base.Scraper):
self._apiHeaders['x-guest-token'] = self._guestTokenManager.token
-
+
def _unset_guest_token(self, blockUntil):
+ if not self._guestTokenManager:
+ return
self._guestTokenManager.reset(blockUntil = blockUntil)
del self._session.cookies['gt']
del self._apiHeaders['x-guest-token']
-
+
+ def _ensure_oauth(self):
+ if not self._cookies:
+ return
@@ -135,7 +135,7 @@ index d1719a8..43d9076 100644
return False, msg
+ '''
return True, None
-
+
def _get_api_data(self, endpoint, apiType, params, instructionsPath = None):
- self._ensure_guest_token()
+ self._ensure_auth_updated()
@@ -152,12 +152,12 @@ index d1719a8..43d9076 100644
+ hasher.update(json.dumps(self._lastResponse).encode('utf-8'))
+ self._lastResponseHash = hasher.hexdigest()
return r._snscrapeObj
-
+
def _iter_api_data(self, endpoint, apiType, params, paginationParams = None, cursor = None, direction = _ScrollDirection.BOTTOM, instructionsPath = None):
@@ -988,8 +1043,50 @@ class _TwitterAPIScraper(snscrape.base.Scraper):
def _get_tweet_id(self, tweet):
return tweet['id'] if 'id' in tweet else int(tweet['id_str'])
-
+
+ def _entry_is_age_restricted(self, entry):
+ if entry['entryId'].startswith('tombstone-') and entry['content']['entryType'] == 'TimelineTimelineItem':
+ if entry['content']['itemContent']['tombstoneInfo']['richText']['text'] == _AGE_RESTRICTED_MSG:
@@ -205,9 +205,22 @@ index d1719a8..43d9076 100644
kwargs['id'] = tweetId
if noteTweet and 'text' in noteTweet:
kwargs['rawContent'] = noteTweet['text']
+@@ -1005,9 +1102,9 @@ class _TwitterAPIScraper(snscrape.base.Scraper):
+ kwargs['date'] = email.utils.parsedate_to_datetime(tweet['created_at'])
+ if links:
+ kwargs['links'] = [TextLink(
+- text = u.get('display_url'),
+- url = u['expanded_url'],
+- tcourl = u['url'],
++ text = u.get('display_url', ''),
++ url = u.get('expanded_url', ''),
++ tcourl = u.get('url', ''),
+ indices = tuple(u['indices']),
+ ) for u in links]
+ kwargs['url'] = f'https://twitter.com/{getattr(user, "username", "i/web")}/status/{tweetId}'
@@ -1789,7 +1886,7 @@ class TwitterUserScraper(TwitterSearchScraper):
self._baseUrl = f'https://twitter.com/{self._user}' if not self._isUserId else f'https://twitter.com/i/user/{self._user}'
-
+
def _get_entity(self):
- self._ensure_guest_token()
+ self._ensure_auth_updated()
diff --git a/src/portal/py/config.def.py b/src/portal/py/config.def.py
index 8c0dab9..0c7ea30 100644
--- a/src/portal/py/config.def.py
+++ b/src/portal/py/config.def.py
@@ -6,7 +6,7 @@ TWITTER_ACCESS_TOKEN = ''
TWITTER_ACCESS_TOKEN_SECRET = ''
TWITTER_CONSUMER_TOKEN = ''
TWITTER_CONSUMER_TOKEN_SECRET = ''
-TWITTER_COOKIES_PATH = '{}/cookies_twitter.txt'.format(BASE_DIR)
+TWITTER_COOKIES_PATH = f'{BASE_DIR}/cookies_twitter.txt'
TWITTER_TIMEOUT = 5 # seconds
FANBOX_SESSID = ''
@@ -18,7 +18,7 @@ PIXIV_REFRESH_TOKEN = ''
INSTAGRAM_USERNAME = ''
INSTAGRAM_PASSWORD = ''
INSTAGRAM_SESSION_ID = ''
-INSTAGRAM_SETTINGS_PATH = Path('{}/ig_settings.json'.format(BASE_DIR))
+INSTAGRAM_SETTINGS_PATH = Path(f'{BASE_DIR}/ig_settings.json')
INSTAGRAM_USER_AGENT = ''
PATREON_USER_ID = ''
diff --git a/src/portal/py/modules/__init__.py b/src/portal/py/modules/__init__.py
index fee4c22..8a1e3cb 100644
--- a/src/portal/py/modules/__init__.py
+++ b/src/portal/py/modules/__init__.py
@@ -1,9 +1,9 @@
import config
from modules.youtube import YoutubeModule
-#from modules.twitter import TwitterScrapeModule
-#from modules.pixiv_app import PixivAppModule
-#from modules.pixiv_web import PixivWebModule
+from modules.twitter import TwitterScrapeModule
+from modules.pixiv_app import PixivAppModule
+from modules.pixiv_web import PixivWebModule
#from modules.fanbox import FanboxModule
#from modules.instagram import InstagramModule
#from modules.patreon import PatreonModule
@@ -13,9 +13,9 @@ ALL_MODULES = {
# 'twitter': (TwitterModule(
# config.TWITTER_ACCESS_TOKEN, config.TWITTER_ACCESS_TOKEN_SECRET,
# config.TWITTER_CONSUMER_TOKEN, config.TWITTER_CONSUMER_TOKEN_SECRET), []),
-# 'twitter': (TwitterScrapeModule(config.TWITTER_COOKIES_PATH), []),
-# 'pixiv_web': (PixivWebModule(config.PIXIV_SESSID, config.PIXIV_USERID), []),
-# 'pixiv_app': (PixivAppModule(config.PIXIV_REFRESH_TOKEN), []),
+ 'twitter': (TwitterScrapeModule(config.TWITTER_COOKIES_PATH), []),
+ 'pixiv_web': (PixivWebModule(config.PIXIV_SESSID, config.PIXIV_USERID), []),
+ 'pixiv_app': (PixivAppModule(config.PIXIV_REFRESH_TOKEN), []),
# 'fanbox': (FanboxModule(config.FANBOX_SESSID, config.FANBOX_CF_CLEARANCE), []),
# 'instagram': (InstagramModule(
# config.INSTAGRAM_USER_AGENT, config.INSTAGRAM_SETTINGS_PATH,
diff --git a/src/portal/py/modules/fanbox.py b/src/portal/py/modules/fanbox.py
index 9d233bb..01e2b8c 100644
--- a/src/portal/py/modules/fanbox.py
+++ b/src/portal/py/modules/fanbox.py
@@ -31,7 +31,7 @@ class FanboxBase(Search):
unique_id = f'fanbox:p:{post_id}'
kwargs['unique_id'] = unique_id
kwargs['raw_responses'] = {}
- kwargs['raw_responses']['api'] = json.dumps(data)
+ kwargs['raw_responses']['api'] = json.dumps(data, separators=(',', ':'), indent=None)
publish_date = reformat_pixiv_date(data['publishedDatetime']).timestamp()
current_date = get_current_utc_time().timestamp()
kwargs['dates'] = [
@@ -56,12 +56,12 @@ class FanboxBase(Search):
media = {}
if data['type'] == 'image':
text = data['body']['text']
- for i, value in enumerate(data['body']['images']):
- media[str(i)] = Image(url=Url(value['originalUrl'], value['extension']), thumbnail_url=Url(value['thumbnailUrl']))
+ for index, value in enumerate(data['body']['images']):
+ media[str(index)] = Image(url=Url(value['originalUrl'], value['extension']), thumbnail_url=Url(value['thumbnailUrl']))
elif data['type'] == 'file':
text = data['body']['text']
- for i, value in enumerate(data['body']['files']):
- media[str(i)] = File(url=Url(value['url'], value['extension']), name=value['name'])
+ for index, value in enumerate(data['body']['files']):
+ media[str(index)] = File(url=Url(value['url'], value['extension']), name=value['name'])
elif data['type'] == 'article':
for block in data['body']['blocks']:
if block['type'] == 'p':
diff --git a/src/portal/py/modules/instagram.py b/src/portal/py/modules/instagram.py
index 014ffc1..7d71f9f 100644
--- a/src/portal/py/modules/instagram.py
+++ b/src/portal/py/modules/instagram.py
@@ -22,7 +22,7 @@ class InstagramSearch(Search):
if num >= self.page + self.REACH_LIMIT:
return False
request_satisfied = False
- for i in range(self.page, num + 1):
+ for index in range(self.page, num + 1):
try:
media, self.cursor = self.module.cl.user_medias_paginated_v1(self.pk, 0, end_cursor=self.cursor)
#media, self.cursor = self.module.cl.user_clips_paginated_v1(self.pk, amount=12, end_cursor=self.cursor)
@@ -34,24 +34,24 @@ class InstagramSearch(Search):
return False
if not self.cursor:
self.completed = True
- self.pages[i] = []
+ self.pages[index] = []
for m in media:
kwargs = {}
kwargs['type'] = PostType.POST
- unique_id = 'instagram:p:{}'.format(m.id)
- #unique_id = 'instagram:r:{}'.format(m.id)
+ unique_id = f'instagram:p:{m.id}'
+ #unique_id = f'instagram:r:{m.id}'
kwargs['unique_id'] = unique_id
kwargs['raw_responses'] = {}
kwargs['raw_responses']['tile'] = m.json()
- kwargs['url'] = 'https://www.instagram.com/p/{}'.format(m.code)
- #kwargs['url'] = 'https://www.instagram.com/reel/{}'.format(m.code)
+ kwargs['url'] = f'https://www.instagram.com/p/{m.code}'
+ #kwargs['url'] = f'https://www.instagram.com/reel/{m.code}'
create_date = m.taken_at.timestamp()
current_date = get_current_utc_time().timestamp()
kwargs['dates'] = [
Date(DateType.CREATED, (create_date, create_date), DateMeta.NONE),
Date(DateType.RETRIEVED, (current_date, current_date), DateMeta.NONE)
]
- user = User(unique_id='instagram:u:{}'.format(m.user.pk))
+ user = User(unique_id=f'instagram:u:{m.user.pk}')
if m.user.username:
user.username = m.user.username
if m.user.full_name:
@@ -79,9 +79,9 @@ class InstagramSearch(Search):
kwargs['media'] = media
post = Post(**kwargs)
self.module.add_to_map(unique_id, post)
- self.pages[i].append(unique_id)
+ self.pages[index].append(unique_id)
self.page += 1
- if i == num:
+ if index == num:
request_satisfied = True
return request_satisfied
diff --git a/src/portal/py/modules/patreon.py b/src/portal/py/modules/patreon.py
index 0e4345a..bf2f0df 100644
--- a/src/portal/py/modules/patreon.py
+++ b/src/portal/py/modules/patreon.py
@@ -126,7 +126,7 @@ class PatreonUser(PatreonBase):
return False
request_satisfied = False
- for i in range(self.page, num + 1):
+ for index in range(self.page, num + 1):
url = f'{BASE_URL}/posts'
#'filter[accessible_by_user_id]': self.module.user_id,
params = {
@@ -187,17 +187,17 @@ class PatreonUser(PatreonBase):
else:
log.warn(f'Unhandled include type {inc['type']}.')
- self.pages[i] = []
+ self.pages[index] = []
for entry in obj['data']:
if entry['type'] != 'post':
log.warn(f'Unhandled entry type {entry['type']}.')
continue
post = self.create_post(entry, hash)
if post:
- self.pages[i].append(post.unique_id)
+ self.pages[index].append(post.unique_id)
self.page += 1
- if i == num:
+ if index == num:
request_satisfied = True
return request_satisfied
diff --git a/src/portal/py/modules/pixiv_app.py b/src/portal/py/modules/pixiv_app.py
index 02d5254..7bd341b 100644
--- a/src/portal/py/modules/pixiv_app.py
+++ b/src/portal/py/modules/pixiv_app.py
@@ -1,9 +1,9 @@
import json
from typing import Optional, Any
from base import USER_AGENT, Search, Module, ParsedJson
-from post import Url, PostType, DateType, User, Post, Image, Tag, TagType
+from post import Url, PostType, Date, DateType, DateMeta, User, Post, Image, Tag, TagType
from query_parser import QueryParser
-from pixivpy3 import AppPixivAPI
+from pixivpy3 import AppPixivAPI, models
from modules.common import get_current_utc_time, reformat_pixiv_date
class PixivAppBase(Search):
@@ -18,63 +18,77 @@ class PixivAppBase(Search):
def request_page(self, num: int) -> bool:
request_satisfied = False
- for i in range(self.page, num + 1):
+ for index in range(self.page, num + 1):
if not self.next_qs:
break
- if i in self.pages:
+ if index in self.pages:
continue
- self.pages[i] = []
- success, self.next_qs = self.api_request_page(i)
+ self.pages[index] = []
+ success, self.next_qs = self.api_request_page(index)
if not success:
self.errored = True
break
- if i == num:
+ if index == num:
request_satisfied = True
self.page += 1
return request_satisfied
- def create_media_url(self, url: str) -> Url:
- return Url(url, url.split('.')[-1])
-
def create_tag(self, data: ParsedJson) -> Tag:
- tag = Tag(type=TagType.GENERAL, name=data['name'])
+ kwargs = {}
+ kwargs['type'] = TagType.GENERAL
+ kwargs['name'] = data['name']
+ kwargs['alts'] = {}
if data['translated_name']:
- tag.alts['en'] = data['translated_name']
- return tag
+ kwargs['alts']['en'] = data['translated_name']
+ return Tag(**kwargs)
- def create_post(self, data: ParsedJson) -> Post:
- if len(data['meta_pages']) == 0:
- data['meta_pages'].append({
- 'image_urls': {
- 'medium': data['image_urls']['medium'],
- 'original': data['meta_single_page']['original_image_url']
- }
- })
+ # Other objects returned from pixivpy3 are likely also ModelT but it seems
+ # so inconsistent internally for now keep some params as ParsedJson.
+ def create_post(self, data: ParsedJson | models.ModelT, retrieved_date: Optional[float] = None) -> Post:
+ is_deleted = not data['visible']
kwargs = {}
+ kwargs['module'] = 'pixiv_app'
kwargs['type'] = PostType.POST
- unique_id = 'pixiv:i:{}'.format(data['id'])
+ unique_id = f'pixiv:i:{data['id']}'
kwargs['unique_id'] = unique_id
kwargs['raw_responses'] = {}
- kwargs['raw_responses']['api'] = json.dumps(data)
- kwargs['url'] = 'https://www.pixiv.net/en/artworks/{}'.format(data['id'])
- kwargs['dates'] = {
- DateType.CREATED: reformat_pixiv_date(data['create_date']).timestamp(),
- DateType.RETRIEVED: get_current_utc_time().timestamp()
- }
+ try:
+ kwargs['raw_responses']['api'] = json.dumps(data, separators=(',', ':'), indent=None)
+ except TypeError:
+ kwargs['raw_responses']['api'] = data.model_dump_json(indent=None)
+ kwargs['url'] = f'https://www.pixiv.net/en/artworks/{data['id']}'
+ if not retrieved_date:
+ retrieved_date = get_current_utc_time().timestamp()
+ kwargs['dates'] = [
+ Date(DateType.RETRIEVED, (retrieved_date, retrieved_date), DateMeta.NONE)
+ ]
+ if not is_deleted:
+ create_date = reformat_pixiv_date(data['create_date']).timestamp()
+ kwargs['dates'].append(Date(DateType.CREATED, (create_date, create_date), DateMeta.NONE))
user = User(unique_id=f'pixiv:u:{data['user']['id']}', username=data['user']['account'], display_name=data['user']['name'])
- user.profile_picture_url = data['user']['profile_image_urls']['medium']
+ if not is_deleted:
+ user.profile_picture_url = Url(data['user']['profile_image_urls']['medium'])
kwargs['author'] = user
kwargs['title'] = data['title']
kwargs['text'] = data['caption']
- kwargs['likes'] = data['total_bookmarks']
+ if not is_deleted: # Default to None instead of 0.
+ kwargs['bookmarks'] = data['total_bookmarks']
+ kwargs['views'] = data['total_view']
if 'total_comments' in data:
kwargs['comments'] = data['total_comments']
- kwargs['views'] = data['total_view']
- kwargs['tags'] = [self.create_tag(tag) for tag in data['tags']]
- media = {}
- for i, page in enumerate(data['meta_pages']):
- media[str(i)] = Image(url=self.create_media_url(page['image_urls']['original']), thumbnail_url=self.create_media_url(page['image_urls']['medium']))
- kwargs['media'] = media
+ kwargs['tags'] = [self.create_tag(t) for t in data['tags']]
+ if not is_deleted:
+ if len(data['meta_pages']) == 0:
+ data['meta_pages'].append({
+ 'image_urls': {
+ 'medium': data['image_urls']['medium'],
+ 'original': data['meta_single_page']['original_image_url']
+ }
+ })
+ media = {}
+ for index, page in enumerate(data['meta_pages']):
+ media[str(index)] = Image(url=Url(page['image_urls']['original']), thumbnail_url=Url(page['image_urls']['medium']))
+ kwargs['media'] = media
post = Post(**kwargs)
self.module.add_to_map(unique_id, post)
return post
@@ -82,16 +96,23 @@ class PixivAppBase(Search):
def create_user(self, data: ParsedJson) -> User:
unique_id = f'pixiv:u:{data['id']}'
user = User(unique_id=unique_id, username=data['account'], display_name=data['name'])
+ user.profile_picture_url = Url(data['profile_image_urls']['medium'])
self.module.add_to_map(unique_id, user)
return user
+ def remake_post(self, raw_responses: ParsedJson, original_date: float) -> Optional[Post]:
+ api = json.loads(raw_responses['api'])
+ return self.create_post(api, original_date)
+
class PixivAppSearch(PixivAppBase):
def __init__(self, userdata: Any, arg: str) -> None:
super().__init__(userdata)
self.next_qs: Optional[dict[str, Any]] = {}
self.next_qs['word'] = arg
- self.next_qs['sort'] = 'popular_desc'
- self.next_qs['search_ai_type'] = 1
+ #self.next_qs['sort'] = 'popular_desc'
+ # https://github.com/upbit/pixivpy/issues/352
+ # We want to include AI results for completeness, to be ignored further down the chain.
+ #self.next_qs['search_ai_type'] = 1
self.next_qs['start_date'] = '2019-01-01'
self.next_qs['end_date'] = '2019-12-31'
@@ -99,27 +120,10 @@ class PixivAppSearch(PixivAppBase):
if not self.next_qs:
return False, None
obj = self.module.api.search_illust(**self.next_qs)
- if 'illusts' not in obj:
- return False, None
- for post in obj['illusts']:
+ for post in obj.illusts:
self.pages[num].append(self.create_post(post).unique_id)
return True, self.module.api.parse_qs(obj['next_url'])
-class PixivAppIllust(PixivAppBase):
- def __init__(self, userdata: Any, arg: str) -> None:
- super().__init__(userdata)
- self.next_qs: Optional[dict[str, Any]] = {}
- self.next_qs['illust_id'] = arg
-
- def api_request_page(self, num: int) -> tuple[bool, Optional[dict[str, Any]]]:
- if not self.next_qs:
- return False, None
- obj = self.module.api.illust_detail(**self.next_qs)
- if 'illust' not in obj:
- return False, None
- self.pages[num].append(self.create_post(obj['illust']).unique_id)
- return True, None
-
class PixivAppUser(PixivAppBase):
def __init__(self, userdata: Any, arg: str) -> None:
super().__init__(userdata)
@@ -131,9 +135,7 @@ class PixivAppUser(PixivAppBase):
if not self.next_qs:
return False, None
obj = self.module.api.user_illusts(**self.next_qs)
- if 'illusts' not in obj:
- return False, None
- for post in obj['illusts']:
+ for post in obj.illusts:
self.pages[num].append(self.create_post(post).unique_id)
return True, self.module.api.parse_qs(obj['next_url'])
@@ -148,9 +150,7 @@ class PixivAppBookmarks(PixivAppBase):
if not self.next_qs:
return False, None
obj = self.module.api.user_bookmarks_illust(**self.next_qs)
- if 'illusts' not in obj:
- return False, None
- for post in obj['illusts']:
+ for post in obj.illusts:
self.pages[num].append(self.create_post(post).unique_id)
return True, self.module.api.parse_qs(obj['next_url'])
@@ -165,22 +165,39 @@ class PixivAppFollowing(PixivAppBase):
if not self.next_qs:
return False, None
obj = self.module.api.user_following(**self.next_qs)
- if 'user_previews' not in obj:
- return False, None
- for user in obj['user_previews']:
+ for user in obj.user_previews:
self.pages[num].append(self.create_user(user['user']).unique_id)
return True, self.module.api.parse_qs(obj['next_url'])
+class PixivAppIllust(PixivAppBase):
+ def __init__(self, userdata: Any, arg: str) -> None:
+ super().__init__(userdata)
+ self.next_qs: Optional[dict[str, Any]] = {}
+ self.next_qs['illust_id'] = arg
+
+ def api_request_page(self, num: int) -> tuple[bool, Optional[dict[str, Any]]]:
+ if not self.next_qs:
+ return False, None
+ obj = self.module.api.illust_detail(**self.next_qs)
+ self.pages[num].append(self.create_post(obj.illust).unique_id)
+ return True, None
+
class PixivAppModule(Module):
def __init__(self, refresh_token: str) -> None:
super().__init__()
self.parser: QueryParser = QueryParser(self, 'search')
self.parser.add_command('search', PixivAppSearch)
- self.parser.add_command('illust', PixivAppIllust)
self.parser.add_command('user', PixivAppUser)
self.parser.add_command('bookmarks', PixivAppBookmarks)
self.parser.add_command('following', PixivAppFollowing)
- self.api: AppPixivAPI = AppPixivAPI()
+ self.parser.add_command('illust', PixivAppIllust)
+ headers = {
+ "app-os": "ios",
+ "app-os-version": "16.7.2",
+ "app-version": "7.19.6",
+ "user-agent": "PixivIOSApp/7.19.6 (iOS 16.7.2; iPhone13,2)"
+ }
+ self.api: AppPixivAPI = AppPixivAPI(headers=headers)
self.api.auth(refresh_token=refresh_token)
def search(self, query: str, *extra_args: Any) -> Optional[Search]:
diff --git a/src/portal/py/modules/pixiv_web.py b/src/portal/py/modules/pixiv_web.py
index 3c04202..e7399dc 100644
--- a/src/portal/py/modules/pixiv_web.py
+++ b/src/portal/py/modules/pixiv_web.py
@@ -33,8 +33,8 @@ class PixivWebBase(Search):
def parse_pages(self, data: ParsedJson) -> dict[str, Media]:
media = {}
- for i, m in enumerate(data):
- media[str(i)] = Image(url=Url(m['urls']['original']), thumbnail_url=Url(m['urls']['small']))
+ for index, m in enumerate(data):
+ media[str(index)] = Image(url=Url(m['urls']['original']), thumbnail_url=Url(m['urls']['small']))
return media
def parse_ugoira(self, data: ParsedJson, thumbnail_url: str) -> Animation:
@@ -43,7 +43,7 @@ class PixivWebBase(Search):
animation.frames.append((frame['file'], frame['delay']))
return animation
- def seek_profile_picture_url(self, user_illusts: ParsedJson) -> Optional[Url]:
+ def search_profile_picture_url(self, user_illusts: ParsedJson) -> Optional[Url]:
for illust in user_illusts.values():
if illust is None:
continue
@@ -52,7 +52,7 @@ class PixivWebBase(Search):
return None
def create_tag(self, data: ParsedJson) -> Tag:
- kwargs= {}
+ kwargs = {}
kwargs['type'] = TagType.GENERAL
kwargs['name'] = data['tag']
kwargs['alts'] = {}
@@ -63,26 +63,27 @@ class PixivWebBase(Search):
kwargs['alts']['en'] = data['translation']['en']
return Tag(**kwargs)
- def create_post(self, data: ParsedJson, pages: Optional[ParsedJson] = None, ugoira: Optional[ParsedJson] = None) -> Optional[Post]:
+ def create_post(self, data: ParsedJson, pages: Optional[ParsedJson] = None, ugoira: Optional[ParsedJson] = None, retrieved_date: Optional[float] = None) -> Optional[Post]:
kwargs = {}
kwargs['type'] = PostType.POST
illust_id = data['illustId']
unique_id = f'pixiv:i:{illust_id}'
kwargs['unique_id'] = unique_id
kwargs['raw_responses'] = {}
- kwargs['raw_responses']['api'] = json.dumps(data)
+ kwargs['raw_responses']['api'] = json.dumps(data, separators=(',', ':'), indent=None)
kwargs['url'] = f'https://www.pixiv.net/{LANG}/artworks/{data['illustId']}'
create_date = reformat_pixiv_date(data['createDate']).timestamp()
- current_date = get_current_utc_time().timestamp()
+ if not retrieved_date:
+ retrieved_date = get_current_utc_time().timestamp()
kwargs['dates'] = [
Date(DateType.CREATED, (create_date, create_date), DateMeta.NONE),
- Date(DateType.RETRIEVED, (current_date, current_date), DateMeta.NONE)
+ Date(DateType.RETRIEVED, (retrieved_date, retrieved_date), DateMeta.NONE)
]
upload_date = reformat_pixiv_date(data['uploadDate']).timestamp()
if upload_date != create_date:
kwargs['dates'].append(Date(DateType.EDITED, (upload_date, upload_date), DateMeta.NONE))
user = User(unique_id=f'pixiv:u:{data['userId']}', username=data['userAccount'], display_name=data['userName'])
- profile_picture_url = self.seek_profile_picture_url(data['userIllusts'])
+ profile_picture_url = self.search_profile_picture_url(data['userIllusts'])
if profile_picture_url:
user.profile_picture_url = profile_picture_url
kwargs['author'] = user
@@ -124,19 +125,6 @@ class PixivWebBase(Search):
self.module.add_to_map(unique_id, post)
return post
- def remake_post(self, raw_responses: ParsedJson, original_date: float) -> Optional[Post]:
- api = json.loads(raw_responses['api'])
- pages = raw_responses.get('pages', None)
- if pages:
- pages = json.loads(pages)
- ugoira = raw_responses.get('ugoira', None)
- if ugoira:
- ugoira = json.loads(ugoira)
- post = self.create_post(api, pages, ugoira)
- if post:
- post.dates[DateType.RETRIEVED] = original_date
- return post
-
def request_illust(self, illust_id: int) -> Optional[Post]:
url = f'{BASE_URL}/illust/{illust_id}?lang={LANG}&version={VERSION}'
obj = self.check_api_response(self.module.do_request(Method.GET, url))
@@ -151,6 +139,16 @@ class PixivWebBase(Search):
self.module.add_to_map(unique_id, user)
return user
+ def remake_post(self, raw_responses: ParsedJson, original_date: float) -> Optional[Post]:
+ api = json.loads(raw_responses['api'])
+ pages = raw_responses.get('pages', None)
+ if pages:
+ pages = json.loads(pages)
+ ugoira = raw_responses.get('ugoira', None)
+ if ugoira:
+ ugoira = json.loads(ugoira)
+ return self.create_post(api, pages, ugoira, original_date)
+
class PixivWebSearch(PixivWebBase):
def __init__(self, userdata: Any, arg: str) -> None:
super().__init__(userdata)
@@ -178,23 +176,6 @@ class PixivWebSearch(PixivWebBase):
self.pages[num].append(post.unique_id)
return True
-class PixivWebIllust(PixivWebBase):
- def __init__(self, userdata: Any, arg: str) -> None:
- super().__init__(userdata)
- try:
- self.id: int = int(arg)
- except ValueError:
- self.errored = True
-
- def request_page(self, num: int) -> bool:
- post = self.request_illust(self.id)
- if not post:
- self.errored = True
- return False
- self.pages[num] = [post.unique_id]
- self.completed = True
- return True
-
class PixivWebUser(PixivWebBase):
def __init__(self, userdata: Any, arg: str) -> None:
super().__init__(userdata)
@@ -285,15 +266,32 @@ class PixivWebFollowing(PixivWebBase):
self.pages[num].append(self.create_user(user).unique_id)
return True
+class PixivWebIllust(PixivWebBase):
+ def __init__(self, userdata: Any, arg: str) -> None:
+ super().__init__(userdata)
+ try:
+ self.id: int = int(arg)
+ except ValueError:
+ self.errored = True
+
+ def request_page(self, num: int) -> bool:
+ post = self.request_illust(self.id)
+ if not post:
+ self.errored = True
+ return False
+ self.pages[num] = [post.unique_id]
+ self.completed = True
+ return True
+
class PixivWebModule(Module):
def __init__(self, sessid: str, user_id: str) -> None:
super().__init__()
self.parser: QueryParser = QueryParser(self, 'search')
self.parser.add_command('search', PixivWebSearch)
- self.parser.add_command('illust', PixivWebIllust)
self.parser.add_command('user', PixivWebUser)
self.parser.add_command('bookmarks', PixivWebBookmarks)
self.parser.add_command('following', PixivWebFollowing)
+ self.parser.add_command('illust', PixivWebIllust)
self.headers['User-Agent'] = USER_AGENT
self.headers['Accept-Encoding'] = 'gzip, deflate, br, zstd'
self.headers['Accept-Language'] = 'en-US,en;q=0.5'
diff --git a/src/portal/py/modules/twitter.py b/src/portal/py/modules/twitter.py
index 2f45ec1..93286c3 100644
--- a/src/portal/py/modules/twitter.py
+++ b/src/portal/py/modules/twitter.py
@@ -3,15 +3,15 @@ import http.cookiejar
from typing import Optional, Any, Iterator
from datetime import timezone
from base import USER_AGENT, Search, Module
-from post import Url, PostType, DateType, PostRef, Post, User, Media, Image, Video
+from post import Url, PostType, Date, DateType, DateMeta, PostRef, Post, User, Media, Image, Video
from query_parser import QueryParser
from modules.common import get_current_utc_time
# GraphQL API
-from snscrape.modules import twitter
from snscrape.base import ScraperException
+from snscrape.modules import twitter
from snscrape.modules.twitter import (Tweet, TweetRef, Tombstone, UserRef,
- TwitterSearchScraper, TwitterProfileScraper, TwitterUserScraper, TwitterTweetScraper)
+ TwitterSearchScraper, TwitterSearchScraperMode, TwitterProfileScraper, TwitterUserScraper, TwitterTweetScraper)
class TwitterScrapeBase(Search):
# Posts per emulated page because snscrape returns an iterator.
@@ -20,7 +20,7 @@ class TwitterScrapeBase(Search):
# Don't allow requests for a page more than this amount past the current page.
REACH_LIMIT = 4
- def __init__(self, userdata: Any):
+ def __init__(self, userdata: Any) -> None:
super().__init__()
self.module: TwitterScrapeModule = userdata
self.iterator: Iterator[Tweet | TweetRef | Tombstone]
@@ -28,38 +28,39 @@ class TwitterScrapeBase(Search):
def create_media_url(self, url: str) -> Url:
format = url.find('format=')
+ ext = None
if format >= 0:
- ext = url[format + 7:format + 10]
- else:
- question = url.rfind('?')
- if question >= 0:
- url = url[0:question]
- ext = url.split('.')[-1]
+ ext = url[format + 7:]
+ amp = ext.find('&')
+ if amp >= 0:
+ ext = ext[:amp]
return Url(url, ext)
def parse_media(self, data: list[twitter.Medium]) -> dict[str, Media]:
media = {}
- for i, m in enumerate(data):
+ for index, m in enumerate(data):
if isinstance(m, twitter.Photo):
- media[str(i)] = Image(url=self.create_media_url(m.fullUrl), thumbnail_url=self.create_media_url(m.previewUrl))
+ media[str(index)] = Image(url=self.create_media_url(m.fullUrl), thumbnail_url=self.create_media_url(m.previewUrl))
elif isinstance(m, twitter.Video) or isinstance(m, twitter.Gif):
videos = sorted(m.variants, key=lambda x: x.bitrate if x.bitrate else 0, reverse=True)
- media[str(i)] = Video(url=self.create_media_url(videos[0].url), thumbnail_url=self.create_media_url(m.thumbnailUrl))
+ media[str(index)] = Video(url=self.create_media_url(videos[0].url), thumbnail_url=self.create_media_url(m.thumbnailUrl))
return media
def create_post(self, tweet: Tweet | TweetRef | Tombstone) -> Post:
kwargs = {}
+ kwargs['module'] = 'twitter_scrape'
kwargs['unique_id'] = f'twitter:t:{tweet.id}'
if isinstance(tweet, TweetRef) or isinstance(tweet, Tombstone):
kwargs['type'] = PostType.TOMBSTONE
return Post(**kwargs)
kwargs['raw_responses'] = tweet.rawResponses
kwargs['url'] = tweet.url
- # @TODO: Replace correct?
- kwargs['dates'] = {
- DateType.CREATED: tweet.date.replace(tzinfo=timezone.utc).timestamp(),
- DateType.RETRIEVED: get_current_utc_time().timestamp()
- }
+ create_date = tweet.date.replace(tzinfo=timezone.utc).timestamp()
+ current_date = get_current_utc_time().timestamp()
+ kwargs['dates'] = [
+ Date(DateType.CREATED, (create_date, create_date), DateMeta.NONE),
+ Date(DateType.RETRIEVED, (current_date, current_date), DateMeta.NONE)
+ ]
author = User(unique_id=f'twitter:u:{tweet.user.id}')
if not isinstance(tweet.user, UserRef):
author.username = tweet.user.username
@@ -116,40 +117,40 @@ class TwitterScrapeBase(Search):
if num - self.page >= self.REACH_LIMIT:
return False
request_satisfied = False
- for i in range(self.page, num + 1):
+ for index in range(self.page, num + 1):
if self.completed:
break
- if i in self.pages:
+ if index in self.pages:
continue
- self.pages[i] = []
- if not self.step_iterator_for_page(i):
+ self.pages[index] = []
+ if not self.step_iterator_for_page(index):
break
- self.page = i
- if i == num:
+ self.page = index
+ if index == num:
request_satisfied = True
return request_satisfied
class TwitterScrapeSearch(TwitterScrapeBase):
- def __init__(self, userdata: Any, arg: str):
+ def __init__(self, userdata: Any, arg: str) -> None:
super().__init__(userdata)
- self.iterator = TwitterSearchScraper(arg, top=True, cookies=self.module.cookies).get_items()
+ self.iterator = TwitterSearchScraper(arg, mode=TwitterSearchScraperMode.TOP, cookies=self.module.cookies).get_items()
class TwitterScrapeUser(TwitterScrapeBase):
- def __init__(self, userdata: Any, arg: str):
+ def __init__(self, userdata: Any, arg: str) -> None:
super().__init__(userdata)
if arg.startswith('@'):
arg = arg[1:]
self.iterator = TwitterUserScraper(arg, cookies=self.module.cookies).get_items()
class TwitterScrapeProfile(TwitterScrapeBase):
- def __init__(self, userdata: Any, arg: str):
+ def __init__(self, userdata: Any, arg: str) -> None:
super().__init__(userdata)
if arg.startswith('@'):
arg = arg[1:]
self.iterator = TwitterProfileScraper(arg, cookies=self.module.cookies).get_items()
class TwitterScrapeTweet(TwitterScrapeBase):
- def __init__(self, userdata: Any, arg: str):
+ def __init__(self, userdata: Any, arg: str) -> None:
super().__init__(userdata)
if arg.startswith('https://'):
arg = arg[arg.rfind('/') + 1:]
@@ -159,7 +160,7 @@ class TwitterScrapeTweet(TwitterScrapeBase):
self.iterator = TwitterTweetScraper(arg, cookies=self.module.cookies).get_items()
class TwitterScrapeModule(Module):
- def __init__(self, cookies_path: str):
+ def __init__(self, cookies_path: str) -> None:
super().__init__()
self.parser: QueryParser = QueryParser(self, 'search')
self.parser.add_command('search', TwitterScrapeSearch)
diff --git a/src/portal/py/post.py b/src/portal/py/post.py
index 1a285c2..f173d0e 100644
--- a/src/portal/py/post.py
+++ b/src/portal/py/post.py
@@ -4,13 +4,6 @@ from typing import Optional, Any
from enum import IntEnum, IntFlag
from json import JSONEncoder
-'''
-List of Things:
- - Social media posts
- - Albums
- - Loose files
-'''
-
class PostType(IntEnum):
UNKNOWN = 0
POST = 1
@@ -54,27 +47,26 @@ def df(c: Any) -> Any:
return field(default_factory=lambda: c)
def default_url_ext_guess(url: str) -> str:
- # @TODO: List of known extensions.
+ slash = url.find('/')
+ if slash < 0:
+ return ''
+ url = url[slash:]
question = url.rfind('?')
if question >= 0:
- guess = url[0:question].rsplit('.')[-1]
- else:
- guess = url.rsplit('.')[-1]
- return guess
+ url = url[0:question]
+ s = url.split('.')
+ if len(s) > 1:
+ return s[-1]
+ return ''
@dataclasses.dataclass
class Url():
url: str
ext: str
-
def __init__(self, url: str, ext: str = None) -> None:
self.url = url
if not ext:
- ext_guess = default_url_ext_guess(url)
- if ext_guess:
- ext = ext_guess
- else:
- ext = 'unknown'
+ ext = default_url_ext_guess(url)
self.ext = ext
@dataclasses.dataclass
@@ -158,6 +150,7 @@ class PostRef():
'''
V4:
- Improved date.
+ - Track origin module.
- Ends when all current modules are ported and/or completed.
V5:
- Formatted body/text.
@@ -165,6 +158,7 @@ V5:
@dataclasses.dataclass
class Post():
version: int = 4
+ module: str = ''
type: PostType = PostType.UNKNOWN
unique_id: str = ''
raw_responses: dict[str, str] = df({})
diff --git a/src/portal/py/query_parser.py b/src/portal/py/query_parser.py
index cd6c1e6..bf276ce 100644
--- a/src/portal/py/query_parser.py
+++ b/src/portal/py/query_parser.py
@@ -20,7 +20,7 @@ class QueryParser():
sp = query.split(' ')
- for i, word in enumerate(sp):
+ for index, word in enumerate(sp):
col = word.find(':')
if col == -1:
continue
@@ -34,7 +34,7 @@ class QueryParser():
else:
arg = None
search = self.commands[command]
- if i == 0:
+ if index == 0:
if len(sp) > 1:
query = query.replace(word + ' ', '', 1)
else:
diff --git a/src/portal/py/test.py b/src/portal/py/test.py
index 0ca89a4..e862bf0 100644
--- a/src/portal/py/test.py
+++ b/src/portal/py/test.py
@@ -1,2 +1,4 @@
#import tests.test
-import tests.archive_query
+import tests.touch_all
+#import tests.rewrite_post
+#import tests.archive_query
diff --git a/src/portal/py/tests/archive_query.py b/src/portal/py/tests/archive_query.py
index 4cc1e1e..3de4329 100644
--- a/src/portal/py/tests/archive_query.py
+++ b/src/portal/py/tests/archive_query.py
@@ -15,8 +15,8 @@ mode = 'pixiv_web'
#mode = 'fanbox'
#mode = 'patreon'
#mode = 'instagram'
-#cmd = 'user'
-cmd = 'bookmarks'
+cmd = 'user'
+#cmd = 'bookmarks'
#cmd = 'search'
module = ALL_MODULES[mode][0]
if not module.init():
@@ -180,8 +180,8 @@ class QueryDownloadThread(threading.Thread):
with queue_cond:
queue_cond.notify()
-#RUNTIME_PATH = "./run"
-RUNTIME_PATH = "/mnt/store/files/tmp/run"
+#RUNTIME_PATH = './run'
+RUNTIME_PATH = '/mnt/store/files/tmp/run'
LOG_FILE = f'{RUNTIME_PATH}/archive4.log'
ARG_FILE = f'{RUNTIME_PATH}/completed_args4.log'
@@ -216,10 +216,7 @@ def download_args():
os.mkdir(output_dir)
os.mkdir(f'{output_dir}/raw_responses')
- #args = module.search(f'following:{22781328}')
- # @TODO:
args = []
- # twitter test: butcha_u, sep__rina, nagayori000, sattinittas,
threads = []
start = True
diff --git a/src/portal/py/tests/old_twitter_api.py b/src/portal/py/tests/old_twitter_api.py
index 40d0346..4427dfd 100644
--- a/src/portal/py/tests/old_twitter_api.py
+++ b/src/portal/py/tests/old_twitter_api.py
@@ -27,14 +27,14 @@ log = Logger(LOG_FILE)
compat_thread = QueryDownloadThread(log, None, None, sys.argv[1])
def compat_media_download(obj, post, output_base):
- for i, key in enumerate(post.media.keys()):
+ for index, key in enumerate(post.media.keys()):
media = post.media[key]
url = media.url
media_path = f'{output_base}_media{key}.{url.ext}'
if os.path.isfile(media_path):
obj.log.write(f'Skipping media {media_path}.')
continue
- origin_path = f'{origin_base}/{post.author.unique_id.replace(':', '_')}/{post.unique_id.replace(':', '_')}_media{i}.{url.ext}'
+ origin_path = f'{origin_base}/{post.author.unique_id.replace(':', '_')}/{post.unique_id.replace(':', '_')}_media{index}.{url.ext}'
if os.path.isfile(origin_path):
shutil.copyfile(origin_path, media_path)
#post.media[key].url.url = url.url.replace('name=orig', 'name=large')
diff --git a/src/portal/py/tests/rewrite_post.py b/src/portal/py/tests/rewrite_post.py
index 3bb152b..f00cfc7 100644
--- a/src/portal/py/tests/rewrite_post.py
+++ b/src/portal/py/tests/rewrite_post.py
@@ -2,20 +2,24 @@ import sys
import os
import gzip
import json
-import hashlib
sys.path.append('../py')
-import config
-#from post import PostEncoder, DateType
-#from modules.pixiv_web import PixivWebBase, PixivWebModule
-#from modules.twitter import TwitterScrapeBase, TwitterScrapeModule
-from logger import Logger
+from modules import ALL_MODULES
+from post import PostEncoder
+from modules.pixiv_app import PixivAppBase
-#module = PixivWebModule(config.PIXIV_SESSID)
-#search = PixivWebBase(module)
+module = ALL_MODULES['pixiv_app'][0]
+module.init()
+search = PixivAppBase(module)
-log = Logger('deleted_posts.log')
+target = sys.argv[1]
+with gzip.open(target, 'rb') as f:
+ obj = json.loads(f.read())
+ mtime = os.path.getmtime(target)
+ print(json.dumps(search.remake_post(obj['raw_responses'], mtime), cls=PostEncoder))
'''
+log = Logger('deleted_posts.log')
+
def rewrite_post(path):
print(f'Rewriting post @ {path}')
with gzip.open(path, 'rb') as f:
@@ -27,7 +31,6 @@ def rewrite_post(path):
#print(json.dumps(post, indent=4, cls=PostEncoder))
with gzip.open(path, 'wb') as f:
f.write(json.dumps(post, cls=PostEncoder).encode('utf-8'))
-'''
def dump_raw_response(path, target):
print(f'Extracting raw_response from {path}')
@@ -54,3 +57,4 @@ for user in os.listdir(target):
if file.split('.')[-1] == 'gz':
dump_raw_response(f'{target}/{user}/{file}', raw_responses)
#rewrite_post(f'{target}/{user}/{file}')
+'''
diff --git a/src/portal/py/tests/test.py b/src/portal/py/tests/test.py
index ef166d9..d3e5640 100644
--- a/src/portal/py/tests/test.py
+++ b/src/portal/py/tests/test.py
@@ -1,13 +1,4 @@
-import sys
-import json
-import httpx
-import os.path
-from base import Method
-from typing import Optional, Any
-from post import DateType, DateMeta, Date, PostEncoder, Post, MediaType
-from modules import ALL_MODULES
-from twitter.scraper import Scraper
-
+#from twitter.scraper import Scraper
#scraper = Scraper(cookies = {
# 'ct0': '',
# 'auth_token': ''
@@ -15,101 +6,3 @@ from twitter.scraper import Scraper
#
#media = scraper.media([1557919548904419328], limit=10)
#print(media)
-
-#output = '/mnt/store/files/ext/patreon_tmp/FPSBlyck'
-#
-#module = ALL_MODULES['patreon'][0]
-#module.init()
-#search = module.search('FPSBlyck')
-#page_num = 0
-#mark = False
-#while not mark:
-# page = search.get_page(page_num)
-# if not page:
-# break
-# page_num += 1
-# for unique_id in page:
-# if unique_id == 'patreon:p:29707872':
-# print('hit mark')
-# mark = True
-# post = module.get_item(unique_id)
-# print(json.dumps(post, indent=4, cls=PostEncoder))
-# for key, m in post.media.items():
-# if m.type == MediaType.FILE:
-# path = f'{output}/{post.unique_id.replace(':', '_')}_{m.name}'
-# if not os.path.isfile(path):
-# dl = module.get_download(post.unique_id, key)
-# retries = 3
-# r: Optional[httpx.Response] = None
-# while retries >= 0:
-# try:
-# r = httpx.get(dl['urls'][0].url, headers=dl['headers'], cookies=dl['cookies'], follow_redirects=True)
-# except:
-# retries -= 1
-# else:
-# break
-# if r:
-# with open(path, 'wb+') as f:
-# f.write(r.content)
-# else:
-# print('Download failed')
-# else:
-# print('Skipping file')
-
-
-#module = ALL_MODULES['pixiv_web'][0]
-#module.init()
-#search = module.search('illust:91352621')
-#page = search.get_page(0)
-#for unique_id in page:
-# post = module.get_item(unique_id)
-# print(json.dumps(post, indent=4, cls=PostEncoder))
-
-module = ALL_MODULES['instagram'][0]
-module.init()
-search = module.search('plottttwistttttt')
-page = search.get_page(0)
-for unique_id in page:
- post = module.get_item(unique_id)
- print(json.dumps(post, indent=4, cls=PostEncoder))
-
-#d = Date(DateType.EDITED, (0.0, 0.0), DateMeta.EDITED_AT_UNKNOWN_TIME)
-#d = Date(DateType.EDITED, (0.0, 0.0), DateMeta.NONE)
-#print(json.dumps(d, indent=4, cls=PostEncoder))
-#sys.exit(1)
-
-#module = ALL_MODULES['fanbox'][0]
-#module = ALL_MODULES['pixiv_web'][0]
-#module = ALL_MODULES['youtube'][0]
-#module = ALL_MODULES['twitter'][0]
-#module.init()
-
-#search = module.search('opium_00pium')
-#search = module.search('search:ddd')
-#page = search.get_page(0)
-#for unique_id in page:
-# post = module.get_item(unique_id)
-# print(json.dumps(post, indent=4, cls=PostEncoder))
-# break
-
-#post = module.get_item(page[0])
-#for key in post.media.keys():
-# download_params = module.get_download(post.unique_id, key)
-# for url in download_params['urls']:
-# #response = module.do_request(Method.GET, url.url)
-# r = httpx.get(url.url, headers=download_params['headers'])
-# with open(f'test_{key}.{url.ext}', 'wb+') as f:
-# f.write(r.content)
-
-#print(json.dumps(page, indent=4, cls=PostEncoder))
-#for i in range(0, len(post.media)):
-# download_params = module.get_download(post.unique_id, i)
-# r = httpx.get(download_params['url'], headers=download_params['headers'])
-# if r.status_code != 200:
-# print('error{}'.format(r.status_code))
-# else:
-# with open('test{}.png'.format(i), 'wb+') as f:
-# f.write(r.content)
-
-#print(json.dumps(page, indent=4, cls=PostEncoder))
-#print(page)
diff --git a/src/portal/py/tests/touch_all.py b/src/portal/py/tests/touch_all.py
new file mode 100644
index 0000000..6d6bfa1
--- /dev/null
+++ b/src/portal/py/tests/touch_all.py
@@ -0,0 +1,79 @@
+import json
+from typing import Optional, Any
+from post import PostEncoder
+from modules import ALL_MODULES
+
+'''
+post = module.get_item(page[0])
+for key in post.media.keys():
+ download_params = module.get_download(post.unique_id, key)
+ for url in download_params['urls']:
+ #response = module.do_request(Method.GET, url.url)
+ r = httpx.get(url.url, headers=download_params['headers'])
+ if r.status_code != 200:
+ print('error{}'.format(r.status_code))
+ else:
+ with open(f'test_{key}.{url.ext}', 'wb+') as f:
+ f.write(r.content)
+'''
+
+def print_page(module: Any, page: Optional[list[str]]) -> None:
+ if not page:
+ print('Page is None')
+ return
+ count = 0
+ for unique_id in page:
+ post = module.get_item(unique_id)
+ print(json.dumps(post, indent=4, cls=PostEncoder))
+ count = count + 1
+ if count == 2:
+ break
+
+''' Blocked on certain endpoints, snscrape is outdated.
+module = ALL_MODULES['twitter'][0]
+module.init()
+
+search = module.search('search:#小関麗奈誕生祭2024')
+print_page(module, search.get_page(0))
+search = module.search('user:xuuikie')
+print_page(module, search.get_page(0))
+search = module.search('profile:@barackobama')
+print_page(module, search.get_page(0))
+search = module.search('tweet:1905966372288434680')
+print_page(module, search.get_page(0))
+'''
+
+'''
+module = ALL_MODULES['pixiv_app'][0]
+module.init()
+search = module.search('search:サムス・アラン')
+print_page(module, search.get_page(0))
+search = module.search('user:757584')
+print_page(module, search.get_page(0))
+search = module.search('bookmarks:2034628')
+print_page(module, search.get_page(0))
+search = module.search('following:22781328')
+print_page(module, search.get_page(0))
+search = module.search('illust:91352621')
+print_page(module, search.get_page(0))
+search = module.search('illust:26497574') # Deleted.
+print_page(module, search.get_page(0))
+'''
+
+''' No on-demand loading (Will load entire user before returning).
+module = ALL_MODULES['pixiv_web'][0]
+module.init()
+
+search = module.search('search:サムス・アラン')
+print_page(module, search.get_page(0))
+search = module.search('user:757584')
+print_page(module, search.get_page(0))
+search = module.search('bookmarks:2034628')
+print_page(module, search.get_page(0))
+search = module.search('following:22781328')
+print_page(module, search.get_page(0))
+search = module.search('illust:91352621')
+print_page(module, search.get_page(0))
+search = module.search('illust:100455669') # Deleted.
+print_page(module, search.get_page(0))
+'''
diff --git a/src/portal/py/tlds.py b/src/portal/py/tlds.py
new file mode 100644
index 0000000..d0e4cdd
--- /dev/null
+++ b/src/portal/py/tlds.py
@@ -0,0 +1,1445 @@
+# https://data.iana.org/TLD/tlds-alpha-by-domain.txt
+# Version 2025051100, Last Updated Sun May 11 07:07:01 2025 UTC
+tlds = [
+ 'aaa',
+ 'aarp',
+ 'abb',
+ 'abbott',
+ 'abbvie',
+ 'abc',
+ 'able',
+ 'abogado',
+ 'abudhabi',
+ 'ac',
+ 'academy',
+ 'accenture',
+ 'accountant',
+ 'accountants',
+ 'aco',
+ 'actor',
+ 'ad',
+ 'ads',
+ 'adult',
+ 'ae',
+ 'aeg',
+ 'aero',
+ 'aetna',
+ 'af',
+ 'afl',
+ 'africa',
+ 'ag',
+ 'agakhan',
+ 'agency',
+ 'ai',
+ 'aig',
+ 'airbus',
+ 'airforce',
+ 'airtel',
+ 'akdn',
+ 'al',
+ 'alibaba',
+ 'alipay',
+ 'allfinanz',
+ 'allstate',
+ 'ally',
+ 'alsace',
+ 'alstom',
+ 'am',
+ 'amazon',
+ 'americanexpress',
+ 'americanfamily',
+ 'amex',
+ 'amfam',
+ 'amica',
+ 'amsterdam',
+ 'analytics',
+ 'android',
+ 'anquan',
+ 'anz',
+ 'ao',
+ 'aol',
+ 'apartments',
+ 'app',
+ 'apple',
+ 'aq',
+ 'aquarelle',
+ 'ar',
+ 'arab',
+ 'aramco',
+ 'archi',
+ 'army',
+ 'arpa',
+ 'art',
+ 'arte',
+ 'as',
+ 'asda',
+ 'asia',
+ 'associates',
+ 'at',
+ 'athleta',
+ 'attorney',
+ 'au',
+ 'auction',
+ 'audi',
+ 'audible',
+ 'audio',
+ 'auspost',
+ 'author',
+ 'auto',
+ 'autos',
+ 'aw',
+ 'aws',
+ 'ax',
+ 'axa',
+ 'az',
+ 'azure',
+ 'ba',
+ 'baby',
+ 'baidu',
+ 'banamex',
+ 'band',
+ 'bank',
+ 'bar',
+ 'barcelona',
+ 'barclaycard',
+ 'barclays',
+ 'barefoot',
+ 'bargains',
+ 'baseball',
+ 'basketball',
+ 'bauhaus',
+ 'bayern',
+ 'bb',
+ 'bbc',
+ 'bbt',
+ 'bbva',
+ 'bcg',
+ 'bcn',
+ 'bd',
+ 'be',
+ 'beats',
+ 'beauty',
+ 'beer',
+ 'berlin',
+ 'best',
+ 'bestbuy',
+ 'bet',
+ 'bf',
+ 'bg',
+ 'bh',
+ 'bharti',
+ 'bi',
+ 'bible',
+ 'bid',
+ 'bike',
+ 'bing',
+ 'bingo',
+ 'bio',
+ 'biz',
+ 'bj',
+ 'black',
+ 'blackfriday',
+ 'blockbuster',
+ 'blog',
+ 'bloomberg',
+ 'blue',
+ 'bm',
+ 'bms',
+ 'bmw',
+ 'bn',
+ 'bnpparibas',
+ 'bo',
+ 'boats',
+ 'boehringer',
+ 'bofa',
+ 'bom',
+ 'bond',
+ 'boo',
+ 'book',
+ 'booking',
+ 'bosch',
+ 'bostik',
+ 'boston',
+ 'bot',
+ 'boutique',
+ 'box',
+ 'br',
+ 'bradesco',
+ 'bridgestone',
+ 'broadway',
+ 'broker',
+ 'brother',
+ 'brussels',
+ 'bs',
+ 'bt',
+ 'build',
+ 'builders',
+ 'business',
+ 'buy',
+ 'buzz',
+ 'bv',
+ 'bw',
+ 'by',
+ 'bz',
+ 'bzh',
+ 'ca',
+ 'cab',
+ 'cafe',
+ 'cal',
+ 'call',
+ 'calvinklein',
+ 'cam',
+ 'camera',
+ 'camp',
+ 'canon',
+ 'capetown',
+ 'capital',
+ 'capitalone',
+ 'car',
+ 'caravan',
+ 'cards',
+ 'care',
+ 'career',
+ 'careers',
+ 'cars',
+ 'casa',
+ 'case',
+ 'cash',
+ 'casino',
+ 'cat',
+ 'catering',
+ 'catholic',
+ 'cba',
+ 'cbn',
+ 'cbre',
+ 'cc',
+ 'cd',
+ 'center',
+ 'ceo',
+ 'cern',
+ 'cf',
+ 'cfa',
+ 'cfd',
+ 'cg',
+ 'ch',
+ 'chanel',
+ 'channel',
+ 'charity',
+ 'chase',
+ 'chat',
+ 'cheap',
+ 'chintai',
+ 'christmas',
+ 'chrome',
+ 'church',
+ 'ci',
+ 'cipriani',
+ 'circle',
+ 'cisco',
+ 'citadel',
+ 'citi',
+ 'citic',
+ 'city',
+ 'ck',
+ 'cl',
+ 'claims',
+ 'cleaning',
+ 'click',
+ 'clinic',
+ 'clinique',
+ 'clothing',
+ 'cloud',
+ 'club',
+ 'clubmed',
+ 'cm',
+ 'cn',
+ 'co',
+ 'coach',
+ 'codes',
+ 'coffee',
+ 'college',
+ 'cologne',
+ 'com',
+ 'commbank',
+ 'community',
+ 'company',
+ 'compare',
+ 'computer',
+ 'comsec',
+ 'condos',
+ 'construction',
+ 'consulting',
+ 'contact',
+ 'contractors',
+ 'cooking',
+ 'cool',
+ 'coop',
+ 'corsica',
+ 'country',
+ 'coupon',
+ 'coupons',
+ 'courses',
+ 'cpa',
+ 'cr',
+ 'credit',
+ 'creditcard',
+ 'creditunion',
+ 'cricket',
+ 'crown',
+ 'crs',
+ 'cruise',
+ 'cruises',
+ 'cu',
+ 'cuisinella',
+ 'cv',
+ 'cw',
+ 'cx',
+ 'cy',
+ 'cymru',
+ 'cyou',
+ 'cz',
+ 'dad',
+ 'dance',
+ 'data',
+ 'date',
+ 'dating',
+ 'datsun',
+ 'day',
+ 'dclk',
+ 'dds',
+ 'de',
+ 'deal',
+ 'dealer',
+ 'deals',
+ 'degree',
+ 'delivery',
+ 'dell',
+ 'deloitte',
+ 'delta',
+ 'democrat',
+ 'dental',
+ 'dentist',
+ 'desi',
+ 'design',
+ 'dev',
+ 'dhl',
+ 'diamonds',
+ 'diet',
+ 'digital',
+ 'direct',
+ 'directory',
+ 'discount',
+ 'discover',
+ 'dish',
+ 'diy',
+ 'dj',
+ 'dk',
+ 'dm',
+ 'dnp',
+ 'do',
+ 'docs',
+ 'doctor',
+ 'dog',
+ 'domains',
+ 'dot',
+ 'download',
+ 'drive',
+ 'dtv',
+ 'dubai',
+ 'dunlop',
+ 'dupont',
+ 'durban',
+ 'dvag',
+ 'dvr',
+ 'dz',
+ 'earth',
+ 'eat',
+ 'ec',
+ 'eco',
+ 'edeka',
+ 'edu',
+ 'education',
+ 'ee',
+ 'eg',
+ 'email',
+ 'emerck',
+ 'energy',
+ 'engineer',
+ 'engineering',
+ 'enterprises',
+ 'epson',
+ 'equipment',
+ 'er',
+ 'ericsson',
+ 'erni',
+ 'es',
+ 'esq',
+ 'estate',
+ 'et',
+ 'eu',
+ 'eurovision',
+ 'eus',
+ 'events',
+ 'exchange',
+ 'expert',
+ 'exposed',
+ 'express',
+ 'extraspace',
+ 'fage',
+ 'fail',
+ 'fairwinds',
+ 'faith',
+ 'family',
+ 'fan',
+ 'fans',
+ 'farm',
+ 'farmers',
+ 'fashion',
+ 'fast',
+ 'fedex',
+ 'feedback',
+ 'ferrari',
+ 'ferrero',
+ 'fi',
+ 'fidelity',
+ 'fido',
+ 'film',
+ 'final',
+ 'finance',
+ 'financial',
+ 'fire',
+ 'firestone',
+ 'firmdale',
+ 'fish',
+ 'fishing',
+ 'fit',
+ 'fitness',
+ 'fj',
+ 'fk',
+ 'flickr',
+ 'flights',
+ 'flir',
+ 'florist',
+ 'flowers',
+ 'fly',
+ 'fm',
+ 'fo',
+ 'foo',
+ 'food',
+ 'football',
+ 'ford',
+ 'forex',
+ 'forsale',
+ 'forum',
+ 'foundation',
+ 'fox',
+ 'fr',
+ 'free',
+ 'fresenius',
+ 'frl',
+ 'frogans',
+ 'frontier',
+ 'ftr',
+ 'fujitsu',
+ 'fun',
+ 'fund',
+ 'furniture',
+ 'futbol',
+ 'fyi',
+ 'ga',
+ 'gal',
+ 'gallery',
+ 'gallo',
+ 'gallup',
+ 'game',
+ 'games',
+ 'gap',
+ 'garden',
+ 'gay',
+ 'gb',
+ 'gbiz',
+ 'gd',
+ 'gdn',
+ 'ge',
+ 'gea',
+ 'gent',
+ 'genting',
+ 'george',
+ 'gf',
+ 'gg',
+ 'ggee',
+ 'gh',
+ 'gi',
+ 'gift',
+ 'gifts',
+ 'gives',
+ 'giving',
+ 'gl',
+ 'glass',
+ 'gle',
+ 'global',
+ 'globo',
+ 'gm',
+ 'gmail',
+ 'gmbh',
+ 'gmo',
+ 'gmx',
+ 'gn',
+ 'godaddy',
+ 'gold',
+ 'goldpoint',
+ 'golf',
+ 'goo',
+ 'goodyear',
+ 'goog',
+ 'google',
+ 'gop',
+ 'got',
+ 'gov',
+ 'gp',
+ 'gq',
+ 'gr',
+ 'grainger',
+ 'graphics',
+ 'gratis',
+ 'green',
+ 'gripe',
+ 'grocery',
+ 'group',
+ 'gs',
+ 'gt',
+ 'gu',
+ 'gucci',
+ 'guge',
+ 'guide',
+ 'guitars',
+ 'guru',
+ 'gw',
+ 'gy',
+ 'hair',
+ 'hamburg',
+ 'hangout',
+ 'haus',
+ 'hbo',
+ 'hdfc',
+ 'hdfcbank',
+ 'health',
+ 'healthcare',
+ 'help',
+ 'helsinki',
+ 'here',
+ 'hermes',
+ 'hiphop',
+ 'hisamitsu',
+ 'hitachi',
+ 'hiv',
+ 'hk',
+ 'hkt',
+ 'hm',
+ 'hn',
+ 'hockey',
+ 'holdings',
+ 'holiday',
+ 'homedepot',
+ 'homegoods',
+ 'homes',
+ 'homesense',
+ 'honda',
+ 'horse',
+ 'hospital',
+ 'host',
+ 'hosting',
+ 'hot',
+ 'hotels',
+ 'hotmail',
+ 'house',
+ 'how',
+ 'hr',
+ 'hsbc',
+ 'ht',
+ 'hu',
+ 'hughes',
+ 'hyatt',
+ 'hyundai',
+ 'ibm',
+ 'icbc',
+ 'ice',
+ 'icu',
+ 'id',
+ 'ie',
+ 'ieee',
+ 'ifm',
+ 'ikano',
+ 'il',
+ 'im',
+ 'imamat',
+ 'imdb',
+ 'immo',
+ 'immobilien',
+ 'in',
+ 'inc',
+ 'industries',
+ 'infiniti',
+ 'info',
+ 'ing',
+ 'ink',
+ 'institute',
+ 'insurance',
+ 'insure',
+ 'int',
+ 'international',
+ 'intuit',
+ 'investments',
+ 'io',
+ 'ipiranga',
+ 'iq',
+ 'ir',
+ 'irish',
+ 'is',
+ 'ismaili',
+ 'ist',
+ 'istanbul',
+ 'it',
+ 'itau',
+ 'itv',
+ 'jaguar',
+ 'java',
+ 'jcb',
+ 'je',
+ 'jeep',
+ 'jetzt',
+ 'jewelry',
+ 'jio',
+ 'jll',
+ 'jm',
+ 'jmp',
+ 'jnj',
+ 'jo',
+ 'jobs',
+ 'joburg',
+ 'jot',
+ 'joy',
+ 'jp',
+ 'jpmorgan',
+ 'jprs',
+ 'juegos',
+ 'juniper',
+ 'kaufen',
+ 'kddi',
+ 'ke',
+ 'kerryhotels',
+ 'kerryproperties',
+ 'kfh',
+ 'kg',
+ 'kh',
+ 'ki',
+ 'kia',
+ 'kids',
+ 'kim',
+ 'kindle',
+ 'kitchen',
+ 'kiwi',
+ 'km',
+ 'kn',
+ 'koeln',
+ 'komatsu',
+ 'kosher',
+ 'kp',
+ 'kpmg',
+ 'kpn',
+ 'kr',
+ 'krd',
+ 'kred',
+ 'kuokgroup',
+ 'kw',
+ 'ky',
+ 'kyoto',
+ 'kz',
+ 'la',
+ 'lacaixa',
+ 'lamborghini',
+ 'lamer',
+ 'land',
+ 'landrover',
+ 'lanxess',
+ 'lasalle',
+ 'lat',
+ 'latino',
+ 'latrobe',
+ 'law',
+ 'lawyer',
+ 'lb',
+ 'lc',
+ 'lds',
+ 'lease',
+ 'leclerc',
+ 'lefrak',
+ 'legal',
+ 'lego',
+ 'lexus',
+ 'lgbt',
+ 'li',
+ 'lidl',
+ 'life',
+ 'lifeinsurance',
+ 'lifestyle',
+ 'lighting',
+ 'like',
+ 'lilly',
+ 'limited',
+ 'limo',
+ 'lincoln',
+ 'link',
+ 'live',
+ 'living',
+ 'lk',
+ 'llc',
+ 'llp',
+ 'loan',
+ 'loans',
+ 'locker',
+ 'locus',
+ 'lol',
+ 'london',
+ 'lotte',
+ 'lotto',
+ 'love',
+ 'lpl',
+ 'lplfinancial',
+ 'lr',
+ 'ls',
+ 'lt',
+ 'ltd',
+ 'ltda',
+ 'lu',
+ 'lundbeck',
+ 'luxe',
+ 'luxury',
+ 'lv',
+ 'ly',
+ 'ma',
+ 'madrid',
+ 'maif',
+ 'maison',
+ 'makeup',
+ 'man',
+ 'management',
+ 'mango',
+ 'map',
+ 'market',
+ 'marketing',
+ 'markets',
+ 'marriott',
+ 'marshalls',
+ 'mattel',
+ 'mba',
+ 'mc',
+ 'mckinsey',
+ 'md',
+ 'me',
+ 'med',
+ 'media',
+ 'meet',
+ 'melbourne',
+ 'meme',
+ 'memorial',
+ 'men',
+ 'menu',
+ 'merckmsd',
+ 'mg',
+ 'mh',
+ 'miami',
+ 'microsoft',
+ 'mil',
+ 'mini',
+ 'mint',
+ 'mit',
+ 'mitsubishi',
+ 'mk',
+ 'ml',
+ 'mlb',
+ 'mls',
+ 'mm',
+ 'mma',
+ 'mn',
+ 'mo',
+ 'mobi',
+ 'mobile',
+ 'moda',
+ 'moe',
+ 'moi',
+ 'mom',
+ 'monash',
+ 'money',
+ 'monster',
+ 'mormon',
+ 'mortgage',
+ 'moscow',
+ 'moto',
+ 'motorcycles',
+ 'mov',
+ 'movie',
+ 'mp',
+ 'mq',
+ 'mr',
+ 'ms',
+ 'msd',
+ 'mt',
+ 'mtn',
+ 'mtr',
+ 'mu',
+ 'museum',
+ 'music',
+ 'mv',
+ 'mw',
+ 'mx',
+ 'my',
+ 'mz',
+ 'na',
+ 'nab',
+ 'nagoya',
+ 'name',
+ 'navy',
+ 'nba',
+ 'nc',
+ 'ne',
+ 'nec',
+ 'net',
+ 'netbank',
+ 'netflix',
+ 'network',
+ 'neustar',
+ 'new',
+ 'news',
+ 'next',
+ 'nextdirect',
+ 'nexus',
+ 'nf',
+ 'nfl',
+ 'ng',
+ 'ngo',
+ 'nhk',
+ 'ni',
+ 'nico',
+ 'nike',
+ 'nikon',
+ 'ninja',
+ 'nissan',
+ 'nissay',
+ 'nl',
+ 'no',
+ 'nokia',
+ 'norton',
+ 'now',
+ 'nowruz',
+ 'nowtv',
+ 'np',
+ 'nr',
+ 'nra',
+ 'nrw',
+ 'ntt',
+ 'nu',
+ 'nyc',
+ 'nz',
+ 'obi',
+ 'observer',
+ 'office',
+ 'okinawa',
+ 'olayan',
+ 'olayangroup',
+ 'ollo',
+ 'om',
+ 'omega',
+ 'one',
+ 'ong',
+ 'onl',
+ 'online',
+ 'ooo',
+ 'open',
+ 'oracle',
+ 'orange',
+ 'org',
+ 'organic',
+ 'origins',
+ 'osaka',
+ 'otsuka',
+ 'ott',
+ 'ovh',
+ 'pa',
+ 'page',
+ 'panasonic',
+ 'paris',
+ 'pars',
+ 'partners',
+ 'parts',
+ 'party',
+ 'pay',
+ 'pccw',
+ 'pe',
+ 'pet',
+ 'pf',
+ 'pfizer',
+ 'pg',
+ 'ph',
+ 'pharmacy',
+ 'phd',
+ 'philips',
+ 'phone',
+ 'photo',
+ 'photography',
+ 'photos',
+ 'physio',
+ 'pics',
+ 'pictet',
+ 'pictures',
+ 'pid',
+ 'pin',
+ 'ping',
+ 'pink',
+ 'pioneer',
+ 'pizza',
+ 'pk',
+ 'pl',
+ 'place',
+ 'play',
+ 'playstation',
+ 'plumbing',
+ 'plus',
+ 'pm',
+ 'pn',
+ 'pnc',
+ 'pohl',
+ 'poker',
+ 'politie',
+ 'porn',
+ 'post',
+ 'pr',
+ 'pramerica',
+ 'praxi',
+ 'press',
+ 'prime',
+ 'pro',
+ 'prod',
+ 'productions',
+ 'prof',
+ 'progressive',
+ 'promo',
+ 'properties',
+ 'property',
+ 'protection',
+ 'pru',
+ 'prudential',
+ 'ps',
+ 'pt',
+ 'pub',
+ 'pw',
+ 'pwc',
+ 'py',
+ 'qa',
+ 'qpon',
+ 'quebec',
+ 'quest',
+ 'racing',
+ 'radio',
+ 're',
+ 'read',
+ 'realestate',
+ 'realtor',
+ 'realty',
+ 'recipes',
+ 'red',
+ 'redstone',
+ 'redumbrella',
+ 'rehab',
+ 'reise',
+ 'reisen',
+ 'reit',
+ 'reliance',
+ 'ren',
+ 'rent',
+ 'rentals',
+ 'repair',
+ 'report',
+ 'republican',
+ 'rest',
+ 'restaurant',
+ 'review',
+ 'reviews',
+ 'rexroth',
+ 'rich',
+ 'richardli',
+ 'ricoh',
+ 'ril',
+ 'rio',
+ 'rip',
+ 'ro',
+ 'rocks',
+ 'rodeo',
+ 'rogers',
+ 'room',
+ 'rs',
+ 'rsvp',
+ 'ru',
+ 'rugby',
+ 'ruhr',
+ 'run',
+ 'rw',
+ 'rwe',
+ 'ryukyu',
+ 'sa',
+ 'saarland',
+ 'safe',
+ 'safety',
+ 'sakura',
+ 'sale',
+ 'salon',
+ 'samsclub',
+ 'samsung',
+ 'sandvik',
+ 'sandvikcoromant',
+ 'sanofi',
+ 'sap',
+ 'sarl',
+ 'sas',
+ 'save',
+ 'saxo',
+ 'sb',
+ 'sbi',
+ 'sbs',
+ 'sc',
+ 'scb',
+ 'schaeffler',
+ 'schmidt',
+ 'scholarships',
+ 'school',
+ 'schule',
+ 'schwarz',
+ 'science',
+ 'scot',
+ 'sd',
+ 'se',
+ 'search',
+ 'seat',
+ 'secure',
+ 'security',
+ 'seek',
+ 'select',
+ 'sener',
+ 'services',
+ 'seven',
+ 'sew',
+ 'sex',
+ 'sexy',
+ 'sfr',
+ 'sg',
+ 'sh',
+ 'shangrila',
+ 'sharp',
+ 'shell',
+ 'shia',
+ 'shiksha',
+ 'shoes',
+ 'shop',
+ 'shopping',
+ 'shouji',
+ 'show',
+ 'si',
+ 'silk',
+ 'sina',
+ 'singles',
+ 'site',
+ 'sj',
+ 'sk',
+ 'ski',
+ 'skin',
+ 'sky',
+ 'skype',
+ 'sl',
+ 'sling',
+ 'sm',
+ 'smart',
+ 'smile',
+ 'sn',
+ 'sncf',
+ 'so',
+ 'soccer',
+ 'social',
+ 'softbank',
+ 'software',
+ 'sohu',
+ 'solar',
+ 'solutions',
+ 'song',
+ 'sony',
+ 'soy',
+ 'spa',
+ 'space',
+ 'sport',
+ 'spot',
+ 'sr',
+ 'srl',
+ 'ss',
+ 'st',
+ 'stada',
+ 'staples',
+ 'star',
+ 'statebank',
+ 'statefarm',
+ 'stc',
+ 'stcgroup',
+ 'stockholm',
+ 'storage',
+ 'store',
+ 'stream',
+ 'studio',
+ 'study',
+ 'style',
+ 'su',
+ 'sucks',
+ 'supplies',
+ 'supply',
+ 'support',
+ 'surf',
+ 'surgery',
+ 'suzuki',
+ 'sv',
+ 'swatch',
+ 'swiss',
+ 'sx',
+ 'sy',
+ 'sydney',
+ 'systems',
+ 'sz',
+ 'tab',
+ 'taipei',
+ 'talk',
+ 'taobao',
+ 'target',
+ 'tatamotors',
+ 'tatar',
+ 'tattoo',
+ 'tax',
+ 'taxi',
+ 'tc',
+ 'tci',
+ 'td',
+ 'tdk',
+ 'team',
+ 'tech',
+ 'technology',
+ 'tel',
+ 'temasek',
+ 'tennis',
+ 'teva',
+ 'tf',
+ 'tg',
+ 'th',
+ 'thd',
+ 'theater',
+ 'theatre',
+ 'tiaa',
+ 'tickets',
+ 'tienda',
+ 'tips',
+ 'tires',
+ 'tirol',
+ 'tj',
+ 'tjmaxx',
+ 'tjx',
+ 'tk',
+ 'tkmaxx',
+ 'tl',
+ 'tm',
+ 'tmall',
+ 'tn',
+ 'to',
+ 'today',
+ 'tokyo',
+ 'tools',
+ 'top',
+ 'toray',
+ 'toshiba',
+ 'total',
+ 'tours',
+ 'town',
+ 'toyota',
+ 'toys',
+ 'tr',
+ 'trade',
+ 'trading',
+ 'training',
+ 'travel',
+ 'travelers',
+ 'travelersinsurance',
+ 'trust',
+ 'trv',
+ 'tt',
+ 'tube',
+ 'tui',
+ 'tunes',
+ 'tushu',
+ 'tv',
+ 'tvs',
+ 'tw',
+ 'tz',
+ 'ua',
+ 'ubank',
+ 'ubs',
+ 'ug',
+ 'uk',
+ 'unicom',
+ 'university',
+ 'uno',
+ 'uol',
+ 'ups',
+ 'us',
+ 'uy',
+ 'uz',
+ 'va',
+ 'vacations',
+ 'vana',
+ 'vanguard',
+ 'vc',
+ 've',
+ 'vegas',
+ 'ventures',
+ 'verisign',
+ 'versicherung',
+ 'vet',
+ 'vg',
+ 'vi',
+ 'viajes',
+ 'video',
+ 'vig',
+ 'viking',
+ 'villas',
+ 'vin',
+ 'vip',
+ 'virgin',
+ 'visa',
+ 'vision',
+ 'viva',
+ 'vivo',
+ 'vlaanderen',
+ 'vn',
+ 'vodka',
+ 'volvo',
+ 'vote',
+ 'voting',
+ 'voto',
+ 'voyage',
+ 'vu',
+ 'wales',
+ 'walmart',
+ 'walter',
+ 'wang',
+ 'wanggou',
+ 'watch',
+ 'watches',
+ 'weather',
+ 'weatherchannel',
+ 'webcam',
+ 'weber',
+ 'website',
+ 'wed',
+ 'wedding',
+ 'weibo',
+ 'weir',
+ 'wf',
+ 'whoswho',
+ 'wien',
+ 'wiki',
+ 'williamhill',
+ 'win',
+ 'windows',
+ 'wine',
+ 'winners',
+ 'wme',
+ 'wolterskluwer',
+ 'woodside',
+ 'work',
+ 'works',
+ 'world',
+ 'wow',
+ 'ws',
+ 'wtc',
+ 'wtf',
+ 'xbox',
+ 'xerox',
+ 'xihuan',
+ 'xin',
+ 'xn--11b4c3d',
+ 'xn--1ck2e1b',
+ 'xn--1qqw23a',
+ 'xn--2scrj9c',
+ 'xn--30rr7y',
+ 'xn--3bst00m',
+ 'xn--3ds443g',
+ 'xn--3e0b707e',
+ 'xn--3hcrj9c',
+ 'xn--3pxu8k',
+ 'xn--42c2d9a',
+ 'xn--45br5cyl',
+ 'xn--45brj9c',
+ 'xn--45q11c',
+ 'xn--4dbrk0ce',
+ 'xn--4gbrim',
+ 'xn--54b7fta0cc',
+ 'xn--55qw42g',
+ 'xn--55qx5d',
+ 'xn--5su34j936bgsg',
+ 'xn--5tzm5g',
+ 'xn--6frz82g',
+ 'xn--6qq986b3xl',
+ 'xn--80adxhks',
+ 'xn--80ao21a',
+ 'xn--80aqecdr1a',
+ 'xn--80asehdb',
+ 'xn--80aswg',
+ 'xn--8y0a063a',
+ 'xn--90a3ac',
+ 'xn--90ae',
+ 'xn--90ais',
+ 'xn--9dbq2a',
+ 'xn--9et52u',
+ 'xn--9krt00a',
+ 'xn--b4w605ferd',
+ 'xn--bck1b9a5dre4c',
+ 'xn--c1avg',
+ 'xn--c2br7g',
+ 'xn--cck2b3b',
+ 'xn--cckwcxetd',
+ 'xn--cg4bki',
+ 'xn--clchc0ea0b2g2a9gcd',
+ 'xn--czr694b',
+ 'xn--czrs0t',
+ 'xn--czru2d',
+ 'xn--d1acj3b',
+ 'xn--d1alf',
+ 'xn--e1a4c',
+ 'xn--eckvdtc9d',
+ 'xn--efvy88h',
+ 'xn--fct429k',
+ 'xn--fhbei',
+ 'xn--fiq228c5hs',
+ 'xn--fiq64b',
+ 'xn--fiqs8s',
+ 'xn--fiqz9s',
+ 'xn--fjq720a',
+ 'xn--flw351e',
+ 'xn--fpcrj9c3d',
+ 'xn--fzc2c9e2c',
+ 'xn--fzys8d69uvgm',
+ 'xn--g2xx48c',
+ 'xn--gckr3f0f',
+ 'xn--gecrj9c',
+ 'xn--gk3at1e',
+ 'xn--h2breg3eve',
+ 'xn--h2brj9c',
+ 'xn--h2brj9c8c',
+ 'xn--hxt814e',
+ 'xn--i1b6b1a6a2e',
+ 'xn--imr513n',
+ 'xn--io0a7i',
+ 'xn--j1aef',
+ 'xn--j1amh',
+ 'xn--j6w193g',
+ 'xn--jlq480n2rg',
+ 'xn--jvr189m',
+ 'xn--kcrx77d1x4a',
+ 'xn--kprw13d',
+ 'xn--kpry57d',
+ 'xn--kput3i',
+ 'xn--l1acc',
+ 'xn--lgbbat1ad8j',
+ 'xn--mgb9awbf',
+ 'xn--mgba3a3ejt',
+ 'xn--mgba3a4f16a',
+ 'xn--mgba7c0bbn0a',
+ 'xn--mgbaam7a8h',
+ 'xn--mgbab2bd',
+ 'xn--mgbah1a3hjkrd',
+ 'xn--mgbai9azgqp6j',
+ 'xn--mgbayh7gpa',
+ 'xn--mgbbh1a',
+ 'xn--mgbbh1a71e',
+ 'xn--mgbc0a9azcg',
+ 'xn--mgbca7dzdo',
+ 'xn--mgbcpq6gpa1a',
+ 'xn--mgberp4a5d4ar',
+ 'xn--mgbgu82a',
+ 'xn--mgbi4ecexp',
+ 'xn--mgbpl2fh',
+ 'xn--mgbt3dhd',
+ 'xn--mgbtx2b',
+ 'xn--mgbx4cd0ab',
+ 'xn--mix891f',
+ 'xn--mk1bu44c',
+ 'xn--mxtq1m',
+ 'xn--ngbc5azd',
+ 'xn--ngbe9e0a',
+ 'xn--ngbrx',
+ 'xn--node',
+ 'xn--nqv7f',
+ 'xn--nqv7fs00ema',
+ 'xn--nyqy26a',
+ 'xn--o3cw4h',
+ 'xn--ogbpf8fl',
+ 'xn--otu796d',
+ 'xn--p1acf',
+ 'xn--p1ai',
+ 'xn--pgbs0dh',
+ 'xn--pssy2u',
+ 'xn--q7ce6a',
+ 'xn--q9jyb4c',
+ 'xn--qcka1pmc',
+ 'xn--qxa6a',
+ 'xn--qxam',
+ 'xn--rhqv96g',
+ 'xn--rovu88b',
+ 'xn--rvc1e0am3e',
+ 'xn--s9brj9c',
+ 'xn--ses554g',
+ 'xn--t60b56a',
+ 'xn--tckwe',
+ 'xn--tiq49xqyj',
+ 'xn--unup4y',
+ 'xn--vermgensberater-ctb',
+ 'xn--vermgensberatung-pwb',
+ 'xn--vhquv',
+ 'xn--vuq861b',
+ 'xn--w4r85el8fhu5dnra',
+ 'xn--w4rs40l',
+ 'xn--wgbh1c',
+ 'xn--wgbl6a',
+ 'xn--xhq521b',
+ 'xn--xkc2al3hye2a',
+ 'xn--xkc2dl3a5ee0h',
+ 'xn--y9a3aq',
+ 'xn--yfro4i67o',
+ 'xn--ygbi2ammx',
+ 'xn--zfr164b',
+ 'xxx',
+ 'xyz',
+ 'yachts',
+ 'yahoo',
+ 'yamaxun',
+ 'yandex',
+ 'ye',
+ 'yodobashi',
+ 'yoga',
+ 'yokohama',
+ 'you',
+ 'youtube',
+ 'yt',
+ 'yun',
+ 'za',
+ 'zappos',
+ 'zara',
+ 'zero',
+ 'zip',
+ 'zm',
+ 'zone',
+ 'zuerich',
+ 'zw'
+]
diff --git a/src/portal/requirements.txt b/src/portal/requirements.txt
index c2e2915..0d31ac1 100644
--- a/src/portal/requirements.txt
+++ b/src/portal/requirements.txt
@@ -1,3 +1,2 @@
httpx[http2,brotli,zstd]
blake3
-pillow
diff --git a/src/portal/scripts/create_vendor.sh b/src/portal/scripts/create_vendor.sh
index 87b0b5d..3dd8bc3 100755
--- a/src/portal/scripts/create_vendor.sh
+++ b/src/portal/scripts/create_vendor.sh
@@ -12,6 +12,10 @@ cd yt-dlp/
pip3 install . --upgrade --target=../vendor
cd ../
+cd snscrape/
+pip3 install . --upgrade --target=../vendor
+cd ../
+
cd twitter-api-client/
pip3 install . --upgrade --target=../vendor
cd ../