diff --git a/API/alldebrid.py b/API/alldebrid.py index deaaf0a..3da0a48 100644 --- a/API/alldebrid.py +++ b/API/alldebrid.py @@ -12,6 +12,7 @@ import sys import time from typing import Any, Dict, Optional, Set, List, Sequence, Tuple +import time from urllib.parse import urlparse from SYS.logger import log, debug @@ -245,6 +246,73 @@ class AllDebridClient: except Exception as exc: raise AllDebridError(f"Failed to unlock link: {exc}") + def _link_delayed(self, delayed_id: int) -> Dict[str, Any]: + """Poll delayed link status.""" + + try: + resp = self._request("link/delayed", {"id": int(delayed_id)}) + if resp.get("status") != "success": + raise AllDebridError("link/delayed returned error status") + data = resp.get("data") or {} + return data if isinstance(data, dict) else {} + except AllDebridError: + raise + except Exception as exc: + raise AllDebridError(f"Failed to poll delayed link: {exc}") + + def resolve_unlock_link( + self, + link: str, + *, + poll: bool = True, + max_wait_seconds: int = 30, + poll_interval_seconds: int = 5, + ) -> Optional[str]: + """Unlock a link and handle delayed links by polling link/delayed.""" + + try: + resp = self._request("link/unlock", {"link": link}) + except AllDebridError: + raise + except Exception as exc: + raise AllDebridError(f"Failed to unlock link: {exc}") + + if resp.get("status") != "success": + return None + + data = resp.get("data") or {} + if not isinstance(data, dict): + return None + + # Immediate link ready + for key in ("link", "file"): + val = data.get(key) + if isinstance(val, str) and val.strip(): + return val.strip() + + delayed_id = data.get("delayed") + if not poll or delayed_id is None: + return None + + try: + delayed_int = int(delayed_id) + except Exception: + return None + + deadline = time.time() + max_wait_seconds + while time.time() < deadline: + time.sleep(max(1, poll_interval_seconds)) + status_data = self._link_delayed(delayed_int) + status = status_data.get("status") + if status == 2: + link_val = status_data.get("link") + if isinstance(link_val, str) and link_val.strip(): + return link_val.strip() + return None + if status == 3: + raise AllDebridError("Delayed link generation failed") + + return None def check_host(self, hostname: str) -> Dict[str, Any]: """Check if a host is supported by AllDebrid. diff --git a/API/data/alldebrid.json b/API/data/alldebrid.json new file mode 100644 index 0000000..eabd64c --- /dev/null +++ b/API/data/alldebrid.json @@ -0,0 +1,2371 @@ +{ + "status": "success", + "data": { + "hosts": [ + "1fichier.com", + "megadl.fr", + "alterupload.com", + "cjoint.net", + "desfichiers.com", + "dfichiers.com", + "mesfichiers.org", + "piecejointe.net", + "pjointe.com", + "tenvoi.com", + "dl4free.com", + "rapidgator.net", + "rg.to", + "rapidgator.asia", + "turbobit.net", + "wayupload.com", + "turbobit.cloud", + "htfl.net", + "turbobit.cc", + "tourbobit.net", + "torbobit.net", + "turbo.to", + "turb.cc", + "turb.pw", + "hitf.cc", + "hitf.to", + "turbobif.com", + "turbobif.cc", + "turbobif.net", + "trbbt.net", + "trbt.cc", + "hitfile.net", + "hitfile.com", + "hitf.to", + "hitf.cc", + "htfl.net", + "htfl.to", + "htfl.cc", + "mega.co.nz", + "mega.nz", + "4shared.com", + "4s.io", + "9xupload.asia", + "9xupload.info", + "alfafile.net", + "alldebrid.com", + "clicknupload.click", + "clickndownload.cc", + "clickndownload.click", + "clickndownload.link", + "clickndownload.name", + "clickndownload.org", + "clickndownload.space", + "clickndownload.xyz", + "clicknupload.cc", + "clicknupload.club", + "clicknupload.co", + "clicknupload.download", + "clicknupload.link", + "clicknupload.name", + "clicknupload.one", + "clicknupload.online", + "clicknupload.org", + "clicknupload.red", + "clicknupload.site", + "clicknupload.space", + "clicknupload.to", + "clicknupload.vip", + "clicknupload.xyz", + "clipwatching.com", + "highstream.tv", + "dailyuploads.net", + "ddl.to", + "ddownload.com", + "dropapk.to", + "drop.download", + "dropgalaxy.in", + "dgdrive.pro", + "dgdrive.xyz", + "dgdrive.site", + "dgdrive.store", + "dropgalaxy.com", + "dropgalaxy.vip", + "example.com", + "example.net", + "exload.com", + "mixdrop.to", + "mixdrop.sx", + "fastbit.cc", + "file-upload.com", + "file-upload.org", + "file-upload.in", + "file-up.org", + "file.al", + "filedot.to", + "filedot.xyz", + "filedot.top", + "filefactory.com", + "filerio.in", + "filespace.com", + "filezip.cc", + "gigapeta.com", + "drive.google.com", + "hexupload.net", + "hexload.com", + "hot4share.com", + "indishare.me", + "indishare.org", + "indishare.info", + "isra.cloud", + "katfile.com", + "katfile.cloud", + "katfile.online", + "mediafire.com", + "mexashare.com", + "mx-sh.net", + "mexa.sh", + "mixdrop.co", + "mixdrop.to", + "mixdrop.sx", + "modsbase.com", + "mp4upload.com", + "prefiles.com", + "scribd.com", + "sendit.cloud", + "send.cm", + "send.now", + "sharemods.com", + "simfileshare.net", + "streamtape.com", + "upload42.com", + "uploadbank.com", + "uploadbox.io", + "uploadboy.com", + "uploader.link", + "uploadhaven.com", + "uploadrar.com", + "usersdrive.com", + "vev.io", + "thevideo.me", + "vidoza.net", + "vidoza.org", + "playvidto.com", + "vidtodo.com", + "vidto-do.com", + "widtodo.com", + "widtodo.com", + "world-files.com", + "upl.wf", + "worldbytez.com", + "worldbytez.net" + ], + "streams": [ + "dailymotion.com", + "geo.dailymotion.com", + "lequipe.fr", + "dai.ly", + "clips.twitch.tv", + "twitch.tv", + "m.twitch.tv", + "go.twitch.tv", + "player.twitch.tv", + "soundcloud.com", + "api.soundcloud.com", + "vimeo.com", + "player.vimeo.com", + "vimeopro.com", + "1news.co.nz", + "onenews.co.nz", + "1tv.ru", + "sport1tv.ru", + "playout.3qsdn.com", + "3sat.de", + "4tube.com", + "m.4tube.com", + "7plus.com.au", + "9news.com.au", + "9now.com.au", + "10.com.au", + "10play.com.au", + "17.live", + "20min.ch", + "video.twentythree.net", + "bonnier-publications-danmark.23video.com", + "24tv.ua", + "56.com", + "247sports.com", + "abc.net.au", + "iview.abc.net.au", + "abcnews.go.com", + "abc7news.com", + "6abc.com", + "abema.tv", + "abema.tv", + "acast.com", + "play.acast.com", + "shows.acast.com", + "embed.acast.com", + "acfun.cn", + "acfun.cn", + "animationdigitalnetwork.com", + "animationdigitalnetwork.com", + "video.tv.adobe.com", + "adultswim.com", + "watch.historyvault.com", + "historyvault.com", + "history.com", + "aetv.com", + "play.mylifetime.com", + "fyi.tv", + "mylifetime.com", + "watch.lifetimemovieclub.com", + "play.aetv.com", + "aeon.co", + "agalega.gal", + "air.tv", + "aitube.kz", + "alibaba.com", + "balkans.aljazeera.net", + "allocine.fr", + "allstar.gg", + "alsace20.tv", + "alsace20.tv", + "altcensored.com", + "cursos.alura.com.br", + "cursos.alura.com.br", + "amadeus.tv", + "amara.org", + "amazon.in", + "amazon.com", + "amazon.in", + "amazon.co.uk", + "amazon.in", + "amazon.com", + "amazon.es", + "amc.com", + "bbcamerica.com", + "wetv.com", + "ifc.com", + "sundancetv.com", + "americastestkitchen.com", + "cookscountry.com", + "cooksillustrated.com", + "ahctv.com", + "anchor.fm", + "anderetijden.nl", + "angel.com", + "animalplanet.com", + "ant1news.gr", + "antenna.gr", + "ant1news.gr", + "antenna.gr", + "aol.com", + "aol.ca", + "aol.co.uk", + "aol.de", + "aol.jp", + "uvp.apa.at", + "uvp-apapublisher.sf.apa.at", + "uvp-rma.sf.apa.at", + "uvp-kleinezeitung.sf.apa.at", + "aparat.com", + "music.apple.com", + "ent.appledaily.com.tw", + "appledaily.com.tw", + "podcasts.apple.com", + "trailers.apple.com", + "movietrailers.apple.com", + "archive.org", + "daserste.de", + "ardaudiothek.de", + "ardaudiothek.de", + "ardmediathek.de", + "beta.ardmediathek.de", + "ardmediathek.de", + "rss.art19.com", + "art19.com", + "art19.com", + "rss.art19.com", + "arte.sky.it", + "arte.tv", + "api.arte.tv", + "arte.tv", + "arte.tv", + "arte.tv", + "asobichannel.asobistore.jp", + "asobistage.asobistore.jp", + "atresplayer.com", + "atscaleconference.com", + "atv.at", + "audi-mediacenter.com", + "audioboom.com", + "nokiatune.audiodraft.com", + "vikinggrace.audiodraft.com", + "timferriss.audiodraft.com", + "audiodraft.com", + "audiomack.com", + "audius.co", + "dcndigital.ae", + "awaan.ae", + "axs.tv", + "tv.telezueri.ch", + "telebaern.tv", + "v.baidu.com", + "banbye.com", + "banbye.com", + "blazo.bandcamp.com", + "nightbringer.bandcamp.com", + "jstrecords.bandcamp.com", + "insulters.bandcamp.com", + "youtube-dl.bandcamp.com", + "benprunty.bandcamp.com", + "relapsealumni.bandcamp.com", + "diskotopia.bandcamp.com", + "adrianvonziegler.bandcamp.com", + "dotscale.bandcamp.com", + "nightcallofficial.bandcamp.com", + "steviasphere.bandcamp.com", + "coldworldofficial.bandcamp.com", + "nuclearwarnowproductions.bandcamp.com", + "bandcamp.com", + "bandlab.com", + "bandlab.com", + "banned.video", + "bbc.com", + "bbc.co.uk", + "bbcnewsd73hkzno2ini43t4gblxvycyac5aw4gnv7t2rccijh7745uqd.onion", + "bbcweb3hytmzhn5d532owbu6oqadra5z3ar726vq5kgwwn6aucdccrad.onion", + "bbc.co.uk", + "bbv-tv.net", + "bbv-tv.net", + "bbv-tv.net", + "beacon.tv", + "beatbump.ml", + "beatbump.io", + "beatbump.ml", + "beatbump.io", + "beatport.com", + "beeg.com", + "web.arbeitsagentur.de", + "bet.com", + "bfmtv.com", + "rmc.bfmtv.com", + "bibeltv.de", + "bigflix.com", + "bigo.tv", + "bild.de", + "bilibili.com", + "bilibili.com", + "bilibili.com", + "bilibili.com", + "bilibili.com", + "bilibili.com", + "bilibili.com", + "space.bilibili.com", + "t.bilibili.com", + "space.bilibili.com", + "bilibili.com", + "bilibili.com", + "space.bilibili.com", + "space.bilibili.com", + "space.bilibili.com", + "bilibili.com", + "bilibili.tv", + "biliintl.com", + "live.bilibili.com", + "tv.biobiochile.cl", + "biobiochile.cl", + "biography.com", + "bitchute.com", + "old.bitchute.com", + "bitchute.com", + "old.bitchute.com", + "streams.bitmovin.com", + "us-lti.bbcollab.com", + "eu.bbcollab.com", + "us.bbcollab.com", + "ca.bbcollab.com", + "au.bbcollab.com", + "au.bbcollab.com", + "us.bbcollab.com", + "eu.bbcollab.com", + "bleacherreport.com", + "bleacherreport.com", + "blerp.com", + "blogger.com", + "bloomberg.com", + "bsky.app", + "main.bsky.dev", + "union.bokecc.com", + "de.bongacams.com", + "cn.bongacams.com", + "de.bongacams.net", + "boosty.to", + "bostonglobe.com", + "mlssoccer.app.box.com", + "utexas.app.box.com", + "thejacksonlaboratory.ent.box.com", + "boxcast.tv", + "bpb.de", + "br.de", + "br-klassik.de", + "brainpop.com", + "ell.brainpop.com", + "esp.brainpop.com", + "fr.brainpop.com", + "il.brainpop.com", + "jr.brainpop.com", + "bravotv.com", + "oxygen.com", + "breitbart.com", + "c.brightcove.com", + "link.brightcove.com", + "players.brightcove.net", + "classes.brilliantpala.org", + "elearn.brilliantpala.org", + "bt.no", + "btvplus.bg", + "bundesliga.com", + "dbtg.tv", + "bundestag.de", + "iframe.mediadelivery.net", + "player.mediadelivery.net", + "uk.businessinsider.com", + "businessinsider.nl", + "businessinsider.com", + "buzzfeed.com", + "byutv.org", + "caffeine.tv", + "callin.com", + "camdemy.com", + "camdemy.com", + "camfm.co.uk", + "camfm.co.uk", + "cammodels.com", + "camsoda.com", + "canal1.com.co", + "noticias.canal1.com.co", + "canalalpha.ch", + "canalc2.tv", + "archives-canalc2.u-strasbg.fr", + "mycanal.fr", + "piwiplus.fr", + "canalsurmas.es", + "play.caracoltv.com", + "cbc.ca", + "cbs.com", + "colbertlateshow.com", + "cbsnews.com", + "cbsnews.com", + "cbsnews.com", + "cbsnews.com", + "cbssports.com", + "embed.247sports.com", + "3cat.cat", + "sports.cntv.cn", + "tv.cctv.com", + "english.cntv.cn", + "cctv.cntv.cn", + "ncpa-classic.com", + "news.cctv.com", + "ent.cntv.cn", + "tv.cntv.cn", + "cda.pl", + "ebd.cda.pl", + "cda.pl", + "cellebrite.com", + "ceskatelevize.cz", + "news.cgtn.com", + "charlierose.com", + "chaturbate.com", + "en.chaturbate.com", + "chaturbate.eu", + "chaturbate.global", + "chilloutzone.net", + "chzzk.naver.com", + "cielotv.it", + "cinemax.com", + "cinetecamilano.it", + "asiancrush.com", + "retrocrush.tv", + "retrocrush.tv", + "asiancrush.com", + "ciscolive.cisco.com", + "ciscolive.com", + "ciscolive.cisco.com", + "ciscolive.com", + "demosubdomain.webex.com", + "cjsw.com", + "clipchamp.com", + "closertotruth.com", + "embed.cloudflarestream.com", + "watch.cloudflarestream.com", + "cloudflarestream.com", + "embed.videodelivery.net", + "customer-aw5py76sw8wyqzmh.cloudflarestream.com", + "embed.cloudycdn.services", + "embed.backscreen.com", + "clubic.com", + "clyp.it", + "cnbc.com", + "cnn.com", + "edition.cnn.com", + "cnnespanol.cnn.com", + "cnnindonesia.com", + "cc.com", + "conanclassic.com", + "conan25.teamcoco.com", + "video.wired.com", + "video.gq.com", + "player.cnevids.com", + "vanityfair.com", + "player-backend.cnevids.com", + "contv.com", + "watch.cookingchanneltv.com", + "hgtv.ca", + "foodnetwork.ca", + "etcanada.com", + "history.ca", + "showcase.ca", + "bigbrothercanada.ca", + "seriesplus.com", + "disneychannel.ca", + "coub.com", + "c-cdn.coub.com", + "cozy.tv", + "cp24.com", + "cpac.ca", + "cracked.com", + "craftsy.com", + "embed.crooksandliars.com", + "crowdbunker.com", + "crowdbunker.com", + "crtvg.es", + "c-span.org", + "c-span.org", + "news.cts.com.tw", + "ctvnews.ca", + "barrie.ctvnews.ca", + "stox.ctvnews.ca", + "ottawa.ctvnews.ca", + "vancouverisland.ctvnews.ca", + "cu.ntv.co.jp", + "cultureunplugged.com", + "curiositystream.com", + "app.curiositystream.com", + "app.cybrary.it", + "app.cybrary.it", + "iframe.dacast.com", + "iframe.dacast.com", + "dagelijksekost.een.be", + "dailymail.co.uk", + "dailywire.com", + "dailywire.com", + "clubdam.com", + "dangalplay.com", + "tvpot.daum.net", + "m.tvpot.daum.net", + "videofarm.daum.net", + "player.daystar.tv", + "dagbladet.no", + "dctp.tv", + "democracynow.org", + "destinationamerica.com", + "2m.ma", + "2m.ma", + "dhm.de", + "digitalconcerthall.com", + "evt.dispeak.com", + "events.digitallyspeaking.com", + "sevt.dispeak.com", + "ultimedia.com", + "ladigitale.dev", + "discogs.com", + "discoverylife.com", + "dmax.de", + "tlc.de", + "discoveryplus.com", + "discoveryplus.in", + "discoveryplus.in", + "discoveryplus.com", + "discoveryplus.it", + "video.disney.com", + "starwars.com", + "videos.disneylatino.com", + "video.en.disneyme.com", + "video.disneyturkiye.com.tr", + "disneyjunior.disney.com", + "spiderman.marvelkids.com", + "disneyjunior.en.disneyme.com", + "disneychannel.de", + "deutschlandfunk.de", + "dlive.tv", + "douyin.com", + "v.douyu.com", + "vmobile.douyu.com", + "douyu.com", + "douyutv.com", + "dplay.se", + "dplay.dk", + "dplay.no", + "it.dplay.com", + "es.dplay.com", + "dplay.fi", + "dplay.jp", + "discoveryplus.se", + "discoveryplus.dk", + "discoveryplus.no", + "discoveryplus.it", + "discoveryplus.es", + "discoveryplus.fi", + "drooble.com", + "dropbox.com", + "watch.dropout.tv", + "watch.dropout.tv", + "drtalks.com", + "drtuber.com", + "m.drtuber.com", + "dr.dk", + "dr-massive.com", + "w.duboku.io", + "dumpert.nl", + "legacy.dumpert.nl", + "duoplay.ee", + "video.aktualne.cz", + "dw.com", + "zen.yandex.ru", + "dzen.ru", + "ebay.com", + "egghead.io", + "app.egghead.io", + "eggs.mu", + "1und1.tv", + "1und1.tv", + "1und1.tv", + "elonet.finna.fi", + "blogs.elpais.com", + "elcomidista.elpais.com", + "elpais.com", + "epv.elpais.com", + "eltrecetv.com.ar", + "cdn.embedly.com", + "empflix.com", + "epicon.in", + "epicon.in", + "epidemicsound.com", + "live.eplus.jp", + "theepochtimes.com", + "eporner.com", + "erocast.me", + "eroprofile.com", + "jupiter.err.ee", + "jupiterpluss.err.ee", + "lasteekraan.err.ee", + "ertflix.gr", + "ert.gr", + "espn.go.com", + "broadband.espn.go.com", + "nonredline.sports.espn.go.com", + "cdn.espn.go.com", + "espn.com", + "espnfc.us", + "espnfc.com", + "espn.go.com", + "espncricinfo.com", + "ettu.tv", + "ec.europa.eu", + "multimedia.europarl.europa.eu", + "europeantour.com", + "eurosport.com", + "eurosport.de", + "eurosport.dk", + "eurosport.nl", + "eurosport.es", + "eurosport.fr", + "eurosport.it", + "eurosport.hu", + "eurosport.no", + "eurosport.ro", + "eurosport.com.tr", + "eurosport.tvn24.pl", + "euscreen.eu", + "tvonline.ewe.de", + "tvonline.ewe.de", + "tvonline.ewe.de", + "expressen.se", + "di.se", + "facebook.com", + "es-la.facebook.com", + "m.facebook.com", + "zh-hk.facebook.com", + "facebookwkhpilnemxj7asaniu7vnjjbiltxjqhye3mhbshg7kx5tfyd.onion", + "facebook.com", + "fancode.com", + "fathom.video", + "aloula.sba.sa", + "bahry.com", + "maraya.sba.net.ae", + "sat7plus.org", + "aloula.sba.sa", + "bahry.com", + "maraya.sba.net.ae", + "sat7plus.org", + "faz.net", + "video.fc2.com", + "live.fc2.com", + "fifa.com", + "filmon.com", + "5-tv.ru", + "floatplane.com", + "beta.floatplane.com", + "floatplane.com", + "beta.floatplane.com", + "watch.foodnetwork.com", + "footyroom.com", + "fox.com", + "foxsports.com", + "foxnews.com", + "insider.foxnews.com", + "video.foxnews.com", + "video.insider.foxnews.com", + "video.foxbusiness.com", + "foxnews.com", + "foxsports.com", + "fptplay.vn", + "francaisfacile.rfi.fr", + "radiofrance.fr", + "france.tv", + "mobile.france.tv", + "francetvinfo.fr", + "france3-regions.francetvinfo.fr", + "freetv.com", + "freetv.com", + "api.frontendmasters.com", + "fod.fujitv.co.jp", + "funk.net", + "play.funk.net", + "funker530.com", + "fux.com", + "fuyin.tv", + "gab.com", + "tv.gab.com", + "gaia.com", + "gamedev.tv", + "gamejolt.com", + "gamejolt.com", + "gamejolt.com", + "gamejolt.com", + "gamejolt.com", + "gamejolt.com", + "gamespot.com", + "gamestar.de", + "gamepro.de", + "gaskrank.tv", + "gazeta.ru", + "gbnews.com", + "gbnews.uk", + "gdcvault.com", + "video.lastampa.it", + "video.huffingtonpost.it", + "video.espresso.repubblica.it", + "video.repubblica.it", + "video.ilsecoloxix.it", + "video.iltirreno.gelocal.it", + "video.messaggeroveneto.gelocal.it", + "video.ilpiccolo.gelocal.it", + "video.gazzettadimantova.gelocal.it", + "video.mattinopadova.gelocal.it", + "video.laprovinciapavese.gelocal.it", + "video.tribunatreviso.gelocal.it", + "video.nuovavenezia.gelocal.it", + "video.gazzettadimodena.gelocal.it", + "video.lanuovaferrara.gelocal.it", + "video.corrierealpi.gelocal.it", + "video.lasentinella.gelocal.it", + "gem.cbc.ca", + "genius.com", + "genius.com", + "germanupa.de", + "academymel.online", + "academymel.getcourse.ru", + "manibeauty.getcourse.ru", + "gaismasmandalas.getcourse.io", + "player02.getcourse.ru", + "cf-api-2.vhcdn.com", + "gettr.com", + "gettr.com", + "giantbomb.com", + "iptv.glattvision.ch", + "iptv.glattvision.ch", + "iptv.glattvision.ch", + "globalplayer.com", + "globalplayer.com", + "globalplayer.com", + "globalplayer.com", + "globalplayer.com", + "globoplay.globo.com", + "g1.globo.com", + "gq.globo.com", + "gshow.globo.com", + "oglobo.globo.com", + "ge.globo.com", + "redeglobo.globo.com", + "player.glomex.com", + "video.glomex.com", + "gmanetwork.com", + "abc.com", + "disneynow.com", + "fxnow.fxnetworks.com", + "freeform.com", + "nationalgeographic.com", + "go.discovery.com", + "discovery.com", + "new.godresource.com", + "godtube.com", + "gofile.io", + "goodgame.ru", + "drive.google.com", + "drive.usercontent.google.com", + "gopro.com", + "gotostage.com", + "vod.graspop.be", + "gronkh.tv", + "harpodeon.com", + "hearthis.at", + "heise.de", + "hellporno.com", + "hellporno.net", + "hgtv.com", + "de.hgtv.com", + "watch.hgtv.com", + "hidive.com", + "history.com", + "hkedcity.net", + "hollywoodreporter.com", + "hollywoodreporter.com", + "holodex.net", + "staging.holodex.net", + "hotstar.com", + "hessenschau.de", + "hr-fernsehen.de", + "hrti.hrt.hr", + "hrti.hrt.hr", + "hse.de", + "hse.de", + "hungama.com", + "un.hungama.com", + "hungama.com", + "un.hungama.com", + "hungama.com", + "un.hungama.com", + "huya.com", + "hytale.com", + "helsinkikanava.fi", + "suite.icareus.com", + "asahitv.fi", + "hyvinvointitv.fi", + "inez.fi", + "permanto.fi", + "videos.minifiddlers.org", + "app.idagio.com", + "app.idagio.com", + "app.idagio.com", + "app.idagio.com", + "app.idagio.com", + "idolplus.com", + "iflix.com", + "iflix.com", + "ign.com", + "pcmag.com", + "me.ign.com", + "ign.com", + "me.ign.com", + "adria.ign.com", + "kr.ign.com", + "iheart.com", + "iheartpodcastnetwork.com", + "ilpost.it", + "iltalehti.fi", + "imdb.com", + "imgur.com", + "i.imgur.com", + "ina.fr", + "m.ina.fr", + "inc.com", + "indavideo.hu", + "embed.indavideo.hu", + "infoq.com", + "instagram.com", + "media", + "internazionale.it", + "investigationdiscovery.com", + "prima.iprima.cz", + "zoom.iprima.cz", + "play.iprima.cz", + "iprima.cz", + "krimi.iprima.cz", + "cool.iprima.cz", + "love.iprima.cz", + "cnn.iprima.cz", + "iq.com", + "iqiyi.com", + "yule.iqiyi.com", + "pps.tv", + "watch.islamchannel.tv", + "watch.islamchannel.tv", + "israelnationalnews.com", + "app.itpro.tv", + "app.itpro.tv", + "itv.com", + "itv.com", + "ivi.ru", + "ivi.tv", + "ivideon.com", + "ivoox.com", + "go.ivoox.com", + "iwara.tv", + "ixigua.com", + "izlesene.com", + "jamendo.com", + "licensing.jamendo.com", + "jamendo.com", + "jeuxvideo.com", + "jiosaavn.com", + "saavn.com", + "media.joj.sk", + "jove.com", + "tv.jtbc.co.kr", + "vod.jtbc.co.kr", + "content.jwplatform.com", + "cdn.jwplayer.com", + "tv.kakao.com", + "kaltura.com", + "cdnapisec.kaltura.com", + "kankanews.com", + "mtv.fi", + "katsomo.fi", + "mtvuutiset.fi", + "members.kelbyone.com", + "video.kenh14.vn", + "video.kenh14.vn", + "khanacademy.org", + "kick.com", + "kicker.de", + "kickstarter.com", + "kika.de", + "kika.de", + "kinja.com", + "kinopoisk.ru", + "video.kompas.com", + "kooapp.com", + "live.erinn.biz", + "kuwo.cn", + "la7.it", + "laracasts.com", + "last.fm", + "last.fm", + "last.fm", + "laxarxames.cat", + "lbry.tv", + "lbry", + "odysee.com", + "tf1info.fr", + "lci.fr", + "lcp.fr", + "play.lcp.fr", + "le.com", + "sports.le.com", + "lesports.com", + "learningonscreen.ac.uk", + "app.lecturio.com", + "lecturio.de", + "app.lecturio.com", + "video.lefigaro.fr", + "video.lefigaro.fr", + "lego.com", + "lemonde.fr", + "redaction.actu.lemonde.fr", + "lenta.ru", + "le.com", + "tv.le.com", + "list.le.com", + "yuntv.letv.com", + "html5-player.libsyn.com", + "embed.life.ru", + "life.ru", + "likee.video", + "linkedin.com", + "liputan6.com", + "listennotes.com", + "litv.tv", + "new.livestream.com", + "livestream.com", + "original.livestream.com", + "livestreamfails.com", + "lnk.lt", + "loc.gov", + "loco.com", + "loom.com", + "lrt.lt", + "lrt.lt", + "lrt.lt", + "archyvai.lrt.lt", + "latvijasradio.lsm.lv", + "radioteatris.lsm.lv", + "lr1.lsm.lv", + "lr2.lsm.lv", + "klasika.lsm.lv", + "lr4.lsm.lv", + "pieci.lv", + "naba.lsm.lv", + "ltv.lsm.lv", + "replay.lsm.lv", + "lumni.fr", + "lynda.com", + "educourse.ga", + "player.maariv.co.il", + "magellantv.com", + "magentamusik.de", + "my.mail.ru", + "m.my.mail.ru", + "videoapi.my.mail.ru", + "webtools-e18da6642b684f8aa9ae449862783a56.msvdn.net", + "webtools-859c1818ed614cc5b0047439470927b0.msvdn.net", + "webtools-f5842579ff984c1c98d63b8d789673eb.msvdn.net", + "f5842579ff984c1c98d63b8d789673eb.msvdn.net", + "webtools.msvdn.net", + "859c1818ed614cc5b0047439470927b0.msvdn.net", + "mgtv.com", + "w.mgtv.com", + "manototv.com", + "manototv.com", + "manyvids.com", + "videoarchiv.markiza.sk", + "markiza.sk", + "dajto.markiza.sk", + "superstar.markiza.sk", + "hybsa.markiza.sk", + "doma.markiza.sk", + "tvnoviny.sk", + "masters.com", + "matchtv.ru", + "video.matchtv.ru", + "budem.mave.digital", + "ochenlichnoe.mave.digital", + "geekcity.mave.digital", + "mbn.co.kr", + "mdr.de", + "medal.tv", + "media.ccc.de", + "mediaite.com", + "mediaklikk.hu", + "m4sport.hu", + "hirado.hu", + "bndestem.nl", + "gelderlander.nl", + "7sur7.be", + "mychannels.video", + "embed.mychannels.video", + "mediasetinfinity.mediaset.it", + "mediasetplay.mediaset.it", + "static3.mediasetplay.mediaset.it", + "mediasetinfinity.mediaset.it", + "hitsmediaweb.h-its.org", + "mediasite.uib.no", + "collegerama.tudelft.nl", + "digitalops.sandia.gov", + "mediasite.ntnu.no", + "events7.mediasite.com", + "medaudio.medicine.iu.edu", + "uipsyc.mediasite.com", + "live.libraries.psu.edu", + "msite.misis.ru", + "mdstrm.com", + "vodupload-api.mediaworks.nz", + "medici.tv", + "edu.medici.tv", + "megatv.com", + "meipai.com", + "metacritic.com", + "mewatch.sg", + "live.mewatch.sg", + "build.microsoft.com", + "microsoft.com", + "learn.microsoft.com", + "learn.microsoft.com", + "learn.microsoft.com", + "medius.microsoft.com", + "web.microsoftstream.com", + "msit.microsoftstream.com", + "minds.com", + "mir24.tv", + "mirrativ.com", + "mirror.co.uk", + "mixch.tv", + "mixcloud.com", + "beta.mixcloud.com", + "suncity-104-9fm.mixlr.com", + "brcountdown.mixlr.com", + "biblewayng.mixlr.com", + "mlb.com", + "m.mlb.com", + "mlb.mlb.com", + "mlb.com", + "mlb.com", + "mlb.com", + "mlssoccer.com", + "whitecapsfc.com", + "torontofc.ca", + "sportingkc.com", + "soundersfc.com", + "sjearthquakes.com", + "rsl.com", + "timbers.com", + "philadelphiaunion.com", + "orlandocitysc.com", + "newyorkredbulls.com", + "nycfc.com", + "revolutionsoccer.net", + "nashvillesc.com", + "cfmontreal.com", + "intermiamicf.com", + "lagalaxy.com", + "lafc.com", + "houstondynamofc.com", + "dcunited.com", + "fcdallas.com", + "columbuscrew.com", + "coloradorapids.com", + "fccincinnati.com", + "chicagofirefc.com", + "austinfc.com", + "atlutd.com", + "tvplus.m-net.de", + "tvplus.m-net.de", + "tvplus.m-net.de", + "video.mocha.com.vn", + "mojevideo.sk", + "monstercat.com", + "monster-siren.hypergryph.com", + "motherless.com", + "motherless.com", + "motherless.com", + "motherless.com", + "moviefap.com", + "moviepilot.de", + "moview.id", + "msn.com", + "tvplay.lv", + "play.tv3.lt", + "tv3play.ee", + "tvplay.skaties.lv", + "tv3play.tv3.ee", + "mtv.com", + "mtvuutiset.fi", + "mujrozhlas.cz", + "murrtube.net", + "murrtube.net", + "muse.ai", + "musescore.com", + "musicdex.org", + "musicdex.org", + "musicdex.org", + "musicdex.org", + "stream.new", + "player.mux.com", + "mx3.ch", + "neo.mx3.ch", + "volksmusik.mx3.ch", + "mxplayer.in", + "mxplayer.in", + "myspace.com", + "myspass.de", + "mzaalo.com", + "n-tv.de", + "sportklub.n1info.rs", + "n1info.si", + "nova.rs", + "n1info.rs", + "hr.n1info.com", + "best-vod.umn.cdn.united.cloud", + "classics.nascar.com", + "tv.nate.com", + "tv.nate.com", + "video.nationalgeographic.com", + "nationalgeographic.com", + "tv.naver.com", + "tvcast.naver.com", + "now.naver.com", + "nba.com", + "secure.nba.com", + "watch.nba.com", + "nbc.com", + "nbcnews.com", + "today.com", + "msnbc.com", + "nbcolympics.com", + "stream.nbcolympics.com", + "nbcsports.com", + "stream.nbcsports.com", + "vplayer.nbcsports.com", + "nbcsports.com", + "nbclosangeles.com", + "telemundoarizona.com", + "nbcboston.com", + "ndr.de", + "khabar.ndtv.com", + "movies.ndtv.com", + "ndtv.com", + "auto.ndtv.com", + "sports.ndtv.com", + "gadgets.ndtv.com", + "profit.ndtv.com", + "food.ndtv.com", + "doctor.ndtv.com", + "swirlster.ndtv.com", + "nebula.tv", + "watchnebula.com", + "beta.nebula.tv", + "nekohacker.com", + "video.nest.com", + "video.nest.com", + "media.netapp.com", + "media.netapp.com", + "music.163.com", + "y.music.163.com", + "netplus.tv", + "netplus.tv", + "netplus.tv", + "netverse.id", + "netverse.id", + "netzkino.de", + "newgrounds.com", + "burn7.newgrounds.com", + "brian-beaton.newgrounds.com", + "newspicks.com", + "newsy.com", + "hk.apple.nextmedia.com", + "hk.dv.nextmedia.com", + "api.nexx.cloud", + "api.nexxcdn.com", + "arc.nexx.cloud", + "embed.nexx.cloud", + "nfb.ca", + "onf.ca", + "nfhsnetwork.com", + "nfl.com", + "chiefs.com", + "buffalobills.com", + "raiders.com", + "www2.nhk.or.jp", + "nhk.or.jp", + "nhk.or.jp", + "nhk.or.jp", + "nhk.or.jp", + "nhk.or.jp", + "www3.nhk.or.jp", + "www3.nhk.or.jp", + "nhl.com", + "wch2016.com", + "nick.com", + "nicovideo.jp", + "sp.nicovideo.jp", + "live.nicovideo.jp", + "nico.ms", + "nicochannel.jp", + "nicovideo.jp", + "ninaprotocol.com", + "nintendo.com", + "nitter.priv.pw", + "n-joy.de", + "nobelprize.org", + "mediaplayer.nobelprize.org", + "open.noice.id", + "nonktube.com", + "nos.nl", + "tn.nova.cz", + "fanda.nova.cz", + "novaplus.nova.cz", + "sport.tn.nova.cz", + "doma.nova.cz", + "prask.nova.cz", + "tv.nova.cz", + "media.cms.nova.cz", + "mediatn.cms.nova.cz", + "play.nova.bg", + "nowcanal.pt", + "nowness.com", + "cn.nowness.com", + "noz.de", + "npo.nl", + "ntr.nl", + "omroepwnl.nl", + "zapp.nl", + "npo3.nl", + "npostart.nl", + "npo.nl", + "npostart.nl", + "npr.org", + "nrk.no", + "v8-psapi.nrk.no", + "nrk.no", + "radio.nrk.no", + "nrk.no", + "tv.nrk.no", + "radio.nrk.no", + "tv.nrk.no", + "radio.nrk.no", + "tv.nrk.no", + "tv.nrk.no", + "tv.nrk.no", + "radio.nrk.no", + "tv.nrk.no", + "tv.nrksuper.no", + "radio.nrk.no", + "nrksuper.no", + "nts.live", + "ntv.ru", + "nuum.ru", + "nuvid.com", + "m.nuvid.com", + "nytimes.com", + "nytimes.com", + "cooking.nytimes.com", + "cooking.nytimes.com", + "nzherald.co.nz", + "nzonscreen.com", + "nzz.ch", + "ocw.mit.edu", + "ok.ru", + "m.ok.ru", + "mobile.ok.ru", + "of.tv", + "of.tv", + "olympics.com", + "event.on24.com", + "ondemandchina.com", + "ondemandkorea.com", + "ondemandkorea.com", + "onefootball.com", + "oneplace.com", + "eurosport.onet.pl", + "film.onet.pl", + "moto.onet.pl", + "businessinsider.com.pl", + "plejada.pl", + "onet.tv", + "onet100.vod.pl", + "onionstudios.com", + "share.onsen.ag", + "onsen.ag", + "oc-video1.ruhr-uni-bochum.de", + "oc-video1.ruhr-uni-bochum.de", + "electures.uni-muenster.de", + "openrec.tv", + "ora.tv", + "unsafespeech.com", + "on.orf.at", + "sound.orf.at", + "radiothek.orf.at", + "ooe.orf.at", + "fm4.orf.at", + "noe.orf.at", + "wien.orf.at", + "burgenland.orf.at", + "steiermark.orf.at", + "kaernten.orf.at", + "salzburg.orf.at", + "tirol.orf.at", + "vorarlberg.orf.at", + "oe3.orf.at", + "oe1.orf.at", + "tvonline.osnatel.de", + "tvonline.osnatel.de", + "tvonline.osnatel.de", + "outsidetv.com", + "ruhr-uni-bochum.sciebo.de", + "packtpub.com", + "subscription.packtpub.com", + "packtpub.com", + "subscription.packtpub.com", + "palcomp3.com.br", + "palcomp3.com", + "demo.hosted.panopto.com", + "howtovideos.hosted.panopto.com", + "unisa.au.panopto.com", + "na-training-1.hosted.panopto.com", + "ucc.cloud.panopto.eu", + "brown.hosted.panopto.com", + "demo.hosted.panopto.com", + "howtovideos.hosted.panopto.com", + "howtovideos.hosted.panopto.com", + "utsa.hosted.panopto.com", + "paramountpressexpress.com", + "parler.com", + "parliamentlive.tv", + "aph.gov.au", + "parti.com", + "patreon.com", + "pbs.org", + "thirteen.org", + "player.pbs.org", + "pbssocal.org", + "watch.knpb.org", + "pbskids.org", + "peekvids.com", + "peer.tv", + "members.onepeloton.com", + "player.performgroup.com", + "periscope.tv", + "pgatour.com", + "philharmoniedeparis.fr", + "live.philharmoniedeparis.fr", + "otoplayer.philharmoniedeparis.fr", + "phoenix.de", + "player.pia-live.jp", + "piapro.jp", + "picarto.tv", + "player.piksel.tech", + "player.piksel.com", + "pinkbike.com", + "es.pinkbike.org", + "pinterest.com", + "pinterest.ca", + "co.pinterest.com", + "pinterest.ca", + "piramide.tv", + "piramide.tv", + "planetmarathi.com", + "platzi.com", + "courses.platzi.com", + "platzi.com", + "courses.platzi.com", + "play.tv", + "player.fm", + "utreon.com", + "playeur.com", + "playsuisse.ch", + "playtvak.cz", + "slowtv.playtvak.cz", + "zpravy.idnes.cz", + "lidovky.cz", + "metro.cz", + "playvids.com", + "config.playwire.com", + "cdn.playwire.com", + "pluralsight.com", + "app.pluralsight.com", + "pluto.tv", + "plvideo.ru", + "podbay.fm", + "podbay.fm", + "podchaser.com", + "scienceteachingtips.podomatic.com", + "ostbahnhof.podomatic.com", + "podomatic.com", + "pokergo.com", + "pokergo.com", + "polsatgo.pl", + "jedynka.polskieradio.pl", + "trojka.polskieradio.pl", + "polskieradio.pl", + "radiokierowcow.pl", + "polskieradio24.pl", + "player.polskieradio.pl", + "podcasty.polskieradio.pl", + "animemanga.popcorntv.it", + "cinema.popcorntv.it", + "pornbox.com", + "pornerbros.com", + "m.pornerbros.com", + "pornflip.com", + "pornhub.com", + "fr.pornhub.com", + "thumbzilla.com", + "pornhub.net", + "pornhub.org", + "pornhubpremium.com", + "pornhubvybmsymdol4iibwgwtkpwmeyd6luq2gxajgjzfjvotyt5zhyd.onion", + "pornhub.com", + "pornhubvybmsymdol4iibwgwtkpwmeyd6luq2gxajgjzfjvotyt5zhyd.onion", + "pornhub.com", + "de.pornhub.com", + "pornhub.com", + "pornhubpremium.com", + "pornhubvybmsymdol4iibwgwtkpwmeyd6luq2gxajgjzfjvotyt5zhyd.onion", + "pornhub.com", + "pornhubvybmsymdol4iibwgwtkpwmeyd6luq2gxajgjzfjvotyt5zhyd.onion", + "porntop.com", + "porntube.com", + "m.porntube.com", + "pr0gramm.com", + "prankcast.com", + "prankcast.com", + "premiershiprugby.com", + "projectveritas.com", + "prosieben.de", + "prosiebenmaxx.de", + "sixx.de", + "sat1.de", + "kabeleins.de", + "ran.de", + "the-voice-of-germany.de", + "fem.com", + "kabeleinsdoku.de", + "sat1gold.de", + "galileo.tv", + "advopedia.de", + "beta.prx.org", + "beta.prx.org", + "beta.prx.org", + "listen.prx.org", + "puhutv.com", + "puls4.com", + "pyvideo.org", + "q-dance.com", + "qingting.fm", + "m.qtfm.cn", + "y.qq.com", + "quantum-tv.com", + "quantum-tv.com", + "quantum-tv.com", + "videos.r7.com", + "esportes.r7.com", + "noticias.r7.com", + "player.r7.com", + "radiko.jp", + "radiko.jp", + "radio1.be", + "ici.radio-canada.ca", + "radiocomercial.pt", + "radiocomercial.pt", + "radiofrance.fr", + "radiofrance.fr", + "radiofrance.fr", + "radiofrance.fr", + "radiokapital.pl", + "radioradicale.it", + "rad.live", + "raisport.rai.it", + "rai.it", + "raicultura.it", + "rainews.it", + "raiplay.it", + "raiplay.it", + "raiplay.it", + "raiplaysound.it", + "raiplaysound.it", + "raiplaysound.it", + "raisudtirol.rai.it", + "raibz.rai.it", + "raywenderlich.com", + "videos.raywenderlich.com", + "live.rbg.tum.de", + "tum.live", + "live.rbg.tum.de", + "tum.live", + "live.rbg.tum.de", + "tum.live", + "video.corriere.it", + "viaggi.corriere.it", + "video.rcs.it", + "video.gazzanet.gazzetta.it", + "video.gazzetta.it", + "leitv.it", + "youreporter.it", + "amica.it", + "rctiplus.com", + "rctiplus.com", + "rctiplus.com", + "rds.ca", + "redbull.com", + "redbull.com", + "redbull.tv", + "redbull.com", + "redbull.com", + "r.dcs.redcdn.pl", + "n-25-12.dcs.redcdn.pl", + "redir.atmcdn.pl", + "reddit.com", + "old.reddit.com", + "nm.reddit.com", + "redditmedia.com", + "redgifs.com", + "thumbs2.redgifs.com", + "redgifs.com", + "redgifs.com", + "redtube.com", + "embed.redtube.com", + "it.redtube.com", + "redtube.com.br", + "ren.tv", + "ren.tv", + "restudy.dk", + "portal.restudy.dk", + "reverbnation.com", + "rheinmaintv.de", + "ridehome.info", + "rinse.fm", + "rinse.fm", + "rmcdecouverte.bfmtv.com", + "rockstargames.com", + "rokfin.com", + "roosterteeth.com", + "achievementhunter.roosterteeth.com", + "funhaus.roosterteeth.com", + "screwattack.roosterteeth.com", + "theknow.roosterteeth.com", + "roosterteeth.com", + "rottentomatoes.com", + "en.roya.tv", + "roya.tv", + "prehravac.rozhlas.cz", + "wave.rozhlas.cz", + "dvojka.rozhlas.cz", + "rtbf.be", + "rtd.rt.com", + "rtd.rt.com", + "rte.ie", + "rtl.lu", + "5minutes.rtl.lu", + "today.rtl.lu", + "rtlxl.nl", + "rtl.nl", + "static.rtl.nl", + "embed.rtl.nl", + "rtl2.de", + "rtl.lu", + "rtl.lu", + "rt.com", + "rtp.pt", + "rtrfm.com.au", + "rts.ch", + "pages.rts.ch", + "media.rtvc.gov.co", + "rtvcplay.co", + "rtvcplay.co", + "rtve.es", + "rtvslo.si", + "365.rtvslo.si", + "4d.rtvslo.si", + "rudo.video", + "rule34video.com", + "rumble.com", + "rumble.com", + "rumble.com", + "ruptly.tv", + "rutube.ru", + "ruutu.fi", + "supla.fi", + "static.nelonenmedia.fi", + "ruv.is", + "ruv.is", + "s4c.cymru", + "s4c.cymru", + "safaribooksonline.com", + "techbus.safaribooksonline.com", + "learning.oreilly.com", + "oreilly.com", + "saitosan.net", + "saktv.ch", + "saktv.ch", + "saktv.ch", + "tv.salt.ch", + "tv.salt.ch", + "tv.salt.ch", + "samplefocus.com", + "webtv.sangiin.go.jp", + "videos.sapo.pt", + "v2.videos.sapo.pt", + "sauceplus.com", + "sbs.com.au", + "allvod.sbs.co.kr", + "programs.sbs.co.kr", + "sciencechannel.com", + "api.screen9.com", + "folkhogskolekanalen.screen9.tv", + "play.su.se", + "screencast.com", + "watch.screencastify.com", + "app.screencastify.com", + "screencast-o-matic.com", + "screenrec.com", + "cookingchanneltv.com", + "diynetwork.com", + "foodnetwork.com", + "hgtv.com", + "travelchannel.com", + "discovery.com", + "watch.geniuskitchen.com", + "scrolller.com", + "learning.scte.org", + "learning.scte.org", + "sejm.gov.pl", + "sejm-embed.redcdn.pl", + "senalcolombia.tv", + "help.senate.gov", + "appropriations.senate.gov", + "banking.senate.gov", + "agriculture.senate.gov", + "aging.senate.gov", + "budget.senate.gov", + "commerce.senate.gov", + "energy.senate.gov", + "epw.senate.gov", + "foreign.senate.gov", + "intelligence.senate.gov", + "inaugural.senate.gov", + "rules.senate.gov", + "sbc.senate.gov", + "veterans.senate.gov", + "senate.gov", + "servustv.com", + "servus.com", + "pm-wissen.com", + "seznamzpravy.cz", + "seznamzpravy.cz", + "seznam.cz", + "shahid.mbc.net", + "shahid.mbc.net", + "lut-my.sharepoint.com", + "greaternyace.sharepoint.com", + "izoobasisschool.sharepoint.com", + "uskudaredutr-my.sharepoint.com", + "epam-my.sharepoint.com", + "microsoft.sharepoint.com", + "shemaroome.com", + "shiey.com", + "shugiintv.go.jp", + "shugiintv.go.jp", + "shugiintv.go.jp", + "the-re-bind-io-podcast.simplecast.com", + "api.simplecast.com", + "player.simplecast.com", + "video.sina.com.cn", + "skeb.jp", + "skysports.com", + "sport.sky.it", + "tg24.sky.it", + "skynewsarabia.com", + "skynews.com.au", + "slideslive.com", + "slutload.com", + "mobile.slutload.com", + "smotrim.ru", + "player.smotrim.ru", + "testplayer.vgtrk.com", + "snapchat.com", + "snotr.com", + "softwhiteunderbelly.com", + "tv.sohu.com", + "my.tv.sohu.com", + "tv.sohu.com", + "sonyliv.com", + "sonyliv.com", + "vod.sooplive.co.kr", + "play.sooplive.co.kr", + "ch.sooplive.co.kr", + "w.soundcloud.com", + "southpark.cc.com", + "southparkstudios.com", + "southpark.de", + "southpark.lat", + "southparkstudios.co.uk", + "southparkstudios.com.br", + "southparkstudios.nu", + "sovietscloset.com", + "sovietscloset.com", + "spankbang.com", + "m.spankbang.com", + "spiegel.de", + "vod.sport5.co.il", + "sport5.co.il", + "news.sportbox.ru", + "matchtv.ru", + "sporteurope.tv", + "player.sporteurope.tv", + "api.spreaker.com", + "spreaker.com", + "api.spreaker.com", + "spreaker.com", + "cms.springboardplatform.com", + "videos.sproutvideo.com", + "sr-mediathek.de", + "srf.ch", + "rtr.ch", + "rts.ch", + "play.swissinfo.ch", + "stacommu.jp", + "stacommu.jp", + "stage-plus.com", + "startrek.com", + "startv.com.tr", + "store.steampowered.com", + "steamcommunity.com", + "steamcommunity.com", + "stitcher.com", + "stitcher.com", + "storyfire.com", + "players.streaks.jp", + "playback.api.streaks.jp", + "streamable.com", + "televizeseznam.cz", + "stream.cz", + "streetvoice.com", + "tw.streetvoice.com", + "stripchat.com", + "player.stv.tv", + "rtvs.sk", + "stvr.sk", + "subsplash.com", + "prophecywatchers.subspla.sh", + "haleynahman.substack.com", + "andrewzimmern.substack.com", + "persuasion1.substack.com", + "sunporno.com", + "embeds.sunporno.com", + "sverigesradio.se", + "svt.se", + "svtplay.se", + "oppetarkiv.se", + "swearnet.com", + "syfy.com", + "24syv.dk", + "taptap.cn", + "taptap.io", + "taptap.cn", + "taptap.io", + "tass.ru", + "itar-tass.com", + "tbs.com", + "tntdrama.com", + "trutv.com", + "cu.tbs.co.jp", + "cu.tbs.co.jp", + "cu.tbs.co.jp", + "gns3.teachable.com", + "v1.upskillcourses.com", + "v1.upskillcourses.com", + "gns3.teachable.com", + "teachertube.com", + "teamcoco.com", + "teamtreehouse.com", + "embed.ted.com", + "ted.com", + "ted.com", + "ted.com", + "tele5.de", + "t13.cl", + "bx1.be", + "play.telecaribe.co", + "telecinco.es", + "cuatro.com", + "mediaset.es", + "t.me", + "telemb.be", + "telemundo.com", + "zonevideo.telequebec.tv", + "coucou.telequebec.tv", + "lindicemcsween.telequebec.tv", + "bancpublic.telequebec.tv", + "telequebec.tv", + "squat.telequebec.tv", + "video.telequebec.tv", + "telewebion.com", + "video.tempo.co", + "tennistv.com", + "tf1.fr", + "theater-complex.town", + "watch.thechosen.tv", + "watch.thechosen.tv", + "theguardian.com", + "theguardian.com", + "thehighwire.com", + "the-hole.tv", + "theintercept.com", + "link.theplatform.com", + "player.theplatform.com", + "feed.theplatform.com", + "thesun.co.uk", + "the-sun.com", + "weather.com", + "thisamericanlife.org", + "thisoldhouse.com", + "thisvid.com", + "thisvid.com", + "thisvid.com", + "3speak.tv", + "3speak.tv", + "tiktok.com", + "m.tiktok.com", + "go.tlc.com", + "tmz.com", + "tnaflix.com", + "player.tnaflix.com", + "player.empflix.com", + "mewatch.sg", + "video.toggle.sg", + "toggo.de", + "audycje.tokfm.pl", + "toongoggles.com", + "ici.tou.tv", + "toutiao.com", + "watch.travelchannel.com", + "triller.co", + "v.triller.co", + "triller.co", + "trovo.live", + "trtcocuk.net.tr", + "trtworld.com", + "trueid.id", + "vn.trueid.net", + "trueid.ph", + "truthsocial.com", + "flextv.co.kr", + "tube8.com", + "tube.tugraz.at", + "tube.tugraz.at", + "tubitv.com", + "tatianamaslanydaily.tumblr.com", + "maskofthedragon.tumblr.com", + "shieldfoss.tumblr.com", + "jujanon.tumblr.com", + "bartlebyshop.tumblr.com", + "afloweroutofstone.tumblr.com", + "prozdvoices.tumblr.com", + "dominustempori.tumblr.com", + "silami.tumblr.com", + "tumblr.com", + "patricia-taxxon.tumblr.com", + "silverfoxstole.tumblr.com", + "fansofcolor.tumblr.com", + "tunein.com", + "tv2.no", + "tv2.no", + "tvsyd.dk", + "tv2lorry.dk", + "tv2ostjylland.dk", + "tvmidtvest.dk", + "tv2fyn.dk", + "tv2east.dk", + "tv2nord.dk", + "tv2kosmopol.dk", + "tv2play.hu", + "tv2play.hu", + "tv4.se", + "tv4play.se", + "tv5monde.com", + "tv5unis.ca", + "tv8.it", + "tvaplus.ca", + "tvc.ru", + "tvc.ru", + "tver.jp", + "tvigle.ru", + "cloud.tvigle.ru", + "tviplayer.iol.pt", + "tvn24.pl", + "tvnmeteo.tvn24.pl", + "fakty.tvn24.pl", + "sport.tvn24.pl", + "tvn24bis.pl", + "tvnoe.cz", + "cdn.ethnos.gr", + "ethnos.gr", + "tvopen.gr", + "tvp.pl", + "tvp.info", + "wiadomosci.tvp.pl", + "swipeto.pl", + "warszawa.tvp.pl", + "opole.tvp.pl", + "abc.tvp.pl", + "jp2.tvp.pl", + "vod.tvp.pl", + "krakow.tvp.pl", + "teleexpress.tvp.pl", + "sport.tvp.pl", + "tvpparlament.pl", + "tvpworld.com", + "stream.tvp.pl", + "tvpstream.vod.tvp.pl", + "play.tv3.lt", + "tv3play.skaties.lv", + "play.tv3.ee", + "tvw.org", + "twitcasting.tv", + "twitcasting.tv", + "twitcasting.tv", + "twitter.com", + "x.com", + "twitter3e4tixl4xyajtrzo62zg5vztmjuricljdp2c5kshju4avyoid.onion", + "txxx.com", + "txxx.tube", + "vxxx.com", + "hclips.com", + "hdzog.com", + "hdzog.tube", + "hotmovs.com", + "hotmovs.tube", + "inporn.com", + "privatehomeclips.com", + "tubepornclassic.com", + "upornia.com", + "upornia.tube", + "vjav.com", + "vjav.tube", + "voyeurhit.com", + "voyeurhit.tube", + "udemy.com", + "wipro.udemy.com", + "video.udn.com", + "ukcolumn.org", + "uktvplay.uktv.co.uk", + "uktvplay.co.uk", + "player-api.p.uliza.jp", + "ulizaportal.jp", + "universal-music.de", + "utv.unistra.fr", + "webtv.un.org", + "unity3d.com", + "player.mais.uol.com.br", + "tvuol.uol.com.br", + "mais.uol.com.br", + "noticias.band.uol.com.br", + "videos.band.uol.com.br", + "noticias.uol.com.br", + "urplay.se", + "urskola.se", + "usanetwork.com", + "usatoday.com", + "ustream.tv", + "video.ibm.com", + "video.varzesh3.com", + "vbox7.com", + "i49.vbox7.com", + "app.veo.co", + "vevo.com", + "embed.vevo.com", + "tv.vevo.com", + "vevo.com", + "vgtv.no", + "aftenposten.no", + "tv.vg.no", + "bt.no", + "ap.vgtv.no", + "tv.aftonbladet.se", + "aftonbladet.se", + "vh1.com", + "vice.com", + "video.vice.com", + "vms.vice.com", + "viceland.com", + "vicetv.com", + "viddler.com", + "videa.hu", + "videakid.hu", + "video.arnes.si", + "video.sky.it", + "xfactor.sky.it", + "masterchef.sky.it", + "videos.neurips.cc", + "videos.icts.res.in", + "videos.cncf.io", + "videos.icts.res.in", + "videos.neurips.cc", + "videos.cncf.io", + "player.videoken.com", + "videos.icts.res.in", + "videos.neurips.cc", + "videos.icts.res.in", + "videos.cncf.io", + "videomore.ru", + "player.videomore.ru", + "odysseus.more.tv", + "siren.more.tv", + "more.tv", + "videopress.com", + "video.wordpress.com", + "video.hockeycanada.ca", + "myfbcgreenville.vidflex.tv", + "figureitoutbaseball.com", + "videos.telusworldofscienceedmonton.ca", + "tuffhedemantv.com", + "albertalacrossetv.com", + "silenticetv.com", + "jphl.vidflex.tv", + "vidio.com", + "vidio.com", + "vidio.com", + "vidlii.com", + "vid.ly", + "s.vid.ly", + "how-to-video.vids.io", + "vyexample03.hubs.vidyard.com", + "share.vidyard.com", + "embed.vidyard.com", + "thelink.hubs.vidyard.com", + "salesforce.vidyard.com", + "play.vidyard.com", + "embed.snagfilms.com", + "snagfilms.com", + "main.snagfilms.com", + "winnersview.com", + "monumentalsportsnetwork.com", + "marquee.tv", + "hoichoi.tv", + "chorki.com", + "videolectures.net", + "vimm.tv", + "vimp.oth-regensburg.de", + "hsbi.de", + "videocampus.sachsen.de", + "www2.univ-sba.dz", + "vimp.weka-fachmedien.de", + "cdn.viqeo.tv", + "api.viqeo.tv", + "viu.com", + "india.viu.com", + "viu.com", + "vk.com", + "vkvideo.ru", + "new.vk.com", + "vk.ru", + "vksport.vkvideo.ru", + "m.vk.com", + "vkplay.live", + "live.vkplay.ru", + "live.vkvideo.ru", + "vkplay.live", + "live.vkplay.ru", + "live.vkvideo.ru", + "tiktok.com", + "vm.tiktok.com", + "vt.tiktok.com", + "vocaroo.com", + "voca.ro", + "vod.pl", + "vod-platform.net", + "embed.kwikmotion.com", + "voicy.jp", + "volej.tv", + "theverge.com", + "vox.com", + "sbnation.com", + "tegenlicht.vpro.nl", + "vpro.nl", + "2doc.nl", + "v.qq.com", + "livr.jp", + "vrt.be", + "sporza.be", + "vrt.be", + "vtm.be", + "vtv.vn", + "vtvgo.vn", + "vtxtv.ch", + "vtxtv.ch", + "vtxtv.ch", + "vvvvid.it", + "vvvvid.it", + "player.waly.tv", + "player.waly.tv", + "player.waly.tv", + "washingtonpost.com", + "wat.tv", + "espn.com", + "deviceids-medp.wdr.de", + "www1.wdr.de", + "wdrmaus.de", + "sportschau.de", + "kinder.wdr.de", + "web.archive.org", + "warszawa-plac-zamkowy.webcamera.pl", + "gdansk-stare-miasto.webcamera.pl", + "bl.webcaster.pro", + "webofstories.com", + "weibo.com", + "m.weibo.cn", + "weibo.com", + "weibo.com", + "video.weibo.com", + "weiqitv.com", + "wetv.vip", + "wetv.vip", + "weverse.io", + "weverse.io", + "weverse.io", + "weverse.io", + "weverse.io", + "weverse.io", + "wevidi.net", + "weyyak.com", + "whowatch.tv", + "whyp.it", + "commons.wikimedia.org", + "wimbledon.com", + "platform.wim.tv", + "winsports.co", + "fast.wistia.net", + "fast.wistia.com", + "fast.wistia.net", + "fast.wistia.net", + "omroepwnl.nl", + "worldstarhiphop.com", + "m.worldstarhiphop.com", + "pilot.wp.pl", + "wppilot", + "wrestle-universe.com", + "wrestle-universe.com", + "video-api.wsj.com", + "wsj.com", + "barrons.com", + "wsj.com", + "wwe.com", + "de.wwe.com", + "wyborcza.pl", + "wyborcza.pl", + "wysokieobcasy.pl", + "wykop.pl", + "xboxclips.com", + "gameclips.io", + "xhamster.com", + "m.xhamster.com", + "it.xhamster.com", + "pt.xhamster.com", + "xhamster.one", + "xhamster.desi", + "xhamster2.com", + "xhamster11.com", + "xhamster26.com", + "de.xhamster.com", + "xhday.com", + "xhvid.com", + "xhamster20.desi", + "xhamster.com", + "xhamster.com", + "xhday.com", + "xhvid.com", + "xiaohongshu.com", + "ximalaya.com", + "m.ximalaya.com", + "xinpianchang.com", + "xnxx.com", + "video.xnxx.com", + "xnxx3.com", + "frontend.xstream.dk", + "xvideos.com", + "flashservice.xvideos.com", + "static-hw.xvideos.com", + "xvideos.es", + "fr.xvideos.com", + "it.xvideos.com", + "de.xvideos.com", + "screen.yahoo.com", + "uk.screen.yahoo.com", + "news.yahoo.com", + "yahoo.com", + "gma.yahoo.com", + "sports.yahoo.com", + "tw.news.yahoo.com", + "tw.video.yahoo.com", + "malaysia.news.yahoo.com", + "es-us.noticias.yahoo.com", + "news.yahoo.co.jp", + "yadi.sk", + "disk.360.yandex.ru", + "music.yandex.ru", + "music.yandex.com", + "yandex.ru", + "frontend.vh.yandex.ru", + "yandex.ru", + "yandex.com", + "yapfiles.ru", + "api.yapfiles.ru", + "yappy.media", + "yappy.media", + "yfanefa.com", + "areena.yle.fi", + "youjizz.com", + "player.youku.com", + "v.youku.com", + "play.tudou.com", + "list.youku.com", + "youporn.com", + "youporn.com", + "youporn.com", + "youporn.com", + "youporn.com", + "youporn.com", + "youporn.com", + "youtube.com", + "music.youtube.com", + "invidio.us", + "youtubekids.com", + "youtube.com", + "youtu.be", + "zaiko.io", + "zaiko.io", + "zapiks.fr", + "zapiks.com", + "zattoo.com", + "zattoo.com", + "zattoo.com", + "zattoo.com", + "zdf.de", + "zee5.com", + "zeenews.india.com", + "zenporn.com", + "zetland.dk", + "zingmp3.vn", + "mp3.zing.vn", + "economist.zoom.us", + "ffgolf.zoom.us", + "us02web.zoom.us", + "cityofdetroit.zoom.us" + ], + "redirectors": [ + "1fichier.com", + "adf.ly", + "pan.baidu.com", + "big.to", + "isrbx.net", + "israbox-music.com", + "isrbx.me", + "bit.ly", + "protect.ddl-island.su", + "dereferer.org", + "dl-protect.top", + "dl-protecte.org", + "protect-lien.com", + "protect-zt.com", + "protecte-link.com", + "liens-telechargement.com", + "dl-protect1.com", + "dl-protect1.co", + "dl-protect.best", + "ed-protect.org", + "redirect.example.com", + "extreme-download.club", + "extreme-protect.net", + "filecrypt.cc", + "generic.tld", + "google.com", + "go_to.com", + "sumoweb.net", + "liencaptcha.com", + "mediafire.com", + "mega.nz", + "mega.co.nz", + "multiup.org", + "prodebrid.com", + "prodebrid.com", + "rapidgator.net", + "rg.to", + "safelinking.net", + "nfo.scene-rls.net", + "searchlossless.com", + "turbobit.net", + "tinyurl.com", + "uploaded.net", + "example.com", + "youtube.com", + "zt-protect.com" + ] + } +} \ No newline at end of file diff --git a/Provider/alldebrid.py b/Provider/alldebrid.py index 773fd56..dd3d28b 100644 --- a/Provider/alldebrid.py +++ b/Provider/alldebrid.py @@ -1,6 +1,7 @@ from __future__ import annotations import hashlib +import json import sys import time from pathlib import Path @@ -8,11 +9,145 @@ from typing import Any, Dict, Iterable, List, Optional, Callable, Tuple from urllib.parse import urlparse from API.HTTP import HTTPClient -from API.alldebrid import AllDebridClient, parse_magnet_or_hash, is_magnet_link, is_torrent_file +from API.alldebrid import AllDebridClient, parse_magnet_or_hash, is_torrent_file from ProviderCore.base import Provider, SearchResult from ProviderCore.download import sanitize_filename from SYS.download import _download_direct_file from SYS.logger import log +from SYS.models import DownloadError + +_HOSTS_CACHE_TTL_SECONDS = 24 * 60 * 60 + + +def _repo_root() -> Path: + try: + return Path(__file__).resolve().parents[1] + except Exception: + return Path(".") + + +def _hosts_cache_path() -> Path: + # Keep this local to the repo so it works in portable installs. + # The registry's URL routing can read this file without instantiating providers. + # + # This file is expected to be the JSON payload shape from AllDebrid: + # {"status":"success","data":{"hosts":[...],"streams":[...],"redirectors":[...]}} + return _repo_root() / "API" / "data" / "alldebrid.json" + + +def _load_cached_domains(category: str) -> List[str]: + """Load cached domain list from API/data/alldebrid.json. + + category: "hosts" | "streams" | "redirectors" + """ + + wanted = str(category or "").strip().lower() + if wanted not in {"hosts", "streams", "redirectors"}: + return [] + + path = _hosts_cache_path() + try: + if not path.exists() or not path.is_file(): + return [] + payload = json.loads(path.read_text(encoding="utf-8")) + except Exception: + return [] + + if not isinstance(payload, dict): + return [] + + data = payload.get("data") + if not isinstance(data, dict): + # Back-compat for older cache shapes. + data = payload + if not isinstance(data, dict): + return [] + + raw_list = data.get(wanted) + if not isinstance(raw_list, list): + return [] + + out: List[str] = [] + seen: set[str] = set() + for d in raw_list: + try: + dom = str(d or "").strip().lower() + except Exception: + continue + if not dom: + continue + if dom.startswith("http://") or dom.startswith("https://"): + # Accidentally stored as a URL; normalize to hostname. + try: + p = urlparse(dom) + dom = str(p.hostname or "").strip().lower() + except Exception: + continue + if dom.startswith("www."): + dom = dom[4:] + if not dom or dom in seen: + continue + seen.add(dom) + out.append(dom) + return out + + +def _load_cached_hoster_domains() -> List[str]: + # For URL routing (download-file), we intentionally use only the "hosts" list. + # The "streams" list is extremely broad and would steal URLs from other providers. + return _load_cached_domains("hosts") + + +def _save_cached_hosts_payload(payload: Dict[str, Any]) -> None: + path = _hosts_cache_path() + try: + path.parent.mkdir(parents=True, exist_ok=True) + except Exception: + return + try: + path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8") + except Exception: + return + + +def _cache_is_fresh() -> bool: + path = _hosts_cache_path() + try: + if not path.exists() or not path.is_file(): + return False + mtime = float(path.stat().st_mtime) + return (time.time() - mtime) < _HOSTS_CACHE_TTL_SECONDS + except Exception: + return False + + +def _fetch_hosts_payload_v4_hosts() -> Optional[Dict[str, Any]]: + """Fetch the public AllDebrid hosts payload. + + This intentionally does NOT require an API key. + Endpoint referenced by user: https://api.alldebrid.com/v4/hosts + """ + + url = "https://api.alldebrid.com/v4/hosts" + try: + with HTTPClient(timeout=20.0) as client: + resp = client.get(url) + resp.raise_for_status() + data = resp.json() + return data if isinstance(data, dict) else None + except Exception as exc: + log(f"[alldebrid] Failed to fetch hosts list: {exc}", file=sys.stderr) + return None + + +def refresh_alldebrid_hoster_cache(*, force: bool = False) -> None: + """Refresh the on-disk cache of host domains (best-effort).""" + if (not force) and _cache_is_fresh(): + return + + payload = _fetch_hosts_payload_v4_hosts() + if isinstance(payload, dict) and payload: + _save_cached_hosts_payload(payload) def _get_debrid_api_key(config: Dict[str, Any]) -> Optional[str]: @@ -177,7 +312,7 @@ def prepare_magnet( api_key = _get_debrid_api_key(config or {}) if not api_key: try: - from ProviderCore.registry import show_provider_config_panel + from SYS.rich_display import show_provider_config_panel show_provider_config_panel("alldebrid", ["api_key"]) except Exception: @@ -193,7 +328,8 @@ def prepare_magnet( try: magnet_info = client.magnet_add(magnet_spec) - magnet_id = int(magnet_info.get("id", 0)) + magnet_id_val = magnet_info.get("id") or 0 + magnet_id = int(magnet_id_val) if magnet_id <= 0: log(f"AllDebrid magnet submission failed: {magnet_info}", file=sys.stderr) return None, None @@ -409,6 +545,26 @@ def adjust_output_dir_for_alldebrid( class AllDebrid(Provider): # Magnet URIs should be routed through this provider. URL = ("magnet:",) + URL_DOMAINS = () + + @classmethod + def url_patterns(cls) -> Tuple[str, ...]: + # Combine static patterns with cached host domains. + patterns = list(super().url_patterns()) + try: + cached = _load_cached_hoster_domains() + for d in cached: + dom = str(d or "").strip().lower() + if dom and dom not in patterns: + patterns.append(dom) + log( + f"[alldebrid] url_patterns loaded {len(cached)} cached host domains; total patterns={len(patterns)}", + file=sys.stderr, + ) + except Exception: + pass + return tuple(patterns) + """Search provider for AllDebrid account content. This provider lists and searches the files/magnets already present in the @@ -421,7 +577,15 @@ class AllDebrid(Provider): def validate(self) -> bool: # Consider "available" when configured; actual API connectivity can vary. - return bool(_get_debrid_api_key(self.config or {})) + ok = bool(_get_debrid_api_key(self.config or {})) + if ok: + # Best-effort: refresh cached host domains so future URL routing can + # route supported hosters through this provider. + try: + refresh_alldebrid_hoster_cache(force=False) + except Exception: + pass + return ok def download(self, result: SearchResult, output_dir: Path) -> Optional[Path]: """Download an AllDebrid SearchResult into output_dir. @@ -435,10 +599,12 @@ class AllDebrid(Provider): try: api_key = _get_debrid_api_key(self.config or {}) if not api_key: + log("[alldebrid] download skipped: missing api_key", file=sys.stderr) return None target = str(getattr(result, "path", "") or "").strip() if not target.startswith(("http://", "https://")): + log(f"[alldebrid] download skipped: target not http(s): {target}", file=sys.stderr) return None try: @@ -449,35 +615,59 @@ class AllDebrid(Provider): log(f"[alldebrid] Failed to init client: {exc}", file=sys.stderr) return None - # Quiet mode when download-file is mid-pipeline. - quiet = ( - bool(self.config.get("_quiet_background_output")) - if isinstance(self.config, - dict) else False - ) + log(f"[alldebrid] download routing target={target}", file=sys.stderr) - unlocked_url = target - try: - unlocked = client.unlock_link(target) - if isinstance(unlocked, - str) and unlocked.strip().startswith(("http://", - "https://")): - unlocked_url = unlocked.strip() - except Exception as exc: - # Fall back to the raw link, but warn. - log(f"[alldebrid] Failed to unlock link: {exc}", file=sys.stderr) - - # Prefer provider title as the output filename. - suggested = sanitize_filename( - str(getattr(result, - "title", - "") or "").strip() - ) + # Prefer provider title as the output filename; later we may override if unlocked URL has a better basename. + suggested = sanitize_filename(str(getattr(result, "title", "") or "").strip()) suggested_name = suggested if suggested else None - try: - from SYS.download import _download_direct_file + # Quiet mode when download-file is mid-pipeline. + quiet = bool(self.config.get("_quiet_background_output")) if isinstance(self.config, dict) else False + def _html_guard(path: Path) -> bool: + try: + if path.exists(): + size = path.stat().st_size + if size > 0 and size <= 250_000 and path.suffix.lower() not in (".html", ".htm"): + head = path.read_bytes()[:512] + try: + text = head.decode("utf-8", errors="ignore").lower() + except Exception: + text = "" + if " Optional[Path]: + # If this is an unlocked debrid link (allow_html=True), stream it directly and skip + # the generic HTML guard to avoid falling back to the public hoster. + if allow_html: + try: + from API.HTTP import HTTPClient + + fname = suggested_name or sanitize_filename(Path(urlparse(unlocked_url).path).name) + if not fname: + fname = "download" + if not Path(fname).suffix: + fname = f"{fname}.bin" + dest = Path(output_dir) / fname + dest.parent.mkdir(parents=True, exist_ok=True) + with HTTPClient(timeout=30.0) as client: + with client._request_stream("GET", unlocked_url, follow_redirects=True) as resp: + resp.raise_for_status() + with dest.open("wb") as fh: + for chunk in resp.iter_bytes(): + if not chunk: + continue + fh.write(chunk) + return dest if dest.exists() else None + except Exception as exc2: + log(f"[alldebrid] raw stream (unlocked) failed: {exc2}", file=sys.stderr) + return None + + # Otherwise, use standard downloader with guardrails. pipe_progress = None try: if isinstance(self.config, dict): @@ -485,47 +675,73 @@ class AllDebrid(Provider): except Exception: pipe_progress = None - dl_res = _download_direct_file( - unlocked_url, - Path(output_dir), - quiet=quiet, - suggested_filename=suggested_name, - pipeline_progress=pipe_progress, - ) - downloaded_path = getattr(dl_res, "path", None) - if downloaded_path is None: - return None - downloaded_path = Path(str(downloaded_path)) - - # Guard: if we got an HTML error/redirect page, treat as failure. try: - if downloaded_path.exists(): - size = downloaded_path.stat().st_size - if (size > 0 and size <= 250_000 - and downloaded_path.suffix.lower() not in (".html", - ".htm")): - head = downloaded_path.read_bytes()[:512] - try: - text = head.decode("utf-8", errors="ignore").lower() - except Exception: - text = "" - if " {unlocked_url}", file=sys.stderr) except Exception as exc: - log(f"[alldebrid] Download failed: {exc}", file=sys.stderr) - return None + log(f"[alldebrid] Failed to unlock link: {exc}", file=sys.stderr) + + if unlocked_url != target: + # Prefer filename from unlocked URL path. + try: + unlocked_name = sanitize_filename(Path(urlparse(unlocked_url).path).name) + if unlocked_name: + suggested_name = unlocked_name + except Exception: + pass + + # When using an unlocked URL different from the original hoster, stream it directly and do NOT fall back to the public URL. + allow_html = unlocked_url != target + log( + f"[alldebrid] downloading from {unlocked_url} (allow_html={allow_html})", + file=sys.stderr, + ) + downloaded = _download_unlocked(unlocked_url, allow_html=allow_html) + if downloaded: + log(f"[alldebrid] downloaded -> {downloaded}", file=sys.stderr) + return downloaded + + # If unlock failed entirely and we never changed URL, allow a single attempt on the original target. + if unlocked_url == target: + downloaded = _download_unlocked(target, allow_html=False) + if downloaded: + log(f"[alldebrid] downloaded (original target) -> {downloaded}", file=sys.stderr) + return downloaded + + return None except Exception: return None @@ -620,9 +836,12 @@ class AllDebrid(Provider): if magnet_id_val is None: magnet_id_val = kwargs.get("magnet_id") + if magnet_id_val is None: + return [] + try: magnet_id = int(magnet_id_val) - except Exception: + except (TypeError, ValueError): return [] magnet_status: Dict[str, @@ -769,9 +988,12 @@ class AllDebrid(Provider): if not isinstance(magnet, dict): continue + magnet_id_val = magnet.get("id") + if magnet_id_val is None: + continue try: - magnet_id = int(magnet.get("id")) - except Exception: + magnet_id = int(magnet_id_val) + except (TypeError, ValueError): continue magnet_name = str( diff --git a/ProviderCore/registry.py b/ProviderCore/registry.py index 748c706..ffbb9ca 100644 --- a/ProviderCore/registry.py +++ b/ProviderCore/registry.py @@ -224,11 +224,19 @@ def match_provider_name_for_url(url: str) -> Optional[str]: # # This keeps direct downloads and item pages routed to `internetarchive`, while # preserving OpenLibrary's scripted borrow pipeline for loan/reader URLs. - if host: - if host == "openlibrary.org" or host.endswith(".openlibrary.org"): + def _norm_host(h: str) -> str: + h_norm = str(h or "").strip().lower() + if h_norm.startswith("www."): + h_norm = h_norm[4:] + return h_norm + + host_norm = _norm_host(host) + + if host_norm: + if host_norm == "openlibrary.org" or host_norm.endswith(".openlibrary.org"): return "openlibrary" if "openlibrary" in _PROVIDERS else None - if host == "archive.org" or host.endswith(".archive.org"): + if host_norm == "archive.org" or host_norm.endswith(".archive.org"): low_path = str(path or "").lower() is_borrowish = ( low_path.startswith("/borrow/") or low_path.startswith("/stream/") @@ -243,16 +251,20 @@ def match_provider_name_for_url(url: str) -> Optional[str]: if not domains: continue for d in domains: - dom = str(d or "").strip().lower() + dom_raw = str(d or "").strip() + dom = dom_raw.lower() if not dom: continue - if raw_url_lower.startswith(dom): - return name - for d in domains: - dom = str(d or "").strip().lower() - if not dom or not host: + # Scheme-like patterns (magnet:, http://example) still use prefix match. + if dom.startswith("magnet:") or dom.startswith("http://") or dom.startswith("https://"): + if raw_url_lower.startswith(dom): + return name continue - if host == dom or host.endswith("." + dom): + + dom_norm = _norm_host(dom) + if not dom_norm or not host_norm: + continue + if host_norm == dom_norm or host_norm.endswith("." + dom_norm): return name return None diff --git a/Store/HydrusNetwork.py b/Store/HydrusNetwork.py index 2a186cf..6ee4e39 100644 --- a/Store/HydrusNetwork.py +++ b/Store/HydrusNetwork.py @@ -2,9 +2,13 @@ from __future__ import annotations import re import sys +import tempfile +import shutil from pathlib import Path from typing import Any, Dict, List, Optional, Tuple +from urllib.parse import quote + import httpx from SYS.logger import debug, log @@ -1099,6 +1103,94 @@ class HydrusNetwork(Store): debug(f"{self._log_prefix()} get_file: url={browser_url}") return browser_url + def download_to_temp( + self, + file_hash: str, + *, + temp_root: Optional[Path] = None, + ) -> Optional[Path]: + """Download a Hydrus file to a temporary path for downstream uploads.""" + + try: + client = self._client + if client is None: + return None + + h = str(file_hash or "").strip().lower() + if len(h) != 64 or not all(ch in "0123456789abcdef" for ch in h): + return None + + created_tmp = False + base_tmp = Path(temp_root) if temp_root is not None else Path( + tempfile.mkdtemp(prefix="hydrus-file-") + ) + if temp_root is None: + created_tmp = True + base_tmp.mkdir(parents=True, exist_ok=True) + + def _safe_filename(raw: str) -> str: + cleaned = re.sub(r"[\\/:*?\"<>|]", "_", str(raw or "")).strip() + if not cleaned: + return h + cleaned = cleaned.strip(". ") or h + return cleaned + + # Prefer ext/title from metadata when available. + fname = h + ext_val = "" + try: + meta = self.get_metadata(h) or {} + if isinstance(meta, dict): + title_val = str(meta.get("title") or "").strip() + if title_val: + fname = _safe_filename(title_val) + ext_val = str(meta.get("ext") or "").strip().lstrip(".") + except Exception: + pass + + if not fname: + fname = h + if ext_val and not fname.lower().endswith(f".{ext_val.lower()}"): + fname = f"{fname}.{ext_val}" + + try: + file_url = client.file_url(h) + except Exception: + file_url = f"{self.URL.rstrip('/')}/get_files/file?hash={quote(h)}" + + dest_path = base_tmp / fname + with httpx.stream( + "GET", + file_url, + headers={"Hydrus-Client-API-Access-Key": self.API}, + follow_redirects=True, + timeout=60.0, + verify=False, + ) as resp: + resp.raise_for_status() + with dest_path.open("wb") as fh: + for chunk in resp.iter_bytes(): + if chunk: + fh.write(chunk) + + if dest_path.exists(): + return dest_path + + if created_tmp: + try: + shutil.rmtree(base_tmp, ignore_errors=True) + except Exception: + pass + return None + except Exception as exc: + log(f"{self._log_prefix()} download_to_temp failed: {exc}", file=sys.stderr) + try: + if temp_root is None and "base_tmp" in locals(): + shutil.rmtree(base_tmp, ignore_errors=True) # type: ignore[arg-type] + except Exception: + pass + return None + def delete_file(self, file_identifier: str, **kwargs: Any) -> bool: """Delete a file from Hydrus, then clear the deletion record. diff --git a/cmdlet/add_file.py b/cmdlet/add_file.py index 4cbe781..5d55256 100644 --- a/cmdlet/add_file.py +++ b/cmdlet/add_file.py @@ -5,6 +5,7 @@ from pathlib import Path from copy import deepcopy import sys import shutil +import tempfile import re from SYS import models @@ -501,7 +502,7 @@ class Add_File(Cmdlet): temp_dir_to_cleanup: Optional[Path] = None delete_after_item = delete_after try: - media_path, file_hash = self._resolve_source( + media_path, file_hash, temp_dir_to_cleanup = self._resolve_source( item, path_arg, pipe_obj, config ) debug( @@ -901,6 +902,38 @@ class Add_File(Cmdlet): except Exception: continue + @staticmethod + def _maybe_download_backend_file( + backend: Any, + file_hash: str, + pipe_obj: models.PipeObject, + ) -> Tuple[Optional[Path], Optional[Path]]: + """Best-effort fetch of a backend file when get_file returns a URL. + + Returns (downloaded_path, temp_dir_to_cleanup). + """ + + downloader = getattr(backend, "download_to_temp", None) + if not callable(downloader): + return None, None + + tmp_dir: Optional[Path] = None + try: + tmp_dir = Path(tempfile.mkdtemp(prefix="add-file-src-")) + downloaded = downloader(str(file_hash), temp_root=tmp_dir) + if isinstance(downloaded, Path) and downloaded.exists(): + pipe_obj.is_temp = True + return downloaded, tmp_dir + except Exception: + pass + + if tmp_dir is not None: + try: + shutil.rmtree(tmp_dir, ignore_errors=True) + except Exception: + pass + return None, None + @staticmethod def _resolve_source( result: Any, @@ -909,10 +942,11 @@ class Add_File(Cmdlet): config: Dict[str, Any], ) -> Tuple[Optional[Path], - Optional[str]]: + Optional[str], + Optional[Path]]: """Resolve the source file path from args or pipeline result. - Returns (media_path, file_hash). + Returns (media_path, file_hash, temp_dir_to_cleanup). """ # PRIORITY 1a: Try hash+path from directory scan result (has 'path' and 'hash' keys) if isinstance(result, dict): @@ -931,7 +965,7 @@ class Add_File(Cmdlet): f"[add-file] Using path+hash from directory scan: {media_path}" ) pipe_obj.path = str(media_path) - return media_path, str(result_hash) + return media_path, str(result_hash), None except Exception as exc: debug(f"[add-file] Failed to use directory scan result: {exc}") @@ -950,7 +984,17 @@ class Add_File(Cmdlet): media_path = backend.get_file(result_hash) if isinstance(media_path, Path) and media_path.exists(): pipe_obj.path = str(media_path) - return media_path, str(result_hash) + return media_path, str(result_hash), None + + if isinstance(media_path, str) and media_path.strip(): + downloaded, tmp_dir = Add_File._maybe_download_backend_file( + backend, + str(result_hash), + pipe_obj, + ) + if isinstance(downloaded, Path) and downloaded.exists(): + pipe_obj.path = str(downloaded) + return downloaded, str(result_hash), tmp_dir except Exception as exc: debug(f"[add-file] Failed to retrieve via hash+store: {exc}") @@ -959,7 +1003,7 @@ class Add_File(Cmdlet): media_path = Path(path_arg) pipe_obj.path = str(media_path) debug(f"[add-file] Using explicit path argument: {media_path}") - return media_path, None + return media_path, None, None # PRIORITY 3: Try from pipe_obj.path (check file first before URL) pipe_path = getattr(pipe_obj, "path", None) @@ -976,8 +1020,8 @@ class Add_File(Cmdlet): "add-file ingests local files only. Use download-file first.", file=sys.stderr, ) - return None, None - return Path(pipe_path_str), None + return None, None, None + return Path(pipe_path_str), None, None # Try from result (if it's a string path or URL) if isinstance(result, str): @@ -993,10 +1037,10 @@ class Add_File(Cmdlet): "add-file ingests local files only. Use download-file first.", file=sys.stderr, ) - return None, None + return None, None, None media_path = Path(result) pipe_obj.path = str(media_path) - return media_path, None + return media_path, None, None # Try from result if it's a list (pipeline emits multiple results) if isinstance(result, list) and result: @@ -1014,10 +1058,10 @@ class Add_File(Cmdlet): "add-file ingests local files only. Use download-file first.", file=sys.stderr, ) - return None, None + return None, None, None media_path = Path(first_item) pipe_obj.path = str(media_path) - return media_path, None + return media_path, None, None # If the first item is a dict, interpret it as a PipeObject-style result if isinstance(first_item, dict): @@ -1037,9 +1081,9 @@ class Add_File(Cmdlet): try: media_path = Path(path_candidate) pipe_obj.path = str(media_path) - return media_path, first_item.get("hash") + return media_path, first_item.get("hash"), None except Exception: - return None, first_item.get("hash") + return None, first_item.get("hash"), None # If first item is a PipeObject object try: @@ -1052,7 +1096,7 @@ class Add_File(Cmdlet): debug(f"Resolved path from PipeObject: {path_candidate}") media_path = Path(path_candidate) pipe_obj.path = str(media_path) - return media_path, getattr(first_item, "hash", None) + return media_path, getattr(first_item, "hash", None), None except Exception: pass @@ -1060,7 +1104,7 @@ class Add_File(Cmdlet): f"No resolution path matched. pipe_obj.path={pipe_path}, result type={type(result).__name__}" ) log("File path could not be resolved") - return None, None + return None, None, None @staticmethod def _scan_directory_for_files(directory: Path) -> List[Dict[str, Any]]: @@ -1778,6 +1822,12 @@ class Add_File(Cmdlet): store = Store(config) backend = store[backend_name] + hydrus_like_backend = False + try: + hydrus_like_backend = str(type(backend).__name__ or "").lower().startswith("hydrus") + except Exception: + hydrus_like_backend = False + # Prepare metadata from pipe_obj and sidecars tags, url, title, f_hash = Add_File._prepare_metadata( result, media_path, pipe_obj, config @@ -1870,6 +1920,11 @@ class Add_File(Cmdlet): log(f"[add-file] FlorenceVision tagging error: {exc}", file=sys.stderr) return 1 + upload_tags = tags + if hydrus_like_backend and upload_tags: + upload_tags = [] + debug("[add-file] Deferring tag application until after Hydrus upload") + debug( f"[add-file] Storing into backend '{backend_name}' path='{media_path}' title='{title}'" ) @@ -1879,7 +1934,7 @@ class Add_File(Cmdlet): file_identifier = backend.add_file( media_path, title=title, - tag=tags, + tag=upload_tags, url=[] if (defer_url_association and url) else url, ) debug( @@ -1921,6 +1976,17 @@ class Add_File(Cmdlet): (f_hash or file_identifier or "unknown") ) + if hydrus_like_backend and tags: + try: + adder = getattr(backend, "add_tag", None) + if callable(adder): + debug( + f"[add-file] Applying {len(tags)} tag(s) post-upload to Hydrus" + ) + adder(resolved_hash, list(tags)) + except Exception as exc: + log(f"[add-file] Hydrus post-upload tagging failed: {exc}", file=sys.stderr) + # If we have url(s), ensure they get associated with the destination file. # This mirrors `add-url` behavior but avoids emitting extra pipeline noise. if url: