Upload 5 files
Browse files- comet/main.py +469 -0
- comet/templates/index.html +563 -0
- comet/utils/__init__.py +0 -0
- comet/utils/general.py +36 -0
- comet/utils/logger.py +26 -0
comet/main.py
ADDED
|
@@ -0,0 +1,469 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import aiohttp, asyncio, bencodepy, hashlib, re, base64, json, os, RTN, time
|
| 2 |
+
|
| 3 |
+
from fastapi import FastAPI, Request
|
| 4 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 5 |
+
from fastapi.responses import RedirectResponse
|
| 6 |
+
from fastapi.templating import Jinja2Templates
|
| 7 |
+
from fastapi.staticfiles import StaticFiles
|
| 8 |
+
from contextlib import asynccontextmanager
|
| 9 |
+
from databases import Database
|
| 10 |
+
|
| 11 |
+
from .utils.logger import logger
|
| 12 |
+
from .utils.general import translate, isVideo, bytesToSize
|
| 13 |
+
|
| 14 |
+
database = Database(f"sqlite:///{os.getenv('DATABASE_PATH', 'database.db')}")
|
| 15 |
+
|
| 16 |
+
class BestOverallRanking(RTN.BaseRankingModel):
|
| 17 |
+
uhd: int = 100
|
| 18 |
+
fhd: int = 90
|
| 19 |
+
hd: int = 80
|
| 20 |
+
sd: int = 70
|
| 21 |
+
dolby_video: int = 100
|
| 22 |
+
hdr: int = 80
|
| 23 |
+
hdr10: int = 90
|
| 24 |
+
dts_x: int = 100
|
| 25 |
+
dts_hd: int = 80
|
| 26 |
+
dts_hd_ma: int = 90
|
| 27 |
+
atmos: int = 90
|
| 28 |
+
truehd: int = 60
|
| 29 |
+
ddplus: int = 40
|
| 30 |
+
aac: int = 30
|
| 31 |
+
ac3: int = 20
|
| 32 |
+
remux: int = 150
|
| 33 |
+
bluray: int = 120
|
| 34 |
+
webdl: int = 90
|
| 35 |
+
|
| 36 |
+
settings = RTN.SettingsModel()
|
| 37 |
+
ranking_model = BestOverallRanking()
|
| 38 |
+
rtn = RTN.RTN(settings=settings, ranking_model=ranking_model)
|
| 39 |
+
|
| 40 |
+
infoHashPattern = re.compile(r"\b([a-fA-F0-9]{40})\b")
|
| 41 |
+
|
| 42 |
+
@asynccontextmanager
|
| 43 |
+
async def lifespan(app: FastAPI):
|
| 44 |
+
await database.connect()
|
| 45 |
+
await database.execute("CREATE TABLE IF NOT EXISTS cache (cacheKey BLOB PRIMARY KEY, timestamp INTEGER, results TEXT)")
|
| 46 |
+
yield
|
| 47 |
+
await database.disconnect()
|
| 48 |
+
|
| 49 |
+
app = FastAPI(lifespan=lifespan, docs_url=None)
|
| 50 |
+
|
| 51 |
+
app.add_middleware(
|
| 52 |
+
CORSMiddleware,
|
| 53 |
+
allow_origins=["*"],
|
| 54 |
+
allow_credentials=True,
|
| 55 |
+
allow_methods=["*"],
|
| 56 |
+
allow_headers=["*"],
|
| 57 |
+
)
|
| 58 |
+
|
| 59 |
+
templates = Jinja2Templates("comet/templates")
|
| 60 |
+
app.mount("/static", StaticFiles(directory="comet/templates"), name="static")
|
| 61 |
+
|
| 62 |
+
@app.get("/")
|
| 63 |
+
async def root():
|
| 64 |
+
return RedirectResponse("/configure")
|
| 65 |
+
|
| 66 |
+
indexers = os.getenv("INDEXER_MANAGER_INDEXERS")
|
| 67 |
+
if "," in indexers:
|
| 68 |
+
indexers = indexers.split(",")
|
| 69 |
+
else:
|
| 70 |
+
indexers = [indexers]
|
| 71 |
+
|
| 72 |
+
webConfig = {
|
| 73 |
+
"indexers": indexers,
|
| 74 |
+
"languages": [indexer.replace(" ", "_") for indexer in RTN.patterns.language_code_mapping.keys()],
|
| 75 |
+
"resolutions": ["480p", "720p", "1080p", "1440p", "2160p", "2880p", "4320p"]
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
@app.get("/configure")
|
| 79 |
+
@app.get("/{b64config}/configure")
|
| 80 |
+
async def configure(request: Request):
|
| 81 |
+
return templates.TemplateResponse("index.html", {"request": request, "CUSTOM_HEADER_HTML": os.getenv("CUSTOM_HEADER_HTML", ""), "webConfig": webConfig})
|
| 82 |
+
|
| 83 |
+
def configChecking(b64config: str):
|
| 84 |
+
try:
|
| 85 |
+
config = json.loads(base64.b64decode(b64config).decode())
|
| 86 |
+
|
| 87 |
+
if not isinstance(config["debridService"], str) or config["debridService"] not in ["realdebrid"]:
|
| 88 |
+
return False
|
| 89 |
+
|
| 90 |
+
if not isinstance(config["debridApiKey"], str):
|
| 91 |
+
return False
|
| 92 |
+
|
| 93 |
+
if not isinstance(config["indexers"], list):
|
| 94 |
+
return False
|
| 95 |
+
|
| 96 |
+
if not isinstance(config["maxResults"], int) or config["maxResults"] < 0:
|
| 97 |
+
return False
|
| 98 |
+
|
| 99 |
+
if not isinstance(config["resolutions"], list) or len(config["resolutions"]) == 0:
|
| 100 |
+
return False
|
| 101 |
+
|
| 102 |
+
if not isinstance(config["languages"], list) or len(config["languages"]) == 0:
|
| 103 |
+
return False
|
| 104 |
+
|
| 105 |
+
return config
|
| 106 |
+
except:
|
| 107 |
+
return False
|
| 108 |
+
|
| 109 |
+
@app.get("/manifest.json")
|
| 110 |
+
@app.get("/{b64config}/manifest.json")
|
| 111 |
+
async def manifest():
|
| 112 |
+
return {
|
| 113 |
+
"id": "stremio.comet.fast",
|
| 114 |
+
"version": "1.0.0",
|
| 115 |
+
"name": "Comet",
|
| 116 |
+
"description": "Stremio's fastest torrent/debrid search add-on.",
|
| 117 |
+
"logo": "https://i.imgur.com/jmVoVMu.jpeg",
|
| 118 |
+
"background": "https://i.imgur.com/WwnXB3k.jpeg",
|
| 119 |
+
"resources": [
|
| 120 |
+
"stream"
|
| 121 |
+
],
|
| 122 |
+
"types": [
|
| 123 |
+
"movie",
|
| 124 |
+
"series"
|
| 125 |
+
],
|
| 126 |
+
"idPrefixes": [
|
| 127 |
+
"tt"
|
| 128 |
+
],
|
| 129 |
+
"catalogs": [],
|
| 130 |
+
"behaviorHints": {
|
| 131 |
+
"configurable": True
|
| 132 |
+
}
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
+
async def getIndexerManager(session: aiohttp.ClientSession, indexerManagerType: str, indexers: list, query: str):
|
| 136 |
+
try:
|
| 137 |
+
timeout = aiohttp.ClientTimeout(total=int(os.getenv("INDEXER_MANAGER_TIMEOUT", 30)))
|
| 138 |
+
results = []
|
| 139 |
+
|
| 140 |
+
if indexerManagerType == "jackett":
|
| 141 |
+
response = await session.get(f"{os.getenv('INDEXER_MANAGER_URL', 'http://127.0.0.1:9117')}/api/v2.0/indexers/all/results?apikey={os.getenv('INDEXER_MANAGER_API_KEY')}&Query={query}&Tracker[]={'&Tracker[]='.join(indexer for indexer in indexers)}", timeout=timeout)
|
| 142 |
+
response = await response.json()
|
| 143 |
+
|
| 144 |
+
for result in response["Results"]:
|
| 145 |
+
results.append(result)
|
| 146 |
+
|
| 147 |
+
if indexerManagerType == "prowlarr":
|
| 148 |
+
getIndexers = await session.get(f"{os.getenv('INDEXER_MANAGER_URL', 'http://127.0.0.1:9696')}/api/v1/indexer", headers={
|
| 149 |
+
"X-Api-Key": os.getenv("INDEXER_MANAGER_API_KEY")
|
| 150 |
+
})
|
| 151 |
+
getIndexers = await getIndexers.json()
|
| 152 |
+
|
| 153 |
+
indexersId = []
|
| 154 |
+
for indexer in getIndexers:
|
| 155 |
+
if indexer["definitionName"] in indexers:
|
| 156 |
+
indexersId.append(indexer["id"])
|
| 157 |
+
|
| 158 |
+
response = await session.get(f"{os.getenv('INDEXER_MANAGER_URL', 'http://127.0.0.1:9696')}/api/v1/search?query={query}&indexerIds={'&indexerIds='.join(str(indexerId) for indexerId in indexersId)}&type=search", headers={
|
| 159 |
+
"X-Api-Key": os.getenv("INDEXER_MANAGER_API_KEY")
|
| 160 |
+
})
|
| 161 |
+
response = await response.json()
|
| 162 |
+
|
| 163 |
+
for result in response:
|
| 164 |
+
results.append(result)
|
| 165 |
+
|
| 166 |
+
return results
|
| 167 |
+
except Exception as e:
|
| 168 |
+
logger.warning(f"Exception while getting {indexerManagerType} results for {query} with {indexers}: {e}")
|
| 169 |
+
|
| 170 |
+
async def getTorrentHash(session: aiohttp.ClientSession, indexerManagerType: str, torrent: dict):
|
| 171 |
+
if "InfoHash" in torrent and torrent["InfoHash"] != None:
|
| 172 |
+
return torrent["InfoHash"]
|
| 173 |
+
|
| 174 |
+
if "infoHash" in torrent:
|
| 175 |
+
return torrent["infoHash"]
|
| 176 |
+
|
| 177 |
+
url = torrent["Link"] if indexerManagerType == "jackett" else torrent["downloadUrl"]
|
| 178 |
+
|
| 179 |
+
try:
|
| 180 |
+
timeout = aiohttp.ClientTimeout(total=int(os.getenv("GET_TORRENT_TIMEOUT", 5)))
|
| 181 |
+
response = await session.get(url, allow_redirects=False, timeout=timeout)
|
| 182 |
+
if response.status == 200:
|
| 183 |
+
torrentData = await response.read()
|
| 184 |
+
torrentDict = bencodepy.decode(torrentData)
|
| 185 |
+
info = bencodepy.encode(torrentDict[b"info"])
|
| 186 |
+
hash = hashlib.sha1(info).hexdigest()
|
| 187 |
+
else:
|
| 188 |
+
location = response.headers.get("Location", "")
|
| 189 |
+
if not location:
|
| 190 |
+
return
|
| 191 |
+
|
| 192 |
+
match = infoHashPattern.search(location)
|
| 193 |
+
if not match:
|
| 194 |
+
return
|
| 195 |
+
|
| 196 |
+
hash = match.group(1).upper()
|
| 197 |
+
|
| 198 |
+
return hash
|
| 199 |
+
except Exception as e:
|
| 200 |
+
logger.warning(f"Exception while getting torrent info hash for {torrent['indexer'] if 'indexer' in torrent else (torrent['Tracker'] if 'Tracker' in torrent else '')}|{url}: {e}")
|
| 201 |
+
# logger.warning(f"Exception while getting torrent info hash for {jackettIndexerPattern.findall(url)[0]}|{jackettNamePattern.search(url)[0]}: {e}")
|
| 202 |
+
|
| 203 |
+
@app.get("/stream/{type}/{id}.json")
|
| 204 |
+
@app.get("/{b64config}/stream/{type}/{id}.json")
|
| 205 |
+
async def stream(request: Request, b64config: str, type: str, id: str):
|
| 206 |
+
config = configChecking(b64config)
|
| 207 |
+
if not config:
|
| 208 |
+
return {
|
| 209 |
+
"streams": [
|
| 210 |
+
{
|
| 211 |
+
"name": "[⚠️] Comet",
|
| 212 |
+
"title": "Invalid Comet config.",
|
| 213 |
+
"url": "https://comet.fast"
|
| 214 |
+
}
|
| 215 |
+
]
|
| 216 |
+
}
|
| 217 |
+
|
| 218 |
+
async with aiohttp.ClientSession() as session:
|
| 219 |
+
checkDebrid = await session.get("https://api.real-debrid.com/rest/1.0/user", headers={
|
| 220 |
+
"Authorization": f"Bearer {config['debridApiKey']}"
|
| 221 |
+
})
|
| 222 |
+
checkDebrid = await checkDebrid.text()
|
| 223 |
+
if not '"type": "premium"' in checkDebrid:
|
| 224 |
+
return {
|
| 225 |
+
"streams": [
|
| 226 |
+
{
|
| 227 |
+
"name": "[⚠️] Comet",
|
| 228 |
+
"title": "Invalid Real-Debrid account.",
|
| 229 |
+
"url": "https://comet.fast"
|
| 230 |
+
}
|
| 231 |
+
]
|
| 232 |
+
}
|
| 233 |
+
|
| 234 |
+
season = None
|
| 235 |
+
episode = None
|
| 236 |
+
if type == "series":
|
| 237 |
+
info = id.split(":")
|
| 238 |
+
|
| 239 |
+
id = info[0]
|
| 240 |
+
season = int(info[1])
|
| 241 |
+
episode = int(info[2])
|
| 242 |
+
|
| 243 |
+
getMetadata = await session.get(f"https://v3.sg.media-imdb.com/suggestion/a/{id}.json")
|
| 244 |
+
metadata = await getMetadata.json()
|
| 245 |
+
|
| 246 |
+
name = metadata["d"][0]["l"]
|
| 247 |
+
name = translate(name)
|
| 248 |
+
|
| 249 |
+
cacheKey = hashlib.md5(json.dumps({"debridService": config["debridService"], "name": name, "season": season, "episode": episode, "indexers": config["indexers"], "resolutions": config["resolutions"], "languages": config["languages"]}).encode("utf-8")).hexdigest()
|
| 250 |
+
cached = await database.fetch_one(f"SELECT EXISTS (SELECT 1 FROM cache WHERE cacheKey = '{cacheKey}')")
|
| 251 |
+
if cached[0] != 0:
|
| 252 |
+
logger.info(f"Cache found for {name}")
|
| 253 |
+
|
| 254 |
+
timestamp = await database.fetch_one(f"SELECT timestamp FROM cache WHERE cacheKey = '{cacheKey}'")
|
| 255 |
+
if timestamp[0] + int(os.getenv("CACHE_TTL", 86400)) < time.time():
|
| 256 |
+
await database.execute(f"DELETE FROM cache WHERE cacheKey = '{cacheKey}'")
|
| 257 |
+
|
| 258 |
+
logger.info(f"Cache expired for {name}")
|
| 259 |
+
else:
|
| 260 |
+
sortedRankedFiles = await database.fetch_one(f"SELECT results FROM cache WHERE cacheKey = '{cacheKey}'")
|
| 261 |
+
sortedRankedFiles = json.loads(sortedRankedFiles[0])
|
| 262 |
+
|
| 263 |
+
results = []
|
| 264 |
+
for hash in sortedRankedFiles:
|
| 265 |
+
results.append({
|
| 266 |
+
"name": f"[RD⚡] Comet {sortedRankedFiles[hash]['data']['resolution'][0] if len(sortedRankedFiles[hash]['data']['resolution']) > 0 else 'Unknown'}",
|
| 267 |
+
"title": f"{sortedRankedFiles[hash]['data']['title']}\n💾 {bytesToSize(sortedRankedFiles[hash]['data']['size'])}",
|
| 268 |
+
"url": f"{request.url.scheme}://{request.url.netloc}/{b64config}/playback/{hash}/{sortedRankedFiles[hash]['data']['index']}"
|
| 269 |
+
})
|
| 270 |
+
|
| 271 |
+
return {"streams": results}
|
| 272 |
+
else:
|
| 273 |
+
logger.info(f"No cache found for {name} with user configuration")
|
| 274 |
+
|
| 275 |
+
indexerManagerType = os.getenv("INDEXER_MANAGER_TYPE", "jackett")
|
| 276 |
+
|
| 277 |
+
logger.info(f"Start of {indexerManagerType} search for {name} with indexers {config['indexers']}")
|
| 278 |
+
|
| 279 |
+
tasks = []
|
| 280 |
+
tasks.append(getIndexerManager(session, indexerManagerType, config["indexers"], name))
|
| 281 |
+
if type == "series":
|
| 282 |
+
tasks.append(getIndexerManager(session, indexerManagerType, config["indexers"], f"{name} S0{season}E0{episode}"))
|
| 283 |
+
searchResponses = await asyncio.gather(*tasks)
|
| 284 |
+
|
| 285 |
+
torrents = []
|
| 286 |
+
for results in searchResponses:
|
| 287 |
+
if results == None:
|
| 288 |
+
continue
|
| 289 |
+
|
| 290 |
+
for result in results:
|
| 291 |
+
torrents.append(result)
|
| 292 |
+
|
| 293 |
+
logger.info(f"{len(torrents)} torrents found for {name}")
|
| 294 |
+
|
| 295 |
+
if len(torrents) == 0:
|
| 296 |
+
return {"streams": []}
|
| 297 |
+
|
| 298 |
+
tasks = []
|
| 299 |
+
filtered = 0
|
| 300 |
+
for torrent in torrents:
|
| 301 |
+
parsedTorrent = RTN.parse(torrent["Title"]) if indexerManagerType == "jackett" else RTN.parse(torrent["title"])
|
| 302 |
+
if not "All" in config["resolutions"] and len(parsedTorrent.resolution) > 0 and parsedTorrent.resolution[0] not in config["resolutions"]:
|
| 303 |
+
filtered += 1
|
| 304 |
+
|
| 305 |
+
continue
|
| 306 |
+
if not "All" in config["languages"] and not parsedTorrent.is_multi_audio and not any(language in parsedTorrent.language for language in config["languages"]):
|
| 307 |
+
filtered += 1
|
| 308 |
+
|
| 309 |
+
continue
|
| 310 |
+
|
| 311 |
+
tasks.append(getTorrentHash(session, indexerManagerType, torrent))
|
| 312 |
+
|
| 313 |
+
torrentHashes = await asyncio.gather(*tasks)
|
| 314 |
+
torrentHashes = list(set([hash for hash in torrentHashes if hash]))
|
| 315 |
+
|
| 316 |
+
logger.info(f"{len(torrentHashes)} info hashes found for {name}")
|
| 317 |
+
|
| 318 |
+
if len(torrentHashes) == 0:
|
| 319 |
+
return {"streams": []}
|
| 320 |
+
|
| 321 |
+
getAvailability = await session.get(f"https://api.real-debrid.com/rest/1.0/torrents/instantAvailability/{'/'.join(torrentHashes)}", headers={
|
| 322 |
+
"Authorization": f"Bearer {config['debridApiKey']}"
|
| 323 |
+
})
|
| 324 |
+
|
| 325 |
+
files = {}
|
| 326 |
+
|
| 327 |
+
availability = await getAvailability.json()
|
| 328 |
+
for hash, details in availability.items():
|
| 329 |
+
if not "rd" in details:
|
| 330 |
+
continue
|
| 331 |
+
|
| 332 |
+
if type == "series":
|
| 333 |
+
for variants in details["rd"]:
|
| 334 |
+
for index, file in variants.items():
|
| 335 |
+
filename = file["filename"].lower()
|
| 336 |
+
|
| 337 |
+
if not isVideo(filename):
|
| 338 |
+
continue
|
| 339 |
+
|
| 340 |
+
filenameParsed = RTN.parse(file["filename"])
|
| 341 |
+
if season in filenameParsed.season and episode in filenameParsed.episode:
|
| 342 |
+
files[hash] = {
|
| 343 |
+
"index": index,
|
| 344 |
+
"title": file["filename"],
|
| 345 |
+
"size": file["filesize"]
|
| 346 |
+
}
|
| 347 |
+
|
| 348 |
+
continue
|
| 349 |
+
|
| 350 |
+
for variants in details["rd"]:
|
| 351 |
+
for index, file in variants.items():
|
| 352 |
+
filename = file["filename"].lower()
|
| 353 |
+
|
| 354 |
+
if not isVideo(filename):
|
| 355 |
+
continue
|
| 356 |
+
|
| 357 |
+
files[hash] = {
|
| 358 |
+
"index": index,
|
| 359 |
+
"title": file["filename"],
|
| 360 |
+
"size": file["filesize"]
|
| 361 |
+
}
|
| 362 |
+
|
| 363 |
+
rankedFiles = set()
|
| 364 |
+
for hash in files:
|
| 365 |
+
# try:
|
| 366 |
+
rankedFile = rtn.rank(files[hash]["title"], hash) # , remove_trash=True, correct_title=name - removed because it's not working great
|
| 367 |
+
rankedFiles.add(rankedFile)
|
| 368 |
+
# except:
|
| 369 |
+
# continue
|
| 370 |
+
|
| 371 |
+
sortedRankedFiles = RTN.sort_torrents(rankedFiles)
|
| 372 |
+
|
| 373 |
+
logger.info(f"{len(sortedRankedFiles)} cached files found on Real-Debrid for {name}")
|
| 374 |
+
|
| 375 |
+
if len(sortedRankedFiles) == 0:
|
| 376 |
+
return {"streams": []}
|
| 377 |
+
|
| 378 |
+
sortedRankedFiles = {
|
| 379 |
+
key: (value.model_dump() if isinstance(value, RTN.Torrent) else value)
|
| 380 |
+
for key, value in sortedRankedFiles.items()
|
| 381 |
+
}
|
| 382 |
+
for hash in sortedRankedFiles: # needed for caching
|
| 383 |
+
sortedRankedFiles[hash]["data"]["title"] = files[hash]["title"]
|
| 384 |
+
sortedRankedFiles[hash]["data"]["size"] = files[hash]["size"]
|
| 385 |
+
sortedRankedFiles[hash]["data"]["index"] = files[hash]["index"]
|
| 386 |
+
|
| 387 |
+
jsonData = json.dumps(sortedRankedFiles).replace("'", "''")
|
| 388 |
+
await database.execute(f"INSERT INTO cache (cacheKey, results, timestamp) VALUES ('{cacheKey}', '{jsonData}', {time.time()})")
|
| 389 |
+
logger.info(f"Results have been cached for {name}")
|
| 390 |
+
|
| 391 |
+
results = []
|
| 392 |
+
for hash in sortedRankedFiles:
|
| 393 |
+
results.append({
|
| 394 |
+
"name": f"[RD⚡] Comet {sortedRankedFiles[hash]['data']['resolution'][0] if len(sortedRankedFiles[hash]['data']['resolution']) > 0 else 'Unknown'}",
|
| 395 |
+
"title": f"{sortedRankedFiles[hash]['data']['title']}\n💾 {bytesToSize(sortedRankedFiles[hash]['data']['size'])}",
|
| 396 |
+
"url": f"{request.url.scheme}://{request.url.netloc}/{b64config}/playback/{hash}/{sortedRankedFiles[hash]['data']['index']}"
|
| 397 |
+
})
|
| 398 |
+
|
| 399 |
+
return {
|
| 400 |
+
"streams": results
|
| 401 |
+
}
|
| 402 |
+
|
| 403 |
+
async def generateDownloadLink(debridApiKey: str, hash: str, index: str):
|
| 404 |
+
try:
|
| 405 |
+
async with aiohttp.ClientSession() as session:
|
| 406 |
+
checkBlacklisted = await session.get("https://real-debrid.com/vpn")
|
| 407 |
+
checkBlacklisted = await checkBlacklisted.text()
|
| 408 |
+
|
| 409 |
+
proxy = None
|
| 410 |
+
if "Your ISP or VPN provider IP address is currently blocked on our website" in checkBlacklisted:
|
| 411 |
+
proxy = os.getenv("DEBRID_PROXY_URL", "http://127.0.0.1:1080")
|
| 412 |
+
|
| 413 |
+
logger.warning(f"Real-Debrid blacklisted server's IP. Switching to proxy {proxy} for {hash}|{index}")
|
| 414 |
+
|
| 415 |
+
addMagnet = await session.post(f"https://api.real-debrid.com/rest/1.0/torrents/addMagnet", headers={
|
| 416 |
+
"Authorization": f"Bearer {debridApiKey}"
|
| 417 |
+
}, data={
|
| 418 |
+
"magnet": f"magnet:?xt=urn:btih:{hash}"
|
| 419 |
+
}, proxy=proxy)
|
| 420 |
+
addMagnet = await addMagnet.json()
|
| 421 |
+
|
| 422 |
+
getMagnetInfo = await session.get(addMagnet["uri"], headers={
|
| 423 |
+
"Authorization": f"Bearer {debridApiKey}"
|
| 424 |
+
}, proxy=proxy)
|
| 425 |
+
getMagnetInfo = await getMagnetInfo.json()
|
| 426 |
+
|
| 427 |
+
selectFile = await session.post(f"https://api.real-debrid.com/rest/1.0/torrents/selectFiles/{addMagnet['id']}", headers={
|
| 428 |
+
"Authorization": f"Bearer {debridApiKey}"
|
| 429 |
+
}, data={
|
| 430 |
+
"files": index
|
| 431 |
+
}, proxy=proxy)
|
| 432 |
+
|
| 433 |
+
getMagnetInfo = await session.get(addMagnet["uri"], headers={
|
| 434 |
+
"Authorization": f"Bearer {debridApiKey}"
|
| 435 |
+
}, proxy=proxy)
|
| 436 |
+
getMagnetInfo = await getMagnetInfo.json()
|
| 437 |
+
|
| 438 |
+
unrestrictLink = await session.post(f"https://api.real-debrid.com/rest/1.0/unrestrict/link", headers={
|
| 439 |
+
"Authorization": f"Bearer {debridApiKey}"
|
| 440 |
+
}, data={
|
| 441 |
+
"link": getMagnetInfo["links"][0]
|
| 442 |
+
}, proxy=proxy)
|
| 443 |
+
unrestrictLink = await unrestrictLink.json()
|
| 444 |
+
|
| 445 |
+
return unrestrictLink["download"]
|
| 446 |
+
except Exception as e:
|
| 447 |
+
logger.warning(f"Exception while getting download link from Real Debrid for {hash}|{index}: {e}")
|
| 448 |
+
|
| 449 |
+
return "https://comet.fast"
|
| 450 |
+
|
| 451 |
+
@app.head("/{b64config}/playback/{hash}/{index}")
|
| 452 |
+
async def stream(b64config: str, hash: str, index: str):
|
| 453 |
+
config = configChecking(b64config)
|
| 454 |
+
if not config:
|
| 455 |
+
return
|
| 456 |
+
|
| 457 |
+
downloadLink = await generateDownloadLink(config["debridApiKey"], hash, index)
|
| 458 |
+
|
| 459 |
+
return RedirectResponse(downloadLink, status_code=302)
|
| 460 |
+
|
| 461 |
+
@app.get("/{b64config}/playback/{hash}/{index}")
|
| 462 |
+
async def stream(b64config: str, hash: str, index: str):
|
| 463 |
+
config = configChecking(b64config)
|
| 464 |
+
if not config:
|
| 465 |
+
return
|
| 466 |
+
|
| 467 |
+
downloadLink = await generateDownloadLink(config["debridApiKey"], hash, index)
|
| 468 |
+
|
| 469 |
+
return RedirectResponse(downloadLink, status_code=302)
|
comet/templates/index.html
ADDED
|
@@ -0,0 +1,563 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html class="sl-theme-dark">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
| 6 |
+
<meta content="Comet" property="og:title" />
|
| 7 |
+
<meta content="Stremio's fastest torrent/debrid search add-on." property="og:description" />
|
| 8 |
+
<meta content="https://comet.fast" property="og:url" />
|
| 9 |
+
<meta content="https://i.imgur.com/jmVoVMu.jpeg" property="og:image" />
|
| 10 |
+
<meta content="#6b6ef8" data-react-helmet="true" name="theme-color" />
|
| 11 |
+
|
| 12 |
+
<title>Comet - Stremio's fastest torrent/debrid search add-on.</title>
|
| 13 |
+
<link id="favicon" rel="icon" type="image/x-icon" href="https://i.imgur.com/jmVoVMu.jpeg">
|
| 14 |
+
|
| 15 |
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.15.1/cdn/themes/dark.css" />
|
| 16 |
+
<script type="module" src="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.15.1/cdn/shoelace-autoloader.js"></script>
|
| 17 |
+
|
| 18 |
+
<style>
|
| 19 |
+
:not(:defined) {
|
| 20 |
+
visibility: hidden;
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
body {
|
| 24 |
+
display: flex;
|
| 25 |
+
flex-direction: column;
|
| 26 |
+
justify-content: center;
|
| 27 |
+
align-items: center;
|
| 28 |
+
min-height: 100vh;
|
| 29 |
+
margin: 0;
|
| 30 |
+
background: radial-gradient(ellipse at bottom, #25292c 0%, #0c0d13 100%);
|
| 31 |
+
font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
| 32 |
+
font-size: 1rem;
|
| 33 |
+
font-weight: 400;
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
::-webkit-scrollbar {
|
| 37 |
+
overflow: hidden;
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
.header {
|
| 41 |
+
text-align: center;
|
| 42 |
+
width: 40%;
|
| 43 |
+
margin-bottom: 20px;
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
.comet-text {
|
| 47 |
+
font-size: calc(1.375rem + 1.5vw);
|
| 48 |
+
font-weight: 500;
|
| 49 |
+
margin-bottom: 0;
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
.form-container {
|
| 53 |
+
background-color: #1a1d20;
|
| 54 |
+
padding: 2rem;
|
| 55 |
+
border-radius: 0.375rem;
|
| 56 |
+
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
|
| 57 |
+
width: 50%;
|
| 58 |
+
margin-bottom: 50px;
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
.form-item {
|
| 62 |
+
margin-bottom: 0.75rem;
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
.centered-item {
|
| 66 |
+
display: flex;
|
| 67 |
+
justify-content: center;
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
.stars {
|
| 71 |
+
position: fixed;
|
| 72 |
+
top: 0;
|
| 73 |
+
left: 0;
|
| 74 |
+
width: 100%;
|
| 75 |
+
height: 120%;
|
| 76 |
+
transform: rotate(-45deg);
|
| 77 |
+
z-index: -1;
|
| 78 |
+
}
|
| 79 |
+
.star {
|
| 80 |
+
--star-color: var(--primary-color);
|
| 81 |
+
--star-tail-length: 6em;
|
| 82 |
+
--star-tail-height: 2px;
|
| 83 |
+
--star-width: calc(var(--star-tail-length) / 6);
|
| 84 |
+
--fall-duration: 9s;
|
| 85 |
+
--tail-fade-duration: var(--fall-duration);
|
| 86 |
+
position: absolute;
|
| 87 |
+
top: var(--top-offset);
|
| 88 |
+
left: 0;
|
| 89 |
+
width: var(--star-tail-length);
|
| 90 |
+
height: var(--star-tail-height);
|
| 91 |
+
color: var(--star-color);
|
| 92 |
+
background: linear-gradient(45deg, currentColor, transparent);
|
| 93 |
+
border-radius: 50%;
|
| 94 |
+
filter: drop-shadow(0 0 6px currentColor);
|
| 95 |
+
transform: translate3d(104em, 0, 0);
|
| 96 |
+
animation: fall var(--fall-duration) var(--fall-delay) linear infinite, tail-fade var(--tail-fade-duration) var(--fall-delay) ease-out infinite;
|
| 97 |
+
}
|
| 98 |
+
@media screen and (max-width: 750px) {
|
| 99 |
+
.star {
|
| 100 |
+
animation: fall var(--fall-duration) var(--fall-delay) linear infinite;
|
| 101 |
+
}
|
| 102 |
+
}
|
| 103 |
+
.star:nth-child(1) {
|
| 104 |
+
--star-tail-length: 6.14em;
|
| 105 |
+
--top-offset: 3.64vh;
|
| 106 |
+
--fall-duration: 10.878s;
|
| 107 |
+
--fall-delay: 0.034s;
|
| 108 |
+
}
|
| 109 |
+
.star:nth-child(2) {
|
| 110 |
+
--star-tail-length: 5.08em;
|
| 111 |
+
--top-offset: 69.69vh;
|
| 112 |
+
--fall-duration: 11.372s;
|
| 113 |
+
--fall-delay: 1.679s;
|
| 114 |
+
}
|
| 115 |
+
.star:nth-child(3) {
|
| 116 |
+
--star-tail-length: 6.1em;
|
| 117 |
+
--top-offset: 64.3vh;
|
| 118 |
+
--fall-duration: 7.088s;
|
| 119 |
+
--fall-delay: 3.382s;
|
| 120 |
+
}
|
| 121 |
+
.star:nth-child(4) {
|
| 122 |
+
--star-tail-length: 6.66em;
|
| 123 |
+
--top-offset: 34.91vh;
|
| 124 |
+
--fall-duration: 6.184s;
|
| 125 |
+
--fall-delay: 9.61s;
|
| 126 |
+
}
|
| 127 |
+
.star:nth-child(5) {
|
| 128 |
+
--star-tail-length: 6.89em;
|
| 129 |
+
--top-offset: 24.92vh;
|
| 130 |
+
--fall-duration: 9.465s;
|
| 131 |
+
--fall-delay: 9.12s;
|
| 132 |
+
}
|
| 133 |
+
.star:nth-child(6) {
|
| 134 |
+
--star-tail-length: 6.5em;
|
| 135 |
+
--top-offset: 51.9vh;
|
| 136 |
+
--fall-duration: 10.52s;
|
| 137 |
+
--fall-delay: 0.214s;
|
| 138 |
+
}
|
| 139 |
+
.star:nth-child(7) {
|
| 140 |
+
--star-tail-length: 5.58em;
|
| 141 |
+
--top-offset: 24.15vh;
|
| 142 |
+
--fall-duration: 8.9s;
|
| 143 |
+
--fall-delay: 0.499s;
|
| 144 |
+
}
|
| 145 |
+
.star:nth-child(8) {
|
| 146 |
+
--star-tail-length: 6.26em;
|
| 147 |
+
--top-offset: 59.4vh;
|
| 148 |
+
--fall-duration: 7.671s;
|
| 149 |
+
--fall-delay: 1.694s;
|
| 150 |
+
}
|
| 151 |
+
.star:nth-child(9) {
|
| 152 |
+
--star-tail-length: 6.46em;
|
| 153 |
+
--top-offset: 54.52vh;
|
| 154 |
+
--fall-duration: 6.484s;
|
| 155 |
+
--fall-delay: 4.616s;
|
| 156 |
+
}
|
| 157 |
+
.star:nth-child(10) {
|
| 158 |
+
--star-tail-length: 5.34em;
|
| 159 |
+
--top-offset: 74.03vh;
|
| 160 |
+
--fall-duration: 8.565s;
|
| 161 |
+
--fall-delay: 1.159s;
|
| 162 |
+
}
|
| 163 |
+
.star:nth-child(11) {
|
| 164 |
+
--star-tail-length: 6.84em;
|
| 165 |
+
--top-offset: 90.94vh;
|
| 166 |
+
--fall-duration: 10.133s;
|
| 167 |
+
--fall-delay: 6.108s;
|
| 168 |
+
}
|
| 169 |
+
.star:nth-child(12) {
|
| 170 |
+
--star-tail-length: 5.48em;
|
| 171 |
+
--top-offset: 97.27vh;
|
| 172 |
+
--fall-duration: 10.248s;
|
| 173 |
+
--fall-delay: 4.186s;
|
| 174 |
+
}
|
| 175 |
+
.star:nth-child(13) {
|
| 176 |
+
--star-tail-length: 7.21em;
|
| 177 |
+
--top-offset: 17.75vh;
|
| 178 |
+
--fall-duration: 10.549s;
|
| 179 |
+
--fall-delay: 1.868s;
|
| 180 |
+
}
|
| 181 |
+
.star:nth-child(14) {
|
| 182 |
+
--star-tail-length: 6.35em;
|
| 183 |
+
--top-offset: 94.98vh;
|
| 184 |
+
--fall-duration: 9.682s;
|
| 185 |
+
--fall-delay: 9.327s;
|
| 186 |
+
}
|
| 187 |
+
.star:nth-child(15) {
|
| 188 |
+
--star-tail-length: 5.18em;
|
| 189 |
+
--top-offset: 52.87vh;
|
| 190 |
+
--fall-duration: 9.934s;
|
| 191 |
+
--fall-delay: 8.919s;
|
| 192 |
+
}
|
| 193 |
+
.star:nth-child(16) {
|
| 194 |
+
--star-tail-length: 5.07em;
|
| 195 |
+
--top-offset: 50.26vh;
|
| 196 |
+
--fall-duration: 11.826s;
|
| 197 |
+
--fall-delay: 8.478s;
|
| 198 |
+
}
|
| 199 |
+
.star:nth-child(17) {
|
| 200 |
+
--star-tail-length: 7.01em;
|
| 201 |
+
--top-offset: 85.73vh;
|
| 202 |
+
--fall-duration: 9.87s;
|
| 203 |
+
--fall-delay: 1.206s;
|
| 204 |
+
}
|
| 205 |
+
.star:nth-child(18) {
|
| 206 |
+
--star-tail-length: 6.39em;
|
| 207 |
+
--top-offset: 2.81vh;
|
| 208 |
+
--fall-duration: 10.292s;
|
| 209 |
+
--fall-delay: 4.394s;
|
| 210 |
+
}
|
| 211 |
+
.star:nth-child(19) {
|
| 212 |
+
--star-tail-length: 5.27em;
|
| 213 |
+
--top-offset: 56.83vh;
|
| 214 |
+
--fall-duration: 8.944s;
|
| 215 |
+
--fall-delay: 8.779s;
|
| 216 |
+
}
|
| 217 |
+
.star:nth-child(20) {
|
| 218 |
+
--star-tail-length: 6.07em;
|
| 219 |
+
--top-offset: 54.55vh;
|
| 220 |
+
--fall-duration: 9.324s;
|
| 221 |
+
--fall-delay: 7.375s;
|
| 222 |
+
}
|
| 223 |
+
.star:nth-child(21) {
|
| 224 |
+
--star-tail-length: 6.1em;
|
| 225 |
+
--top-offset: 87.67vh;
|
| 226 |
+
--fall-duration: 11.943s;
|
| 227 |
+
--fall-delay: 6.919s;
|
| 228 |
+
}
|
| 229 |
+
.star:nth-child(22) {
|
| 230 |
+
--star-tail-length: 5.98em;
|
| 231 |
+
--top-offset: 70.04vh;
|
| 232 |
+
--fall-duration: 9.995s;
|
| 233 |
+
--fall-delay: 4.472s;
|
| 234 |
+
}
|
| 235 |
+
.star:nth-child(23) {
|
| 236 |
+
--star-tail-length: 6.34em;
|
| 237 |
+
--top-offset: 77.19vh;
|
| 238 |
+
--fall-duration: 10.073s;
|
| 239 |
+
--fall-delay: 8.354s;
|
| 240 |
+
}
|
| 241 |
+
.star:nth-child(24) {
|
| 242 |
+
--star-tail-length: 6.95em;
|
| 243 |
+
--top-offset: 14.48vh;
|
| 244 |
+
--fall-duration: 9.028s;
|
| 245 |
+
--fall-delay: 7.638s;
|
| 246 |
+
}
|
| 247 |
+
.star:nth-child(25) {
|
| 248 |
+
--star-tail-length: 6.23em;
|
| 249 |
+
--top-offset: 8.48vh;
|
| 250 |
+
--fall-duration: 7.427s;
|
| 251 |
+
--fall-delay: 0.915s;
|
| 252 |
+
}
|
| 253 |
+
.star:nth-child(26) {
|
| 254 |
+
--star-tail-length: 5.09em;
|
| 255 |
+
--top-offset: 6.56vh;
|
| 256 |
+
--fall-duration: 7.706s;
|
| 257 |
+
--fall-delay: 2.841s;
|
| 258 |
+
}
|
| 259 |
+
.star:nth-child(27) {
|
| 260 |
+
--star-tail-length: 7.01em;
|
| 261 |
+
--top-offset: 92.85vh;
|
| 262 |
+
--fall-duration: 7.359s;
|
| 263 |
+
--fall-delay: 7.229s;
|
| 264 |
+
}
|
| 265 |
+
.star:nth-child(28) {
|
| 266 |
+
--star-tail-length: 5.49em;
|
| 267 |
+
--top-offset: 27.89vh;
|
| 268 |
+
--fall-duration: 10.344s;
|
| 269 |
+
--fall-delay: 2.346s;
|
| 270 |
+
}
|
| 271 |
+
.star:nth-child(29) {
|
| 272 |
+
--star-tail-length: 5.82em;
|
| 273 |
+
--top-offset: 56.08vh;
|
| 274 |
+
--fall-duration: 10.911s;
|
| 275 |
+
--fall-delay: 4.231s;
|
| 276 |
+
}
|
| 277 |
+
.star:nth-child(30) {
|
| 278 |
+
--star-tail-length: 7.24em;
|
| 279 |
+
--top-offset: 22.54vh;
|
| 280 |
+
--fall-duration: 9.344s;
|
| 281 |
+
--fall-delay: 2.112s;
|
| 282 |
+
}
|
| 283 |
+
.star:nth-child(31) {
|
| 284 |
+
--star-tail-length: 6.8em;
|
| 285 |
+
--top-offset: 59.49vh;
|
| 286 |
+
--fall-duration: 7.059s;
|
| 287 |
+
--fall-delay: 0.924s;
|
| 288 |
+
}
|
| 289 |
+
.star:nth-child(32) {
|
| 290 |
+
--star-tail-length: 5.22em;
|
| 291 |
+
--top-offset: 44.01vh;
|
| 292 |
+
--fall-duration: 10.121s;
|
| 293 |
+
--fall-delay: 0.591s;
|
| 294 |
+
}
|
| 295 |
+
.star:nth-child(33) {
|
| 296 |
+
--star-tail-length: 6.1em;
|
| 297 |
+
--top-offset: 78.61vh;
|
| 298 |
+
--fall-duration: 8.306s;
|
| 299 |
+
--fall-delay: 4.403s;
|
| 300 |
+
}
|
| 301 |
+
.star:nth-child(34) {
|
| 302 |
+
--star-tail-length: 7.26em;
|
| 303 |
+
--top-offset: 85.76vh;
|
| 304 |
+
--fall-duration: 7.058s;
|
| 305 |
+
--fall-delay: 6.772s;
|
| 306 |
+
}
|
| 307 |
+
.star:nth-child(35) {
|
| 308 |
+
--star-tail-length: 7.01em;
|
| 309 |
+
--top-offset: 77.17vh;
|
| 310 |
+
--fall-duration: 6.29s;
|
| 311 |
+
--fall-delay: 1.468s;
|
| 312 |
+
}
|
| 313 |
+
.star:nth-child(36) {
|
| 314 |
+
--star-tail-length: 5.17em;
|
| 315 |
+
--top-offset: 13.63vh;
|
| 316 |
+
--fall-duration: 6.739s;
|
| 317 |
+
--fall-delay: 0.019s;
|
| 318 |
+
}
|
| 319 |
+
.star:nth-child(37) {
|
| 320 |
+
--star-tail-length: 6.41em;
|
| 321 |
+
--top-offset: 70.18vh;
|
| 322 |
+
--fall-duration: 6.177s;
|
| 323 |
+
--fall-delay: 8.148s;
|
| 324 |
+
}
|
| 325 |
+
.star:nth-child(38) {
|
| 326 |
+
--star-tail-length: 5.32em;
|
| 327 |
+
--top-offset: 62.65vh;
|
| 328 |
+
--fall-duration: 10.476s;
|
| 329 |
+
--fall-delay: 0.98s;
|
| 330 |
+
}
|
| 331 |
+
.star:nth-child(39) {
|
| 332 |
+
--star-tail-length: 7.24em;
|
| 333 |
+
--top-offset: 66.12vh;
|
| 334 |
+
--fall-duration: 8.449s;
|
| 335 |
+
--fall-delay: 4.255s;
|
| 336 |
+
}
|
| 337 |
+
.star:nth-child(40) {
|
| 338 |
+
--star-tail-length: 6.73em;
|
| 339 |
+
--top-offset: 14.73vh;
|
| 340 |
+
--fall-duration: 9.857s;
|
| 341 |
+
--fall-delay: 6.867s;
|
| 342 |
+
}
|
| 343 |
+
.star:nth-child(41) {
|
| 344 |
+
--star-tail-length: 5.25em;
|
| 345 |
+
--top-offset: 45.23vh;
|
| 346 |
+
--fall-duration: 7.898s;
|
| 347 |
+
--fall-delay: 4.966s;
|
| 348 |
+
}
|
| 349 |
+
.star:nth-child(42) {
|
| 350 |
+
--star-tail-length: 6.73em;
|
| 351 |
+
--top-offset: 36.17vh;
|
| 352 |
+
--fall-duration: 7.32s;
|
| 353 |
+
--fall-delay: 3.93s;
|
| 354 |
+
}
|
| 355 |
+
.star:nth-child(43) {
|
| 356 |
+
--star-tail-length: 7.38em;
|
| 357 |
+
--top-offset: 83.09vh;
|
| 358 |
+
--fall-duration: 7.394s;
|
| 359 |
+
--fall-delay: 5.388s;
|
| 360 |
+
}
|
| 361 |
+
.star:nth-child(44) {
|
| 362 |
+
--star-tail-length: 5.18em;
|
| 363 |
+
--top-offset: 98.36vh;
|
| 364 |
+
--fall-duration: 6.905s;
|
| 365 |
+
--fall-delay: 2.771s;
|
| 366 |
+
}
|
| 367 |
+
.star:nth-child(45) {
|
| 368 |
+
--star-tail-length: 6.66em;
|
| 369 |
+
--top-offset: 27.99vh;
|
| 370 |
+
--fall-duration: 7.62s;
|
| 371 |
+
--fall-delay: 3.624s;
|
| 372 |
+
}
|
| 373 |
+
.star:nth-child(46) {
|
| 374 |
+
--star-tail-length: 5.19em;
|
| 375 |
+
--top-offset: 92vh;
|
| 376 |
+
--fall-duration: 9.158s;
|
| 377 |
+
--fall-delay: 1.984s;
|
| 378 |
+
}
|
| 379 |
+
.star:nth-child(47) {
|
| 380 |
+
--star-tail-length: 6.16em;
|
| 381 |
+
--top-offset: 2.87vh;
|
| 382 |
+
--fall-duration: 9.266s;
|
| 383 |
+
--fall-delay: 4.04s;
|
| 384 |
+
}
|
| 385 |
+
.star:nth-child(48) {
|
| 386 |
+
--star-tail-length: 6.34em;
|
| 387 |
+
--top-offset: 19.39vh;
|
| 388 |
+
--fall-duration: 7.503s;
|
| 389 |
+
--fall-delay: 0.045s;
|
| 390 |
+
}
|
| 391 |
+
.star:nth-child(49) {
|
| 392 |
+
--star-tail-length: 6.85em;
|
| 393 |
+
--top-offset: 79.92vh;
|
| 394 |
+
--fall-duration: 7.472s;
|
| 395 |
+
--fall-delay: 1.514s;
|
| 396 |
+
}
|
| 397 |
+
.star:nth-child(50) {
|
| 398 |
+
--star-tail-length: 7.35em;
|
| 399 |
+
--top-offset: 63.71vh;
|
| 400 |
+
--fall-duration: 8.117s;
|
| 401 |
+
--fall-delay: 4.46s;
|
| 402 |
+
}
|
| 403 |
+
.star::before, .star::after {
|
| 404 |
+
position: absolute;
|
| 405 |
+
content: "";
|
| 406 |
+
top: 0;
|
| 407 |
+
left: calc(var(--star-width) / -2);
|
| 408 |
+
width: var(--star-width);
|
| 409 |
+
height: 100%;
|
| 410 |
+
background: linear-gradient(45deg, transparent, currentColor, transparent);
|
| 411 |
+
border-radius: inherit;
|
| 412 |
+
animation: blink 2s linear infinite;
|
| 413 |
+
}
|
| 414 |
+
.star::before {
|
| 415 |
+
transform: rotate(45deg);
|
| 416 |
+
}
|
| 417 |
+
.star::after {
|
| 418 |
+
transform: rotate(-45deg);
|
| 419 |
+
}
|
| 420 |
+
|
| 421 |
+
@keyframes fall {
|
| 422 |
+
to {
|
| 423 |
+
transform: translate3d(-30em, 0, 0);
|
| 424 |
+
}
|
| 425 |
+
}
|
| 426 |
+
@keyframes tail-fade {
|
| 427 |
+
0%, 50% {
|
| 428 |
+
width: var(--star-tail-length);
|
| 429 |
+
opacity: 1;
|
| 430 |
+
}
|
| 431 |
+
70%, 80% {
|
| 432 |
+
width: 0;
|
| 433 |
+
opacity: 0.4;
|
| 434 |
+
}
|
| 435 |
+
100% {
|
| 436 |
+
width: 0;
|
| 437 |
+
opacity: 0;
|
| 438 |
+
}
|
| 439 |
+
}
|
| 440 |
+
@keyframes blink {
|
| 441 |
+
50% {
|
| 442 |
+
opacity: 0.6;
|
| 443 |
+
}
|
| 444 |
+
}
|
| 445 |
+
</style>
|
| 446 |
+
</head>
|
| 447 |
+
|
| 448 |
+
<body>
|
| 449 |
+
<div class="stars"></div>
|
| 450 |
+
|
| 451 |
+
<script>
|
| 452 |
+
document.addEventListener("DOMContentLoaded", function() {
|
| 453 |
+
const starsContainer = document.querySelector('.stars');
|
| 454 |
+
const isMobile = window.innerWidth <= 750;
|
| 455 |
+
const starCount = isMobile ? 30 : 30; // Reduce the number of stars on mobile because of performance issues (currently disabled because we need to find a better workarround)
|
| 456 |
+
|
| 457 |
+
for (let i = 0; i < starCount; i++) {
|
| 458 |
+
const star = document.createElement("div");
|
| 459 |
+
star.classList.add("star");
|
| 460 |
+
starsContainer.appendChild(star);
|
| 461 |
+
}
|
| 462 |
+
});
|
| 463 |
+
</script>
|
| 464 |
+
|
| 465 |
+
<div class="header">
|
| 466 |
+
<p class="comet-text">🚀 Comet - <a href="https://github.com/g0ldyy/comet">GitHub</a></p>
|
| 467 |
+
{{CUSTOM_HEADER_HTML|safe}}
|
| 468 |
+
</div>
|
| 469 |
+
|
| 470 |
+
<div class="form-container">
|
| 471 |
+
<div class="form-item">
|
| 472 |
+
<sl-select id="indexers" multiple clearable label="Indexers" placeholder="Select indexers"></sl-select>
|
| 473 |
+
</div>
|
| 474 |
+
|
| 475 |
+
<div class="form-item">
|
| 476 |
+
<sl-select id="languages" multiple clearable label="Languages" placeholder="Select languages"></sl-select>
|
| 477 |
+
</div>
|
| 478 |
+
|
| 479 |
+
<div class="form-item">
|
| 480 |
+
<sl-select id="resolutions" multiple clearable label="Resolutions" placeholder="Select resolutions"></sl-select>
|
| 481 |
+
</div>
|
| 482 |
+
|
| 483 |
+
<div class="form-item">
|
| 484 |
+
<sl-input id="maxResults" type="number" min=0 value=0 label="Max Results" placeholder="Enter max results"></sl-input>
|
| 485 |
+
</div>
|
| 486 |
+
|
| 487 |
+
<div class="form-item">
|
| 488 |
+
<sl-select id="debridService" value="realdebrid" label="Debrid Service" placeholder="Select debrid service">
|
| 489 |
+
<sl-option value="realdebrid">Real-Debrid</sl-option>
|
| 490 |
+
</sl-select>
|
| 491 |
+
</div>
|
| 492 |
+
|
| 493 |
+
<div class="form-item">
|
| 494 |
+
<sl-input id="debridApiKey" label="Debrid API Key" placeholder="Enter API key"></sl-input>
|
| 495 |
+
</div>
|
| 496 |
+
|
| 497 |
+
<div class="centered-item">
|
| 498 |
+
<sl-button id="install" variant="neutral">Install</sl-button>
|
| 499 |
+
|
| 500 |
+
<sl-alert variant="neutral" duration="3000" closable>
|
| 501 |
+
<sl-icon slot="icon" name="clipboard2-check"></sl-icon>
|
| 502 |
+
<strong>The Stremio addon link has been automatically copied.</strong>
|
| 503 |
+
</sl-alert>
|
| 504 |
+
|
| 505 |
+
<script type="module">
|
| 506 |
+
let defaultLanguages = [];
|
| 507 |
+
let defaultResolutions = [];
|
| 508 |
+
|
| 509 |
+
document.addEventListener("DOMContentLoaded", function() {
|
| 510 |
+
fetch("/static/config.json")
|
| 511 |
+
.then(response => response.json())
|
| 512 |
+
.then(data => {
|
| 513 |
+
populateSelect("indexers", data.indexers);
|
| 514 |
+
populateSelect("languages", data.languages);
|
| 515 |
+
populateSelect("resolutions", data.resolutions);
|
| 516 |
+
|
| 517 |
+
defaultLanguages = data.languages;
|
| 518 |
+
defaultResolutions = data.resolutions;
|
| 519 |
+
});
|
| 520 |
+
});
|
| 521 |
+
|
| 522 |
+
function populateSelect(selectId, options) {
|
| 523 |
+
const selectElement = document.getElementById(selectId);
|
| 524 |
+
options.forEach(option => {
|
| 525 |
+
const optionElement = document.createElement("sl-option");
|
| 526 |
+
optionElement.value = option;
|
| 527 |
+
optionElement.textContent = option;
|
| 528 |
+
selectElement.appendChild(optionElement);
|
| 529 |
+
});
|
| 530 |
+
selectElement.value = options;
|
| 531 |
+
}
|
| 532 |
+
|
| 533 |
+
const button = document.querySelector("sl-button");
|
| 534 |
+
const alert = document.querySelector('sl-alert[variant="neutral"]');
|
| 535 |
+
button.addEventListener("click", () => {
|
| 536 |
+
const debridService = document.getElementById("debridService").value;
|
| 537 |
+
const debridApiKey = document.getElementById("debridApiKey").value;
|
| 538 |
+
const indexers = Array.from(document.getElementById("indexers").selectedOptions).map(option => option.value);
|
| 539 |
+
const languages = Array.from(document.getElementById("languages").selectedOptions).map(option => option.value);
|
| 540 |
+
const resolutions = Array.from(document.getElementById("resolutions").selectedOptions).map(option => option.value);
|
| 541 |
+
const maxResults = document.getElementById("maxResults").value;
|
| 542 |
+
|
| 543 |
+
const selectedLanguages = languages.length === defaultLanguages.length && languages.every((val, index) => val === defaultLanguages[index]) ? ["All"] : languages;
|
| 544 |
+
const selectedResolutions = resolutions.length === defaultResolutions.length && resolutions.every((val, index) => val === defaultResolutions[index]) ? ["All"] : resolutions;
|
| 545 |
+
|
| 546 |
+
const settings = {
|
| 547 |
+
debridService: debridService,
|
| 548 |
+
debridApiKey: debridApiKey,
|
| 549 |
+
indexers: indexers,
|
| 550 |
+
maxResults: parseInt(maxResults),
|
| 551 |
+
resolutions: selectedResolutions,
|
| 552 |
+
languages: selectedLanguages
|
| 553 |
+
};
|
| 554 |
+
|
| 555 |
+
navigator.clipboard.writeText(`${window.location.origin}/${btoa(JSON.stringify(settings))}/manifest.json`).then(() => {
|
| 556 |
+
alert.toast();
|
| 557 |
+
});
|
| 558 |
+
});
|
| 559 |
+
</script>
|
| 560 |
+
</div>
|
| 561 |
+
</div>
|
| 562 |
+
</body>
|
| 563 |
+
</html>
|
comet/utils/__init__.py
ADDED
|
File without changes
|
comet/utils/general.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import math
|
| 2 |
+
|
| 3 |
+
translationTable = {
|
| 4 |
+
'ā': 'a', 'ă': 'a', 'ą': 'a', 'ć': 'c', 'č': 'c', 'ç': 'c',
|
| 5 |
+
'ĉ': 'c', 'ċ': 'c', 'ď': 'd', 'đ': 'd', 'è': 'e', 'é': 'e',
|
| 6 |
+
'ê': 'e', 'ë': 'e', 'ē': 'e', 'ĕ': 'e', 'ę': 'e', 'ě': 'e',
|
| 7 |
+
'ĝ': 'g', 'ğ': 'g', 'ġ': 'g', 'ģ': 'g', 'ĥ': 'h', 'î': 'i',
|
| 8 |
+
'ï': 'i', 'ì': 'i', 'í': 'i', 'ī': 'i', 'ĩ': 'i', 'ĭ': 'i',
|
| 9 |
+
'ı': 'i', 'ĵ': 'j', 'ķ': 'k', 'ĺ': 'l', 'ļ': 'l', 'ł': 'l',
|
| 10 |
+
'ń': 'n', 'ň': 'n', 'ñ': 'n', 'ņ': 'n', 'ʼn': 'n', 'ó': 'o',
|
| 11 |
+
'ô': 'o', 'õ': 'o', 'ö': 'o', 'ø': 'o', 'ō': 'o', 'ő': 'o',
|
| 12 |
+
'œ': 'oe', 'ŕ': 'r', 'ř': 'r', 'ŗ': 'r', 'š': 's', 'ş': 's',
|
| 13 |
+
'ś': 's', 'ș': 's', 'ß': 'ss', 'ť': 't', 'ţ': 't', 'ū': 'u',
|
| 14 |
+
'ŭ': 'u', 'ũ': 'u', 'û': 'u', 'ü': 'u', 'ù': 'u', 'ú': 'u',
|
| 15 |
+
'ų': 'u', 'ű': 'u', 'ŵ': 'w', 'ý': 'y', 'ÿ': 'y', 'ŷ': 'y',
|
| 16 |
+
'ž': 'z', 'ż': 'z', 'ź': 'z', 'æ': 'ae', 'ǎ': 'a', 'ǧ': 'g',
|
| 17 |
+
'ə': 'e', 'ƒ': 'f', 'ǐ': 'i', 'ǒ': 'o', 'ǔ': 'u', 'ǚ': 'u',
|
| 18 |
+
'ǜ': 'u', 'ǹ': 'n', 'ǻ': 'a', 'ǽ': 'ae', 'ǿ': 'o',
|
| 19 |
+
}
|
| 20 |
+
translationTable = str.maketrans(translationTable)
|
| 21 |
+
|
| 22 |
+
def translate(title: str):
|
| 23 |
+
return title.translate(translationTable)
|
| 24 |
+
|
| 25 |
+
def isVideo(title: str):
|
| 26 |
+
return title.endswith(tuple([".mkv", ".mp4", ".avi", ".mov", ".flv", ".wmv", ".webm", ".mpg", ".mpeg", ".m4v", ".3gp", ".3g2", ".ogv", ".ogg", ".drc", ".gif", ".gifv", ".mng", ".avi", ".mov", ".qt", ".wmv", ".yuv", ".rm", ".rmvb", ".asf", ".amv", ".m4p", ".m4v", ".mpg", ".mp2", ".mpeg", ".mpe", ".mpv", ".mpg", ".mpeg", ".m2v", ".m4v", ".svi", ".3gp", ".3g2", ".mxf", ".roq", ".nsv", ".flv", ".f4v", ".f4p", ".f4a", ".f4b"]))
|
| 27 |
+
|
| 28 |
+
def bytesToSize(bytes: int):
|
| 29 |
+
sizes = ["Bytes", "KB", "MB", "GB", "TB"]
|
| 30 |
+
|
| 31 |
+
if bytes == 0:
|
| 32 |
+
return "0 Byte"
|
| 33 |
+
|
| 34 |
+
i = int(math.floor(math.log(bytes, 1024)))
|
| 35 |
+
|
| 36 |
+
return f"{round(bytes / math.pow(1024, i), 2)} {sizes[i]}"
|
comet/utils/logger.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sys
|
| 2 |
+
from loguru import logger
|
| 3 |
+
|
| 4 |
+
def setupLogger(level: str):
|
| 5 |
+
logger.level("INFO", icon="📰", color="<fg #FC5F39>")
|
| 6 |
+
logger.level("DEBUG", icon="🕸️", color="<fg #DC5F00>")
|
| 7 |
+
logger.level("WARNING", icon="⚠️", color="<fg #DC5F00>")
|
| 8 |
+
|
| 9 |
+
log_format = (
|
| 10 |
+
"<white>{time:YYYY-MM-DD}</white> <magenta>{time:HH:mm:ss}</magenta> | "
|
| 11 |
+
"<level>{level.icon}</level> <level>{level}</level> | "
|
| 12 |
+
"<cyan>{module}</cyan>.<cyan>{function}</cyan> - <level>{message}</level>"
|
| 13 |
+
)
|
| 14 |
+
|
| 15 |
+
logger.configure(handlers=[
|
| 16 |
+
{
|
| 17 |
+
"sink": sys.stderr,
|
| 18 |
+
"level": level,
|
| 19 |
+
"format": log_format,
|
| 20 |
+
"backtrace": False,
|
| 21 |
+
"diagnose": False,
|
| 22 |
+
"enqueue": True,
|
| 23 |
+
}
|
| 24 |
+
])
|
| 25 |
+
|
| 26 |
+
setupLogger("DEBUG")
|