From dcf16e0cc4f52dd1f0110164f7a7d6cec8c5e76c Mon Sep 17 00:00:00 2001 From: Nose Date: Sat, 31 Jan 2026 16:11:25 -0800 Subject: [PATCH] h --- .gitignore | 3 ++- API/HTTP.py | 31 ++++++++---------------- API/data/alldebrid.json | 52 ++++++++++++++++++++++++++--------------- SYS/database.py | 20 ++++++++++++++-- cmdlet/search_file.py | 48 +++++++++++++++++++++++++++++++++++++ logs/log_fallback.txt | 1 + 6 files changed, 112 insertions(+), 43 deletions(-) diff --git a/.gitignore b/.gitignore index 914ea57..4db6640 100644 --- a/.gitignore +++ b/.gitignore @@ -245,4 +245,5 @@ mypy. medios.db medios* mypy.ini -\logs\* \ No newline at end of file +\logs\* +logs \ No newline at end of file diff --git a/API/HTTP.py b/API/HTTP.py index db7b145..344119a 100644 --- a/API/HTTP.py +++ b/API/HTTP.py @@ -100,8 +100,8 @@ def _resolve_verify_value(verify_ssl: bool) -> Union[bool, str]: pass return None - # Prefer pip_system_certs if available - for mod_name in ("pip_system_certs",): + # Prefer helpful modules if available (use safe checks to avoid first-chance import errors) + for mod_name in ("pip_system_certs", "certifi_win32"): path = _try_module_bundle(mod_name) if path: try: @@ -111,29 +111,18 @@ def _resolve_verify_value(verify_ssl: bool) -> Union[bool, str]: logger.info(f"SSL_CERT_FILE not set; using bundle from {mod_name}: {path}") return path - # Special-case helpers that merge system certs (eg. certifi_win32) + # Fallback to certifi try: - import certifi_win32 as _cw # type: ignore - if hasattr(_cw, "add_windows_store_certs") and callable(_cw.add_windows_store_certs): - try: - _cw.add_windows_store_certs() - except Exception: - pass - try: - import certifi # type: ignore + import certifi # type: ignore - path = certifi.where() - if path: - try: - os.environ["SSL_CERT_FILE"] = path - except Exception: - pass - logger.info( - f"SSL_CERT_FILE not set; using certifi bundle after certifi_win32: {path}" - ) - return path + path = certifi.where() + if path: + try: + os.environ["SSL_CERT_FILE"] = path except Exception: pass + logger.info(f"SSL_CERT_FILE not set; using certifi bundle: {path}") + return path except Exception: pass diff --git a/API/data/alldebrid.json b/API/data/alldebrid.json index fcd9165..2175865 100644 --- a/API/data/alldebrid.json +++ b/API/data/alldebrid.json @@ -234,7 +234,7 @@ "ddl\\.to/([0-9a-zA-Z]{12})" ], "regexp": "((ddownload\\.com/[0-9a-zA-Z]{12}))|(ddl\\.to/([0-9a-zA-Z]{12}))", - "status": false + "status": true }, "dropapk": { "name": "dropapk", @@ -622,7 +622,7 @@ "(simfileshare\\.net/download/[0-9]+/)" ], "regexp": "(simfileshare\\.net/download/[0-9]+/)", - "status": true + "status": false }, "streamtape": { "name": "streamtape", @@ -4759,6 +4759,17 @@ ], "regexp": "(https?://(?:www\\.)?eroprofile\\.com/m/videos/album/([^/]+))|(https?://(?:www\\.)?eroprofile\\.com/m/videos/view/([^/]+))" }, + "errarhiiv": { + "name": "errarhiiv", + "type": "free", + "domains": [ + "arhiiv.err.ee" + ], + "regexps": [ + "https://arhiiv\\.err\\.ee/video/(?:vaata/)?([^/?#]+)" + ], + "regexp": "https://arhiiv\\.err\\.ee/video/(?:vaata/)?([^/?#]+)" + }, "errjupiter": { "name": "errjupiter", "type": "free", @@ -5315,6 +5326,19 @@ ], "regexp": "https?://(?:www\\.)?radiofrance\\.fr/(?:franceculture|franceinfo|franceinter|francemusique|fip|mouv)/podcasts/(?:[^?#]+/)?([^?#]+)-(\\d{6,})(?:$|[?#])" }, + "franceinfo": { + "name": "franceinfo", + "type": "free", + "domains": [ + "francetvinfo.fr", + "france3-regions.francetvinfo.fr", + "franceinfo.fr" + ], + "regexps": [ + "https?://(?:www|mobile|france3-regions)\\.france(?:tv)?info.fr/(?:[^/?#]+/)*([^/?#&.]+)" + ], + "regexp": "https?://(?:www|mobile|france3-regions)\\.france(?:tv)?info.fr/(?:[^/?#]+/)*([^/?#&.]+)" + }, "franceinter": { "name": "franceinter", "type": "free", @@ -5337,18 +5361,6 @@ ], "regexp": "(francetv:([^@#]+))|(https?://(?:(?:www\\.)?france\\.tv|mobile\\.france\\.tv)/(?:[^/]+/)*([^/]+)\\.html)" }, - "francetvinfo.fr": { - "name": "francetvinfo.fr", - "type": "free", - "domains": [ - "francetvinfo.fr", - "france3-regions.francetvinfo.fr" - ], - "regexps": [ - "https?://(?:www|mobile|france3-regions)\\.francetvinfo\\.fr/(?:[^/]+/)*([^/?#&.]+)" - ], - "regexp": "https?://(?:www|mobile|france3-regions)\\.francetvinfo\\.fr/(?:[^/]+/)*([^/?#&.]+)" - }, "freesound": { "name": "freesound", "type": "free", @@ -9760,7 +9772,7 @@ "name": "nitter", "type": "free", "domains": [ - "nitter.projectsegfau.lt" + "nitter.fdn.fr" ], "regexps": [ "https?://(?:3nzoldnxplag42gqjs23xvghtzf6t6yzssrtytnntc6ppc7xxuoneoad\\.onion|nitter\\.l4qlywnpwqsluw65ts7md3khrivpirse744un3x7mlskqauz5pyuzgqd\\.onion|nitter7bryz3jv7e3uekphigvmoyoem4al3fynerxkj22dmoxoq553qd\\.onion|npf37k3mtzwxreiw52ccs5ay4e6qt2fkcs2ndieurdyn2cuzzsfyfvid\\.onion|nitter\\.v6vgyqpa7yefkorazmg5d5fimstmvm2vtbirt6676mt7qmllrcnwycqd\\.onion|i23nv6w3juvzlw32xzoxcqzktegd4i4fu3nmnc2ewv4ggiu4ledwklad\\.onion|26oq3gioiwcmfojub37nz5gzbkdiqp7fue5kvye7d4txv4ny6fb4wwid\\.onion|vfaomgh4jxphpbdfizkm5gbtjahmei234giqj4facbwhrfjtcldauqad\\.onion|iwgu3cv7ywf3gssed5iqtavmrlszgsxazkmwwnt4h2kdait75thdyrqd\\.onion|erpnncl5nhyji3c32dcfmztujtl3xaddqb457jsbkulq24zqq7ifdgad\\.onion|ckzuw5misyahmg7j5t5xwwuj3bwy62jfolxyux4brfflramzsvvd3syd\\.onion|jebqj47jgxleaiosfcxfibx2xdahjettuydlxbg64azd4khsxv6kawid\\.onion|nttr2iupbb6fazdpr2rgbooon2tzbbsvvkagkgkwohhodjzj43stxhad\\.onion|nitraeju2mipeziu2wtcrqsxg7h62v5y4eqgwi75uprynkj74gevvuqd\\.onion|nitter\\.lqs5fjmajyp7rvp4qvyubwofzi6d4imua7vs237rkc4m5qogitqwrgyd\\.onion|ibsboeui2im5o7dxnik3s5yghufumgy5abevtij5nbizequfpu4qi4ad\\.onion|ec5nvbycpfa5k6ro77blxgkyrzbkv7uy6r5cngcbkadtjj2733nm3uyd\\.onion|nitter\\.i2p|u6ikd6zndl3c4dsdq4mmujpntgeevdk5qzkfb57r4tnfeccrn2qa\\.b32\\.i2p|nitterlgj3n5fgwesu3vxc5h67ruku33nqaoeoocae2mvlzhsu6k7fqd\\.onion|nitter\\.lacontrevoie\\.fr|nitter\\.fdn\\.fr|nitter\\.1d4\\.us|nitter\\.kavin\\.rocks|nitter\\.unixfox\\.eu|nitter\\.domain\\.glass|nitter\\.namazso\\.eu|birdsite\\.xanny\\.family|nitter\\.moomoo\\.me|bird\\.trom\\.tf|nitter\\.it|twitter\\.censors\\.us|nitter\\.grimneko\\.de|twitter\\.076\\.ne\\.jp|nitter\\.fly\\.dev|notabird\\.site|nitter\\.weiler\\.rocks|nitter\\.sethforprivacy\\.com|nitter\\.cutelab\\.space|nitter\\.nl|nitter\\.mint\\.lgbt|nitter\\.bus\\-hit\\.me|nitter\\.esmailelbob\\.xyz|tw\\.artemislena\\.eu|nitter\\.winscloud\\.net|nitter\\.tiekoetter\\.com|nitter\\.spaceint\\.fr|nitter\\.privacy\\.com\\.de|nitter\\.poast\\.org|nitter\\.bird\\.froth\\.zone|nitter\\.dcs0\\.hu|twitter\\.dr460nf1r3\\.org|nitter\\.garudalinux\\.org|twitter\\.femboy\\.hu|nitter\\.cz|nitter\\.privacydev\\.net|nitter\\.evil\\.site|tweet\\.lambda\\.dance|nitter\\.kylrth\\.com|nitter\\.foss\\.wtf|nitter\\.priv\\.pw|nitter\\.tokhmi\\.xyz|nitter\\.catalyst\\.sx|unofficialbird\\.com|nitter\\.projectsegfau\\.lt|nitter\\.eu\\.projectsegfau\\.lt|singapore\\.unofficialbird\\.com|canada\\.unofficialbird\\.com|india\\.unofficialbird\\.com|nederland\\.unofficialbird\\.com|uk\\.unofficialbird\\.com|n\\.l5\\.ca|nitter\\.slipfox\\.xyz|nitter\\.soopy\\.moe|nitter\\.qwik\\.space|read\\.whatever\\.social|nitter\\.rawbit\\.ninja|nt\\.vern\\.cc|ntr\\.odyssey346\\.dev|nitter\\.ir|nitter\\.privacytools\\.io|nitter\\.sneed\\.network|n\\.sneed\\.network|nitter\\.manasiwibi\\.com|nitter\\.smnz\\.de|nitter\\.twei\\.space|nitter\\.inpt\\.fr|nitter\\.d420\\.de|nitter\\.caioalonso\\.com|nitter\\.at|nitter\\.drivet\\.xyz|nitter\\.pw|nitter\\.nicfab\\.eu|bird\\.habedieeh\\.re|nitter\\.hostux\\.net|nitter\\.adminforge\\.de|nitter\\.platypush\\.tech|nitter\\.mask\\.sh|nitter\\.pufe\\.org|nitter\\.us\\.projectsegfau\\.lt|nitter\\.arcticfoxes\\.net|t\\.com\\.sb|nitter\\.kling\\.gg|nitter\\.ktachibana\\.party|nitter\\.riverside\\.rocks|nitter\\.girlboss\\.ceo|nitter\\.lunar\\.icu|twitter\\.moe\\.ngo|nitter\\.freedit\\.eu|ntr\\.frail\\.duckdns\\.org|nitter\\.librenode\\.org|n\\.opnxng\\.com|nitter\\.plus\\.st|nitter\\.ethibox\\.fr|nitter\\.net|is\\-nitter\\.resolv\\.ee|lu\\-nitter\\.resolv\\.ee|nitter\\.13ad\\.de|nitter\\.40two\\.app|nitter\\.cattube\\.org|nitter\\.cc|nitter\\.dark\\.fail|nitter\\.himiko\\.cloud|nitter\\.koyu\\.space|nitter\\.mailstation\\.de|nitter\\.mastodont\\.cat|nitter\\.tedomum\\.net|nitter\\.tokhmi\\.xyz|nitter\\.weaponizedhumiliation\\.com|nitter\\.vxempire\\.xyz|tweet\\.lambda\\.dance|nitter\\.ca|nitter\\.42l\\.fr|nitter\\.pussthecat\\.org|nitter\\.nixnet\\.services|nitter\\.eu|nitter\\.actionsack\\.com|nitter\\.hu|twitr\\.gq|nittereu\\.moomoo\\.me|bird\\.from\\.tf|twitter\\.grimneko\\.de|nitter\\.alefvanoon\\.xyz|n\\.hyperborea\\.cloud|twitter\\.mstdn\\.social|nitter\\.silkky\\.cloud|nttr\\.stream|fuckthesacklers\\.network|nitter\\.govt\\.land|nitter\\.datatunnel\\.xyz|de\\.nttr\\.stream|twtr\\.bch\\.bar|nitter\\.exonip\\.de|nitter\\.mastodon\\.pro|nitter\\.notraxx\\.ch|nitter\\.skrep\\.in|nitter\\.snopyta\\.org)/(.+)/status/([0-9]+)(#.)?" @@ -14557,9 +14569,9 @@ "watch.thechosen.tv" ], "regexps": [ - "https?://(?:www\\.)?watch\\.thechosen\\.tv/video/([0-9]+)" + "https?://(?:www\\.)?watch\\.thechosen\\.tv/watch/([0-9]+)" ], - "regexp": "https?://(?:www\\.)?watch\\.thechosen\\.tv/video/([0-9]+)" + "regexp": "https?://(?:www\\.)?watch\\.thechosen\\.tv/watch/([0-9]+)" }, "thechosengroup": { "name": "thechosengroup", @@ -16353,9 +16365,11 @@ "volej.tv" ], "regexps": [ - "https?://volej\\.tv/video/(\\d+)" + "https?://volej\\.tv/kategorie/([^/$?]+)", + "https?://volej\\.tv/klub/(\\d+)", + "https?://volej\\.tv/match/(\\d+)" ], - "regexp": "https?://volej\\.tv/video/(\\d+)" + "regexp": "(https?://volej\\.tv/kategorie/([^/$?]+))|(https?://volej\\.tv/klub/(\\d+))|(https?://volej\\.tv/match/(\\d+))" }, "voxmedia": { "name": "voxmedia", diff --git a/SYS/database.py b/SYS/database.py index ea5105c..0904275 100644 --- a/SYS/database.py +++ b/SYS/database.py @@ -2,6 +2,7 @@ from __future__ import annotations import sqlite3 import json +import ast import threading import os from queue import Queue @@ -396,6 +397,9 @@ def rows_to_config(rows) -> Dict[str, Any]: # Conservative JSON parsing: only attempt to decode when the value # looks like JSON (object/array/quoted string/true/false/null/number). + # If JSON decoding fails, also attempt Python literal parsing (e.g., single-quoted + # dict/list reprs) using ast.literal_eval as a safe fallback. If both + # attempts fail, use the raw string value. parsed_val = val try: if isinstance(val, str): @@ -409,14 +413,26 @@ def rows_to_config(rows) -> Dict[str, Any]: try: parsed_val = json.loads(val) except Exception: - parsed_val = val + # Try parsing Python literal formats (single-quoted dicts/lists) + try: + parsed_val = ast.literal_eval(val) + debug(f"Parsed config value for key '{key}' using ast.literal_eval (non-JSON literal)") + except Exception: + parsed_val = val else: parsed_val = val else: try: parsed_val = json.loads(val) except Exception: - parsed_val = val + # Non-string values can sometimes be bytes or Python literals; try decoding when appropriate + try: + if isinstance(val, (bytes, bytearray)): + parsed_val = json.loads(val.decode('utf-8', errors='replace')) + else: + parsed_val = val + except Exception: + parsed_val = val except Exception: parsed_val = val diff --git a/cmdlet/search_file.py b/cmdlet/search_file.py index 3fda562..a6d0ee2 100644 --- a/cmdlet/search_file.py +++ b/cmdlet/search_file.py @@ -586,6 +586,52 @@ class search_file(Cmdlet): if store_filter and not storage_backend: storage_backend = store_filter + # If the user accidentally used `-store ` or `store:`, + # prefer to treat it as a provider search (providers like 'alldebrid' are not store backends). + try: + from Store.registry import list_configured_backend_names + providers_map = list_search_providers(config) + configured = list_configured_backend_names(config or {}) + if storage_backend: + matched = None + for p in (providers_map or {}): + if str(p).strip().lower() == str(storage_backend).strip().lower(): + matched = p + break + if matched and str(storage_backend) not in configured: + log(f"Note: Treating '-store {storage_backend}' as provider search for '{matched}'", file=sys.stderr) + return self._run_provider_search( + provider_name=matched, + query=query, + limit=limit, + limit_set=limit_set, + open_id=open_id, + args_list=args_list, + refresh_mode=refresh_mode, + config=config, + ) + elif store_filter: + matched = None + for p in (providers_map or {}): + if str(p).strip().lower() == str(store_filter).strip().lower(): + matched = p + break + if matched and str(store_filter) not in configured: + log(f"Note: Treating 'store:{store_filter}' as provider search for '{matched}'", file=sys.stderr) + return self._run_provider_search( + provider_name=matched, + query=query, + limit=limit, + limit_set=limit_set, + open_id=open_id, + args_list=args_list, + refresh_mode=refresh_mode, + config=config, + ) + except Exception: + # Be conservative: if provider detection fails, fall back to store behaviour + pass + hash_query = parse_hash_query(query) if not query: @@ -644,6 +690,8 @@ class search_file(Cmdlet): from Store._base import Store as BaseStore backend_to_search = storage_backend or None + + if hash_query: # Explicit hash list search: build rows from backend metadata. backends_to_try: List[str] = [] diff --git a/logs/log_fallback.txt b/logs/log_fallback.txt index ce2dca9..04adcc7 100644 --- a/logs/log_fallback.txt +++ b/logs/log_fallback.txt @@ -567,3 +567,4 @@ http://10.162.158.28:45899/get_files/file?hash=5c7296f1a5544522e3d118f60080e0389 2026-01-31T03:13:05.258091Z [DEBUG] logger.debug: DEBUG: 2026-01-31T03:13:22.207331Z [DEBUG] logger.debug: DEBUG: 2026-01-31T03:13:39.166965Z [DEBUG] alldebrid.search: [alldebrid] Failed to list account magnets: AllDebrid API error: The auth apikey is invalid +2026-02-01T00:05:13.728816Z [DEBUG] logger.debug: DEBUG: [Store] Unknown store type 'debrid'