Files
comet/comet/core/models.py
T

807 lines
26 KiB
Python

import random
import string
from typing import List, Optional, Union
import RTN
from databases import Database
from pydantic import BaseModel, Field, field_validator
from pydantic_settings import BaseSettings, SettingsConfigDict
from RTN import DefaultRanking, SettingsModel
from RTN.models import (AudioRankModel, CustomRank, CustomRanksConfig,
ExtrasRankModel, HdrRankModel, LanguagesConfig,
OptionsConfig, QualityRankModel, ResolutionConfig,
RipsRankModel)
from comet.core.db_router import ReplicaAwareDatabase
from comet.core.logger import logger
class AppSettings(BaseSettings):
model_config = SettingsConfigDict(
env_file=".env", env_file_encoding="utf-8", extra="allow"
)
ADDON_ID: Optional[str] = "stremio.comet.fast"
ADDON_NAME: Optional[str] = "Comet"
FASTAPI_HOST: Optional[str] = "0.0.0.0"
FASTAPI_PORT: Optional[int] = 8000
FASTAPI_WORKERS: Optional[int] = 1
USE_GUNICORN: Optional[bool] = True
GUNICORN_PRELOAD_APP: Optional[bool] = True
EXECUTOR_MAX_WORKERS: Optional[int] = None
ADMIN_DASHBOARD_PASSWORD: Optional[str] = "".join(
random.choices(string.ascii_letters + string.digits, k=16)
)
PUBLIC_METRICS_API: Optional[bool] = False
DATABASE_TYPE: Optional[str] = "sqlite"
DATABASE_URL: Optional[str] = "username:password@hostname:port"
DATABASE_PATH: Optional[str] = "data/comet.db"
DATABASE_BATCH_SIZE: Optional[int] = 20000
DATABASE_READ_REPLICA_URLS: List[str] = Field(default_factory=list)
DATABASE_STARTUP_CLEANUP_INTERVAL: Optional[int] = 3600
METADATA_CACHE_TTL: Optional[int] = 2592000 # 30 days
TORRENT_CACHE_TTL: Optional[int] = 2592000 # 30 days
LIVE_TORRENT_CACHE_TTL: Optional[int] = 604800 # 7 days
DEBRID_CACHE_TTL: Optional[int] = 86400 # 1 day
METRICS_CACHE_TTL: Optional[int] = 60 # 1 minute
DEBRID_CACHE_CHECK_RATIO: Optional[float] = 0.0 # 0.0 to 1.0
SCRAPE_LOCK_TTL: Optional[int] = 300 # 5 minutes
SCRAPE_WAIT_TIMEOUT: Optional[int] = (
30 # Max time to wait for other instance to complete
)
INDEXER_MANAGER_TYPE: Optional[str] = None
INDEXER_MANAGER_URL: Optional[str] = "http://127.0.0.1:9117"
INDEXER_MANAGER_API_KEY: Optional[str] = None
INDEXER_MANAGER_MODE: Union[bool, str] = "both"
INDEXER_MANAGER_TIMEOUT: Optional[int] = 30
INDEXER_MANAGER_INDEXERS: List[str] = []
INDEXER_MANAGER_UPDATE_INTERVAL: Optional[int] = 900
INDEXER_MANAGER_WAIT_TIMEOUT: Optional[int] = 30
SCRAPE_JACKETT: Union[bool, str] = False
JACKETT_URL: Optional[str] = "http://127.0.0.1:9117"
JACKETT_API_KEY: Optional[str] = None
JACKETT_INDEXERS: List[str] = []
SCRAPE_PROWLARR: Union[bool, str] = False
PROWLARR_URL: Optional[str] = "http://127.0.0.1:9696"
PROWLARR_API_KEY: Optional[str] = None
PROWLARR_INDEXERS: List[str] = []
GET_TORRENT_TIMEOUT: Optional[int] = 5
MAGNET_RESOLVE_TIMEOUT: Optional[int] = 60
CATALOG_TIMEOUT: Optional[int] = 30
DOWNLOAD_TORRENT_FILES: Optional[bool] = False
SCRAPE_COMET: Union[bool, str] = False
COMET_URL: Union[str, List[str]] = "https://comet.elfhosted.com"
SCRAPE_NYAA: Union[bool, str] = False
NYAA_ANIME_ONLY: Optional[bool] = True
NYAA_MAX_CONCURRENT_PAGES: Optional[int] = 5
SCRAPE_ANIMETOSHO: Union[bool, str] = False
ANIMETOSHO_ANIME_ONLY: Optional[bool] = True
ANIMETOSHO_MAX_CONCURRENT_PAGES: Optional[int] = 10
SCRAPE_ZILEAN: Union[bool, str] = False
ZILEAN_URL: Union[str, List[str]] = "https://zileanfortheweebs.midnightignite.me"
SCRAPE_STREMTHRU: Union[bool, str] = False
STREMTHRU_SCRAPE_URL: Union[str, List[str]] = "https://stremthru.13377001.xyz"
SCRAPE_BITMAGNET: Union[bool, str] = False
BITMAGNET_URL: Union[str, List[str]] = (
"https://bitmagnetfortheweebs.midnightignite.me"
)
BITMAGNET_MAX_CONCURRENT_PAGES: Optional[int] = 5
BITMAGNET_MAX_OFFSET: Optional[int] = 15000
SCRAPE_TORRENTIO: Union[bool, str] = False
TORRENTIO_URL: Union[str, List[str]] = "https://torrentio.strem.fun"
SCRAPE_MEDIAFUSION: Union[bool, str] = False
MEDIAFUSION_URL: Union[str, List[str]] = "https://mediafusion.elfhosted.com"
MEDIAFUSION_API_PASSWORD: Union[str, List[str], None] = None
MEDIAFUSION_LIVE_SEARCH: Optional[bool] = True
SCRAPE_AIOSTREAMS: Union[bool, str] = False
AIOSTREAMS_URL: Optional[Union[str, List[str]]] = None
AIOSTREAMS_USER_UUID_AND_PASSWORD: Union[str, List[str], None] = None
SCRAPE_JACKETTIO: Union[bool, str] = False
JACKETTIO_URL: Optional[Union[str, List[str]]] = None
SCRAPE_DEBRIDIO: Union[bool, str] = False
DEBRIDIO_API_KEY: Optional[str] = None
DEBRIDIO_PROVIDER: Optional[str] = None
DEBRIDIO_PROVIDER_KEY: Optional[str] = None
SCRAPE_TORBOX: Union[bool, str] = False
TORBOX_API_KEY: Optional[str] = None
SCRAPE_TORRENTSDB: Union[bool, str] = False
CUSTOM_HEADER_HTML: Optional[str] = None
PROXY_DEBRID_STREAM: Optional[bool] = False
PROXY_DEBRID_STREAM_PASSWORD: Optional[str] = "".join(
random.choices(string.ascii_letters + string.digits, k=16)
)
PROXY_DEBRID_STREAM_MAX_CONNECTIONS: Optional[int] = -1
PROXY_DEBRID_STREAM_DEBRID_DEFAULT_SERVICE: Optional[str] = "realdebrid"
PROXY_DEBRID_STREAM_DEBRID_DEFAULT_APIKEY: Optional[str] = None
PROXY_DEBRID_STREAM_INACTIVITY_THRESHOLD: Optional[int] = 300
STREMTHRU_URL: Optional[str] = "https://stremthru.13377001.xyz"
DISABLE_TORRENT_STREAMS: Optional[bool] = False
TORRENT_DISABLED_STREAM_NAME: Optional[str] = "[INFO] Comet"
TORRENT_DISABLED_STREAM_DESCRIPTION: Optional[str] = (
"Direct torrent playback is disabled on this server."
)
TORRENT_DISABLED_STREAM_URL: Optional[str] = "https://comet.feels.legal"
PUBLIC_BASE_URL: Optional[str] = None
REMOVE_ADULT_CONTENT: Optional[bool] = False
BACKGROUND_SCRAPER_ENABLED: Optional[bool] = False
BACKGROUND_SCRAPER_CONCURRENT_WORKERS: Optional[int] = 1
BACKGROUND_SCRAPER_INTERVAL: Optional[int] = 3600
BACKGROUND_SCRAPER_MAX_MOVIES_PER_RUN: Optional[int] = 100
BACKGROUND_SCRAPER_MAX_SERIES_PER_RUN: Optional[int] = 100
ANIME_MAPPING_REFRESH_INTERVAL: Optional[int] = 432000
DIGITAL_RELEASE_FILTER: Optional[bool] = False
TMDB_READ_ACCESS_TOKEN: Optional[str] = None
GLOBAL_PROXY_URL: Optional[str] = None
PROXY_ETHOS: Optional[str] = "always"
RATELIMIT_MAX_RETRIES: Optional[int] = 3
RATELIMIT_RETRY_BASE_DELAY: Optional[float] = 1.0
RTN_FILTER_DEBUG: Optional[bool] = False
@field_validator("INDEXER_MANAGER_TYPE")
def set_indexer_manager_type(cls, v, values):
if v is not None and v.lower() == "none":
return None
return v
@field_validator("DATABASE_TYPE", mode="before")
def normalize_database_type(cls, v):
if v is None:
return v
value = str(v).strip().lower()
if value in {"postgres", "postgresql", "postgresql+asyncpg", "pgsql", "psql"}:
return "postgresql"
if value in {"sqlite", "sqlite3"}:
return "sqlite"
return value
@field_validator(
"INDEXER_MANAGER_INDEXERS", "JACKETT_INDEXERS", "PROWLARR_INDEXERS"
)
def indexer_manager_indexers_normalization(cls, v, values):
v = [indexer.replace(" ", "").lower() for indexer in v]
return v
@field_validator(
"INDEXER_MANAGER_URL",
"STREMTHRU_URL",
"STREMTHRU_SCRAPE_URL",
"BITMAGNET_URL",
"COMET_URL",
"ZILEAN_URL",
"TORRENTIO_URL",
"MEDIAFUSION_URL",
"AIOSTREAMS_URL",
"JACKETTIO_URL",
"JACKETT_URL",
"PROWLARR_URL",
"PUBLIC_BASE_URL",
)
def normalize_urls(cls, v):
if isinstance(v, str):
return v.rstrip("/")
elif isinstance(v, list):
return [url.rstrip("/") for url in v]
return v
def is_scraper_enabled(self, scraper_setting: Union[bool, str], context: str):
if isinstance(scraper_setting, bool):
return scraper_setting
if isinstance(scraper_setting, str):
scraper_setting = scraper_setting.lower()
if scraper_setting in ["true", "both"]:
return True
elif scraper_setting == context:
return True
return False
def format_scraper_mode(self, scraper_setting: Union[bool, str]):
if isinstance(scraper_setting, bool):
return "both" if scraper_setting else "False"
if isinstance(scraper_setting, str):
scraper_setting = scraper_setting.lower()
if scraper_setting in ["true", "both"]:
return "both"
elif scraper_setting in ["live", "background"]:
return scraper_setting
return "False"
def is_any_context_enabled(self, scraper_setting: Union[bool, str]):
if isinstance(scraper_setting, bool):
return scraper_setting
if isinstance(scraper_setting, str):
scraper_setting = scraper_setting.lower()
return scraper_setting in ["true", "both", "live", "background"]
def model_post_init(self, __context):
if self.INDEXER_MANAGER_TYPE == "jackett":
if not self.SCRAPE_JACKETT:
self.SCRAPE_JACKETT = self.INDEXER_MANAGER_MODE
if self.JACKETT_URL == "http://127.0.0.1:9117" and self.INDEXER_MANAGER_URL:
self.JACKETT_URL = self.INDEXER_MANAGER_URL
if not self.JACKETT_API_KEY and self.INDEXER_MANAGER_API_KEY:
self.JACKETT_API_KEY = self.INDEXER_MANAGER_API_KEY
if not self.JACKETT_INDEXERS and self.INDEXER_MANAGER_INDEXERS:
self.JACKETT_INDEXERS = self.INDEXER_MANAGER_INDEXERS
elif self.INDEXER_MANAGER_TYPE == "prowlarr":
if not self.SCRAPE_PROWLARR:
self.SCRAPE_PROWLARR = self.INDEXER_MANAGER_MODE
if (
self.PROWLARR_URL == "http://127.0.0.1:9696"
and self.INDEXER_MANAGER_URL
):
self.PROWLARR_URL = self.INDEXER_MANAGER_URL
if not self.PROWLARR_API_KEY and self.INDEXER_MANAGER_API_KEY:
self.PROWLARR_API_KEY = self.INDEXER_MANAGER_API_KEY
if not self.PROWLARR_INDEXERS and self.INDEXER_MANAGER_INDEXERS:
self.PROWLARR_INDEXERS = self.INDEXER_MANAGER_INDEXERS
settings = AppSettings()
class CometSettingsModel(SettingsModel):
model_config = SettingsConfigDict()
resolutions: ResolutionConfig = ResolutionConfig(
r2160p=True, r480p=True, r360p=True
)
options: OptionsConfig = OptionsConfig(remove_ranks_under=-10000000000)
languages: LanguagesConfig = LanguagesConfig(exclude=[])
custom_ranks: CustomRanksConfig = CustomRanksConfig(
quality=QualityRankModel(
av1=CustomRank(fetch=True),
dvd=CustomRank(fetch=True),
mpeg=CustomRank(fetch=True),
remux=CustomRank(fetch=True),
vhs=CustomRank(fetch=True),
webmux=CustomRank(fetch=True),
xvid=CustomRank(fetch=True),
),
rips=RipsRankModel(
bdrip=CustomRank(fetch=True),
dvdrip=CustomRank(fetch=True),
ppvrip=CustomRank(fetch=True),
satrip=CustomRank(fetch=True),
tvrip=CustomRank(fetch=True),
uhdrip=CustomRank(fetch=True),
vhsrip=CustomRank(fetch=True),
webdlrip=CustomRank(fetch=True),
),
hdr=HdrRankModel(
dolby_vision=CustomRank(fetch=True),
),
audio=AudioRankModel(
mono=CustomRank(fetch=True),
mp3=CustomRank(fetch=True),
),
extras=ExtrasRankModel(
three_d=CustomRank(fetch=True),
converted=CustomRank(fetch=True),
documentary=CustomRank(fetch=True),
site=CustomRank(fetch=True),
upscaled=CustomRank(fetch=True),
),
)
rtn_settings_default = CometSettingsModel()
rtn_settings_default_dumped = rtn_settings_default.model_dump()
# {
# "profile":"default",
# "require":[
# ],
# "exclude":[
# ],
# "preferred":[
# ],
# "resolutions":{
# "r2160p":true,
# "r1080p":true,
# "r720p":true,
# "r480p":true,
# "r360p":true,
# "unknown":true
# },
# "options":{
# "title_similarity":0.85,
# "remove_all_trash":true,
# "remove_ranks_under":-10000000000,
# "remove_unknown_languages":false,
# "allow_english_in_languages":false,
# "enable_fetch_speed_mode":true,
# "remove_adult_content":true
# },
# "languages":{
# "required":[
# ],
# "exclude":[
# ],
# "preferred":[
# ]
# },
# "custom_ranks":{
# "quality":{
# "av1":{
# "fetch":true,
# "use_custom_rank":false,
# "rank":0
# },
# "avc":{
# "fetch":true,
# "use_custom_rank":false,
# "rank":0
# },
# "bluray":{
# "fetch":true,
# "use_custom_rank":false,
# "rank":0
# },
# "dvd":{
# "fetch":true,
# "use_custom_rank":false,
# "rank":0
# },
# "hdtv":{
# "fetch":true,
# "use_custom_rank":false,
# "rank":0
# },
# "hevc":{
# "fetch":true,
# "use_custom_rank":false,
# "rank":0
# },
# "mpeg":{
# "fetch":true,
# "use_custom_rank":false,
# "rank":0
# },
# "remux":{
# "fetch":true,
# "use_custom_rank":false,
# "rank":0
# },
# "vhs":{
# "fetch":true,
# "use_custom_rank":false,
# "rank":0
# },
# "web":{
# "fetch":true,
# "use_custom_rank":false,
# "rank":0
# },
# "webdl":{
# "fetch":true,
# "use_custom_rank":false,
# "rank":0
# },
# "webmux":{
# "fetch":true,
# "use_custom_rank":false,
# "rank":0
# },
# "xvid":{
# "fetch":true,
# "use_custom_rank":false,
# "rank":0
# }
# },
# "rips":{
# "bdrip":{
# "fetch":true,
# "use_custom_rank":false,
# "rank":0
# },
# "brrip":{
# "fetch":true,
# "use_custom_rank":false,
# "rank":0
# },
# "dvdrip":{
# "fetch":true,
# "use_custom_rank":false,
# "rank":0
# },
# "hdrip":{
# "fetch":true,
# "use_custom_rank":false,
# "rank":0
# },
# "ppvrip":{
# "fetch":true,
# "use_custom_rank":false,
# "rank":0
# },
# "satrip":{
# "fetch":true,
# "use_custom_rank":false,
# "rank":0
# },
# "tvrip":{
# "fetch":true,
# "use_custom_rank":false,
# "rank":0
# },
# "uhdrip":{
# "fetch":true,
# "use_custom_rank":false,
# "rank":0
# },
# "vhsrip":{
# "fetch":true,
# "use_custom_rank":false,
# "rank":0
# },
# "webdlrip":{
# "fetch":true,
# "use_custom_rank":false,
# "rank":0
# },
# "webrip":{
# "fetch":true,
# "use_custom_rank":false,
# "rank":0
# }
# },
# "hdr":{
# "bit10":{
# "fetch":true,
# "use_custom_rank":false,
# "rank":0
# },
# "dolby_vision":{
# "fetch":true,
# "use_custom_rank":false,
# "rank":0
# },
# "hdr":{
# "fetch":true,
# "use_custom_rank":false,
# "rank":0
# },
# "hdr10plus":{
# "fetch":true,
# "use_custom_rank":false,
# "rank":0
# },
# "sdr":{
# "fetch":true,
# "use_custom_rank":false,
# "rank":0
# }
# },
# "audio":{
# "aac":{
# "fetch":true,
# "use_custom_rank":false,
# "rank":0
# },
# "ac3":{
# "fetch":true,
# "use_custom_rank":false,
# "rank":0
# },
# "atmos":{
# "fetch":true,
# "use_custom_rank":false,
# "rank":0
# },
# "dolby_digital":{
# "fetch":true,
# "use_custom_rank":false,
# "rank":0
# },
# "dolby_digital_plus":{
# "fetch":true,
# "use_custom_rank":false,
# "rank":0
# },
# "dts_lossy":{
# "fetch":true,
# "use_custom_rank":false,
# "rank":0
# },
# "dts_lossless":{
# "fetch":true,
# "use_custom_rank":false,
# "rank":0
# },
# "eac3":{
# "fetch":true,
# "use_custom_rank":false,
# "rank":0
# },
# "flac":{
# "fetch":true,
# "use_custom_rank":false,
# "rank":0
# },
# "mono":{
# "fetch":true,
# "use_custom_rank":false,
# "rank":0
# },
# "mp3":{
# "fetch":true,
# "use_custom_rank":false,
# "rank":0
# },
# "stereo":{
# "fetch":true,
# "use_custom_rank":false,
# "rank":0
# },
# "surround":{
# "fetch":true,
# "use_custom_rank":false,
# "rank":0
# },
# "truehd":{
# "fetch":true,
# "use_custom_rank":false,
# "rank":0
# }
# },
# "extras":{
# "three_d":{
# "fetch":true,
# "use_custom_rank":false,
# "rank":0
# },
# "converted":{
# "fetch":true,
# "use_custom_rank":false,
# "rank":0
# },
# "documentary":{
# "fetch":true,
# "use_custom_rank":false,
# "rank":0
# },
# "dubbed":{
# "fetch":true,
# "use_custom_rank":false,
# "rank":0
# },
# "edition":{
# "fetch":true,
# "use_custom_rank":false,
# "rank":0
# },
# "hardcoded":{
# "fetch":true,
# "use_custom_rank":false,
# "rank":0
# },
# "network":{
# "fetch":true,
# "use_custom_rank":false,
# "rank":0
# },
# "proper":{
# "fetch":true,
# "use_custom_rank":false,
# "rank":0
# },
# "repack":{
# "fetch":true,
# "use_custom_rank":false,
# "rank":0
# },
# "retail":{
# "fetch":true,
# "use_custom_rank":false,
# "rank":0
# },
# "site":{
# "fetch":true,
# "use_custom_rank":false,
# "rank":0
# },
# "subbed":{
# "fetch":true,
# "use_custom_rank":false,
# "rank":0
# },
# "upscaled":{
# "fetch":true,
# "use_custom_rank":false,
# "rank":0
# },
# "scene":{
# "fetch":true,
# "use_custom_rank":false,
# "rank":0
# }
# },
# "trash":{
# "cam":{
# "fetch":false,
# "use_custom_rank":false,
# "rank":0
# },
# "clean_audio":{
# "fetch":false,
# "use_custom_rank":false,
# "rank":0
# },
# "pdtv":{
# "fetch":false,
# "use_custom_rank":false,
# "rank":0
# },
# "r5":{
# "fetch":false,
# "use_custom_rank":false,
# "rank":0
# },
# "screener":{
# "fetch":false,
# "use_custom_rank":false,
# "rank":0
# },
# "size":{
# "fetch":false,
# "use_custom_rank":false,
# "rank":0
# },
# "telecine":{
# "fetch":false,
# "use_custom_rank":false,
# "rank":0
# },
# "telesync":{
# "fetch":false,
# "use_custom_rank":false,
# "rank":0
# }
# }
# }
# }
rtn_ranking_default = DefaultRanking()
class ConfigModel(BaseModel):
cachedOnly: Optional[bool] = False
sortCachedUncachedTogether: Optional[bool] = False
removeTrash: Optional[bool] = True
resultFormat: Optional[List[str]] = ["all"]
maxResultsPerResolution: Optional[int] = 0
maxSize: Optional[float] = 0
debridService: Optional[str] = "torrent"
debridApiKey: Optional[str] = ""
debridStreamProxyPassword: Optional[str] = ""
languages: Optional[dict] = rtn_settings_default_dumped["languages"]
resolutions: Optional[dict] = rtn_settings_default_dumped["resolutions"]
options: Optional[dict] = rtn_settings_default_dumped["options"]
rtnSettings: Optional[CometSettingsModel] = rtn_settings_default
rtnRanking: Optional[DefaultRanking] = rtn_ranking_default
@field_validator("maxResultsPerResolution")
def check_max_results_per_resolution(cls, v):
if not isinstance(v, int):
v = 0
if v < 0:
v = 0
return v
@field_validator("maxSize")
def check_max_size(cls, v):
if not isinstance(v, float):
v = 0
if v < 0:
v = 0
return v
@field_validator("debridService")
def check_debrid_service(cls, v):
if v not in [
"realdebrid",
"alldebrid",
"premiumize",
"torbox",
"debrider",
"easydebrid",
"debridlink",
"offcloud",
"pikpak",
"torrent",
]:
raise ValueError("Invalid debridService")
return v
default_config = ConfigModel().model_dump()
default_config["rtnSettings"] = rtn_settings_default
default_config["rtnRanking"] = rtn_ranking_default
web_config = {
"resolutions": [resolution.value for resolution in RTN.extras.Resolution],
"resultFormat": [
"title",
"video_info",
"audio_info",
"quality_info",
"release_group",
"seeders",
"size",
"tracker",
"languages",
],
}
def _build_database_instance(raw_url: str):
driver = "sqlite" if settings.DATABASE_TYPE == "sqlite" else "postgresql+asyncpg"
prefix = "/" if settings.DATABASE_TYPE == "sqlite" else ""
return Database(f"{driver}://{prefix}{raw_url}")
database_url = (
settings.DATABASE_PATH
if settings.DATABASE_TYPE == "sqlite"
else settings.DATABASE_URL
)
replica_instances: List[Database] = []
if settings.DATABASE_TYPE != "sqlite" and settings.DATABASE_READ_REPLICA_URLS:
for replica_url in settings.DATABASE_READ_REPLICA_URLS:
if replica_url:
replica_instances.append(_build_database_instance(replica_url))
elif settings.DATABASE_TYPE == "sqlite" and settings.DATABASE_READ_REPLICA_URLS:
logger.log("DATABASE", "Read replicas are ignored for sqlite deployments")
database = ReplicaAwareDatabase(
_build_database_instance(database_url), replicas=replica_instances
)
trackers = [
"udp://tracker-udp.gbitt.info:80/announce",
"udp://tracker.0x7c0.com:6969/announce",
"udp://opentracker.io:6969/announce",
"udp://leet-tracker.moe:1337/announce",
"udp://tracker.torrent.eu.org:451/announce",
"udp://tracker.tiny-vps.com:6969/announce",
"udp://tracker.leechers-paradise.org:6969/announce",
"udp://tracker.pomf.se:80/announce",
"udp://9.rarbg.me:2710/announce",
"http://tracker.gbitt.info:80/announce",
"udp://tracker.bittor.pw:1337/announce",
"udp://open.free-tracker.ga:6969/announce",
"udp://open.stealth.si:80/announce",
"udp://retracker01-msk-virt.corbina.net:80/announce",
"udp://tracker.openbittorrent.com:80/announce",
"udp://tracker.opentrackr.org:1337/announce",
"udp://isk.richardsw.club:6969/announce",
"https://tracker.gbitt.info:443/announce",
"udp://tracker.coppersurfer.tk:6969/announce",
"udp://oh.fuuuuuck.com:6969/announce",
"udp://ipv4.tracker.harry.lu:80/announce",
"udp://open.demonii.com:1337/announce",
"https://tracker.tamersunion.org:443/announce",
"https://tracker.renfei.net:443/announce",
"udp://open.tracker.cl:1337/announce",
"udp://tracker.internetwarriors.net:1337/announce",
"udp://exodus.desync.com:6969/announce",
"udp://tracker.dump.cl:6969/announce",
]