feat(torbox): Implement Payload Slicing and Auto-Cacher Toggle #2
5 changed files with 170 additions and 16 deletions
|
|
@ -169,15 +169,19 @@ class TorBoxService(BaseDebridService):
|
|||
if not links or not api_key:
|
||||
return links
|
||||
|
||||
links.sort(key=quality_sort_key)
|
||||
top_links = links[:15]
|
||||
rest_links = links[15:]
|
||||
|
||||
cache_timeout = config.get("stream_request_timeout", settings.STREAM_REQUEST_TIMEOUT)
|
||||
check_endpoint = "usenet/checkcached" if download_type == "usenet" else "webdl/checkcached"
|
||||
|
||||
cache_logger.debug(f"[TorBox] Checking {len(links)} links (timeout: {cache_timeout}s)")
|
||||
cache_logger.debug(f"[TorBox] Checking top {len(top_links)} links (timeout: {cache_timeout}s)")
|
||||
start_time = time.time()
|
||||
|
||||
link_to_hash = {}
|
||||
hashes = []
|
||||
for link_dict in links:
|
||||
for link_dict in top_links:
|
||||
link = link_dict.get("link")
|
||||
if link:
|
||||
if download_type == "usenet":
|
||||
|
|
@ -193,8 +197,10 @@ class TorBoxService(BaseDebridService):
|
|||
hashes.append(link_hash)
|
||||
|
||||
if not hashes:
|
||||
cache_logger.debug("[TorBox] No valid links")
|
||||
return links
|
||||
cache_logger.debug("[TorBox] No valid links to hash")
|
||||
for link_dict in rest_links:
|
||||
link_dict["cache_status"] = "uncached"
|
||||
return top_links + rest_links
|
||||
|
||||
http_error_count = 0
|
||||
|
||||
|
|
@ -216,25 +222,29 @@ class TorBoxService(BaseDebridService):
|
|||
|
||||
if response.status_code != 200:
|
||||
cache_logger.error(f"[TorBox] Batch failed: HTTP {response.status_code}")
|
||||
for link_dict in links:
|
||||
for link_dict in top_links:
|
||||
link_dict["cache_status"] = "uncached"
|
||||
return links
|
||||
for link_dict in rest_links:
|
||||
link_dict["cache_status"] = "uncached"
|
||||
return top_links + rest_links
|
||||
|
||||
response_json = response.json()
|
||||
|
||||
error_code = response_json.get("error")
|
||||
cooldown_result, http_error_count = await self._handle_cooldown_limit(error_code, http_error_count)
|
||||
if cooldown_result == "RETRY_ERROR":
|
||||
for link_dict in links:
|
||||
for link_dict in top_links:
|
||||
link_dict["cache_status"] = "uncached"
|
||||
return links
|
||||
for link_dict in rest_links:
|
||||
link_dict["cache_status"] = "uncached"
|
||||
return top_links + rest_links
|
||||
elif cooldown_result == "RETRY":
|
||||
continue
|
||||
|
||||
http_error_count = 0
|
||||
|
||||
cache_data = response_json.get("data", {})
|
||||
for link_dict in links:
|
||||
for link_dict in top_links:
|
||||
link = link_dict.get("link")
|
||||
link_hash = link_to_hash.get(link)
|
||||
|
||||
|
|
@ -280,15 +290,18 @@ class TorBoxService(BaseDebridService):
|
|||
|
||||
except Exception as e:
|
||||
cache_logger.error(f"[TorBox] Batch error: {type(e).__name__}: {e}")
|
||||
for link_dict in links:
|
||||
for link_dict in top_links:
|
||||
link_dict["cache_status"] = "uncached"
|
||||
break
|
||||
|
||||
cached_count = sum(1 for r in links if r.get("cache_status") == "cached")
|
||||
elapsed = time.time() - start_time
|
||||
cache_logger.debug(f"[TorBox] Done in {elapsed:.1f}s: {cached_count} cached / {len(links) - cached_count} uncached")
|
||||
for link_dict in rest_links:
|
||||
link_dict["cache_status"] = "uncached"
|
||||
|
||||
return links
|
||||
cached_count = sum(1 for r in top_links if r.get("cache_status") == "cached")
|
||||
elapsed = time.time() - start_time
|
||||
cache_logger.debug(f"[TorBox] Done in {elapsed:.1f}s: {cached_count} cached / {len(top_links) - cached_count} uncached (skipped {len(rest_links)})")
|
||||
|
||||
return top_links + rest_links
|
||||
|
||||
async def check_cache_and_enrich(self, results: List[Dict], api_key: str, config: Dict, timeout_remaining: float, user_season: Optional[str] = None, user_episode: Optional[str] = None, user_hosts: Optional[List[str]] = None) -> List[Dict]:
|
||||
start_time = time.time()
|
||||
|
|
@ -484,6 +497,38 @@ class TorBoxService(BaseDebridService):
|
|||
debrid_logger.error(f"[TorBox] Failed after {settings.DEBRID_MAX_RETRIES} attempts")
|
||||
return "FATAL_ERROR", http_error_count
|
||||
|
||||
async def trigger_auto_download(self, link: str, api_key: str):
|
||||
if not api_key or not link:
|
||||
return
|
||||
|
||||
download_type = "usenet" if "/nzb/" in link else "webdl"
|
||||
create_endpoint = "usenet/createusenetdownload" if download_type == "usenet" else "webdl/createwebdownload"
|
||||
|
||||
cleaned_link = link
|
||||
if download_type == "webdl" and "&af=" in cleaned_link:
|
||||
cleaned_link = cleaned_link.split("&af=")[0]
|
||||
elif download_type == "usenet":
|
||||
nzb_id = link.split("/")[-1]
|
||||
cleaned_link = f"{settings.DARKI_API_URL}/nzb/{nzb_id}/download"
|
||||
|
||||
try:
|
||||
headers = self._get_headers(api_key)
|
||||
data_payload = {"link": cleaned_link}
|
||||
if download_type == "webdl":
|
||||
data_payload["add_only_if_cached"] = False
|
||||
|
||||
debrid_logger.info(f"[TorBox] Triggering auto-cacher for uncached link: {cleaned_link[:50]}...")
|
||||
|
||||
# Fire and forget without retries for background job
|
||||
await http_client.post(
|
||||
f"{self.API_URL}/{create_endpoint}",
|
||||
data=data_payload,
|
||||
headers=headers,
|
||||
timeout=settings.HTTP_TIMEOUT
|
||||
)
|
||||
except Exception as e:
|
||||
debrid_logger.error(f"[TorBox] Auto-cacher background trigger failed: {type(e).__name__}: {e}")
|
||||
|
||||
async def convert_link(self, link: str, api_key: str, season: Optional[str] = None, episode: Optional[str] = None, hoster: Optional[str] = None) -> Optional[str]:
|
||||
if not api_key:
|
||||
debrid_logger.error("[TorBox] Empty API key")
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ const translations = {
|
|||
cachedOnly: 'Cached links only',
|
||||
enableUsenet: 'Enable Usenet (TorBox only)',
|
||||
fullSeasonPacks: 'Include Full Season Packs (Usenet)',
|
||||
autoCache: 'Auto-cache best link (TorBox)',
|
||||
deduplicateResults: 'Deduplicate results',
|
||||
earlyStop: 'Early Stop',
|
||||
earlyStopIncludeNzb: 'Include NZB in count',
|
||||
|
|
@ -62,6 +63,7 @@ const translations = {
|
|||
tokenLabel: 'Token:',
|
||||
hostsInfo: 'Select specific hosts or leave empty for all',
|
||||
sourcesInfo: 'Select specific sources or leave empty for all',
|
||||
autoCacheInfo: 'Automatically triggers a background download for the best quality link if no streams are cached.',
|
||||
timeoutInfo: 'Maximum search time (default: 20s)',
|
||||
excludedInfo: 'Streams with these words will be filtered',
|
||||
deduplicateInfo: 'Hides duplicate results from different hosts',
|
||||
|
|
@ -179,6 +181,7 @@ const translations = {
|
|||
cachedOnly: 'Liens en cache uniquement',
|
||||
enableUsenet: 'Activer Usenet (TorBox uniquement)',
|
||||
fullSeasonPacks: 'Inclure les packs de saison (Usenet)',
|
||||
autoCache: 'Auto-cache le meilleur lien (TorBox)',
|
||||
deduplicateResults: 'Dédupliquer les résultats',
|
||||
earlyStop: 'Arrêt Anticipé',
|
||||
earlyStopIncludeNzb: 'Inclure NZB dans le comptage',
|
||||
|
|
@ -194,6 +197,7 @@ const translations = {
|
|||
tokenLabel: 'Token :',
|
||||
hostsInfo: 'Sélectionner des hébergeurs ou laisser vide pour tous',
|
||||
sourcesInfo: 'Sélectionner des sources ou laisser vide pour toutes',
|
||||
autoCacheInfo: 'Déclenche automatiquement le téléchargement en arrière-plan du meilleur lien si aucun stream n\'est en cache.',
|
||||
timeoutInfo: 'Temps de recherche maximum (défaut : 20s)',
|
||||
excludedInfo: 'Les streams avec ces mots seront filtrés',
|
||||
deduplicateInfo: 'Masque les résultats identiques provenant de différents hébergeurs',
|
||||
|
|
|
|||
|
|
@ -475,6 +475,22 @@ class StreamService:
|
|||
stream_logger.debug(f"Excluded {excluded_count} streams")
|
||||
streams = filtered_streams
|
||||
|
||||
torbox_config = next((s for s in get_debrid_services(config) if s.get("service") == "torbox"), None)
|
||||
if torbox_config and torbox_config.get("auto_cache", False):
|
||||
cached_count = sum(1 for s in streams if "⚡" in s["name"] and "TB" in s["name"])
|
||||
|
||||
if cached_count == 0 and len(streams) > 0:
|
||||
best_torbox_stream = next((s for s in streams if "TB" in s["name"]), None)
|
||||
if best_torbox_stream:
|
||||
token_data = decode_playback_token(best_torbox_stream["url"].split("/")[-2])
|
||||
link_to_download = token_data.get("l")
|
||||
|
||||
if link_to_download:
|
||||
asyncio.create_task(torbox_service.trigger_auto_download(
|
||||
link=link_to_download,
|
||||
api_key=torbox_config.get("api_key")
|
||||
))
|
||||
|
||||
return streams
|
||||
|
||||
async def _get_metadata(self, imdb_id: str, tmdb_api_token: str) -> Optional[Dict]:
|
||||
|
|
@ -1286,7 +1302,23 @@ class StreamService:
|
|||
excluded_count = len(streams) - len(filtered_streams)
|
||||
if excluded_count > 0:
|
||||
stream_logger.debug(f"Excluded {excluded_count} streams")
|
||||
return filtered_streams
|
||||
streams = filtered_streams
|
||||
|
||||
torbox_config = next((s for s in get_debrid_services(config) if s.get("service") == "torbox"), None)
|
||||
if torbox_config and torbox_config.get("auto_cache", False):
|
||||
cached_count = sum(1 for s in streams if "⚡" in s["name"] and "TB" in s["name"])
|
||||
|
||||
if cached_count == 0 and len(streams) > 0:
|
||||
best_torbox_stream = next((s for s in streams if "TB" in s["name"]), None)
|
||||
if best_torbox_stream:
|
||||
token_data = decode_playback_token(best_torbox_stream["url"].split("/")[-2])
|
||||
link_to_download = token_data.get("l")
|
||||
|
||||
if link_to_download:
|
||||
asyncio.create_task(torbox_service.trigger_auto_download(
|
||||
link=link_to_download,
|
||||
api_key=torbox_config.get("api_key")
|
||||
))
|
||||
|
||||
return streams
|
||||
|
||||
|
|
|
|||
|
|
@ -1768,6 +1768,22 @@
|
|||
</div>
|
||||
<input type="checkbox" class="service-enable-fullseason" style="display: none;" />
|
||||
</div>
|
||||
<div class="form-field service-autocache-toggle" style="display: none;">
|
||||
<div class="toggle-wrapper">
|
||||
<div class="toggle service-autocache-toggle-btn">
|
||||
<svg class="toggle-icon left" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<line x1="18" y1="6" x2="6" y2="18"></line>
|
||||
<line x1="6" y1="6" x2="18" y2="18"></line>
|
||||
</svg>
|
||||
<svg class="toggle-icon right" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M12 2v20M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<span class="toggle-label" data-i18n="toggles.autoCache">Auto-cache best link (TorBox)</span>
|
||||
</div>
|
||||
<input type="checkbox" class="service-enable-autocache" style="display: none;" />
|
||||
<span class="field-help" style="margin-top: 6px; display: block;" data-i18n="help.autoCacheInfo">Automatically triggers a background download for the best quality link if no streams are cached.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-field">
|
||||
|
|
@ -2322,6 +2338,7 @@
|
|||
const currentWebdavPassInput = entry.querySelector('.nzbdavWebdavPassInput');
|
||||
const currentNzbToggle = entry.querySelector('.service-nzb-toggle-btn');
|
||||
const currentFullseasonToggle = entry.querySelector('.service-fullseason-toggle-btn');
|
||||
const currentAutocacheToggle = entry.querySelector('.service-autocache-toggle-btn');
|
||||
|
||||
const targetSelect = targetEntry.querySelector('.debridServiceSelect');
|
||||
const targetInput = targetEntry.querySelector('.debridKeyInput');
|
||||
|
|
@ -2332,6 +2349,7 @@
|
|||
const targetWebdavPassInput = targetEntry.querySelector('.nzbdavWebdavPassInput');
|
||||
const targetNzbToggle = targetEntry.querySelector('.service-nzb-toggle-btn');
|
||||
const targetFullseasonToggle = targetEntry.querySelector('.service-fullseason-toggle-btn');
|
||||
const targetAutocacheToggle = targetEntry.querySelector('.service-autocache-toggle-btn');
|
||||
|
||||
const currentService = currentSelect.value;
|
||||
const currentApiKey = currentInput.value;
|
||||
|
|
@ -2342,6 +2360,7 @@
|
|||
const currentWebdavPass = currentWebdavPassInput ? currentWebdavPassInput.value : '';
|
||||
const currentNzbEnabled = currentNzbToggle ? currentNzbToggle.classList.contains('active') : false;
|
||||
const currentFullseasonEnabled = currentFullseasonToggle ? currentFullseasonToggle.classList.contains('active') : false;
|
||||
const currentAutocacheEnabled = currentAutocacheToggle ? currentAutocacheToggle.classList.contains('active') : false;
|
||||
|
||||
const targetService = targetSelect.value;
|
||||
const targetApiKey = targetInput.value;
|
||||
|
|
@ -2352,6 +2371,7 @@
|
|||
const targetWebdavPass = targetWebdavPassInput ? targetWebdavPassInput.value : '';
|
||||
const targetNzbEnabled = targetNzbToggle ? targetNzbToggle.classList.contains('active') : false;
|
||||
const targetFullseasonEnabled = targetFullseasonToggle ? targetFullseasonToggle.classList.contains('active') : false;
|
||||
const targetAutocacheEnabled = targetAutocacheToggle ? targetAutocacheToggle.classList.contains('active') : false;
|
||||
|
||||
currentSelect.value = targetService;
|
||||
currentInput.value = targetApiKey;
|
||||
|
|
@ -2387,6 +2407,17 @@
|
|||
if (targetFullseasonCheckbox) targetFullseasonCheckbox.checked = currentFullseasonEnabled;
|
||||
}
|
||||
|
||||
if (currentAutocacheToggle) {
|
||||
currentAutocacheToggle.classList.toggle('active', targetAutocacheEnabled);
|
||||
const currentAutocacheCheckbox = entry.querySelector('.service-enable-autocache');
|
||||
if (currentAutocacheCheckbox) currentAutocacheCheckbox.checked = targetAutocacheEnabled;
|
||||
}
|
||||
if (targetAutocacheToggle) {
|
||||
targetAutocacheToggle.classList.toggle('active', currentAutocacheEnabled);
|
||||
const targetAutocacheCheckbox = targetEntry.querySelector('.service-enable-autocache');
|
||||
if (targetAutocacheCheckbox) targetAutocacheCheckbox.checked = currentAutocacheEnabled;
|
||||
}
|
||||
|
||||
updateDebridLink(entry, targetService);
|
||||
updateDebridLink(targetEntry, currentService);
|
||||
|
||||
|
|
@ -2397,8 +2428,10 @@
|
|||
|
||||
const currentUsenetField = entry.querySelector('.service-usenet-toggle');
|
||||
const currentFullseasonField = entry.querySelector('.service-fullseason-toggle');
|
||||
const currentAutocacheField = entry.querySelector('.service-autocache-toggle');
|
||||
const targetUsenetField = targetEntry.querySelector('.service-usenet-toggle');
|
||||
const targetFullseasonField = targetEntry.querySelector('.service-fullseason-toggle');
|
||||
const targetAutocacheField = targetEntry.querySelector('.service-autocache-toggle');
|
||||
|
||||
if (currentUsenetField) {
|
||||
currentUsenetField.style.display = targetService === 'torbox' ? 'block' : 'none';
|
||||
|
|
@ -2413,6 +2446,9 @@
|
|||
currentFullseasonField.style.opacity = '';
|
||||
}
|
||||
}
|
||||
if (currentAutocacheField) {
|
||||
currentAutocacheField.style.display = targetService === 'torbox' ? 'block' : 'none';
|
||||
}
|
||||
if (targetUsenetField) {
|
||||
targetUsenetField.style.display = currentService === 'torbox' ? 'block' : 'none';
|
||||
}
|
||||
|
|
@ -2426,6 +2462,9 @@
|
|||
targetFullseasonField.style.opacity = '';
|
||||
}
|
||||
}
|
||||
if (targetAutocacheField) {
|
||||
targetAutocacheField.style.display = currentService === 'torbox' ? 'block' : 'none';
|
||||
}
|
||||
|
||||
updateServiceNumbers();
|
||||
checkWarningIndicator();
|
||||
|
|
@ -2780,7 +2819,7 @@
|
|||
}
|
||||
};
|
||||
|
||||
const addDebridServiceEntry = (service = 'alldebrid', apiKey = '', hosts = [], sources = [], nzbdavUrl = '', enableNzb = false, enableFullSeason = false, webdavUser = '', webdavPassword = '') => {
|
||||
const addDebridServiceEntry = (service = 'alldebrid', apiKey = '', hosts = [], sources = [], nzbdavUrl = '', enableNzb = false, enableFullSeason = false, autoCache = false, webdavUser = '', webdavPassword = '') => {
|
||||
const index = getDebridServiceCount();
|
||||
const trans = translations[state.currentLanguage];
|
||||
const label = trans.labels?.debridService || 'Debrid service';
|
||||
|
|
@ -2866,6 +2905,22 @@
|
|||
</div>
|
||||
<input type="checkbox" class="service-enable-fullseason" style="display: none;" ${enableFullSeason ? 'checked' : ''} />
|
||||
</div>
|
||||
<div class="form-field service-autocache-toggle" style="display: ${service === 'torbox' ? 'block' : 'none'};">
|
||||
<div class="toggle-wrapper">
|
||||
<div class="toggle service-autocache-toggle-btn ${autoCache ? 'active' : ''}">
|
||||
<svg class="toggle-icon left" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<line x1="18" y1="6" x2="6" y2="18"></line>
|
||||
<line x1="6" y1="6" x2="18" y2="18"></line>
|
||||
</svg>
|
||||
<svg class="toggle-icon right" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M12 2v20M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<span class="toggle-label" data-i18n="toggles.autoCache">${trans.toggles?.autoCache || 'Auto-cache best link (TorBox)'}</span>
|
||||
</div>
|
||||
<input type="checkbox" class="service-enable-autocache" style="display: none;" ${autoCache ? 'checked' : ''} />
|
||||
<span class="field-help" style="margin-top: 6px; display: block;" data-i18n="help.autoCacheInfo">${trans.help?.autoCacheInfo || 'Automatically triggers a background download for the best quality link if no streams are cached.'}</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
els.debridServicesContainer.appendChild(entry);
|
||||
|
|
@ -2878,10 +2933,13 @@
|
|||
const sourcesSelect = entry.querySelector('.sourcesSelect');
|
||||
const usenetToggleField = entry.querySelector('.service-usenet-toggle');
|
||||
const fullseasonToggleField = entry.querySelector('.service-fullseason-toggle');
|
||||
const autocacheToggleField = entry.querySelector('.service-autocache-toggle');
|
||||
const nzbToggleBtn = entry.querySelector('.service-nzb-toggle-btn');
|
||||
const fullseasonToggleBtn = entry.querySelector('.service-fullseason-toggle-btn');
|
||||
const autocacheToggleBtn = entry.querySelector('.service-autocache-toggle-btn');
|
||||
const enableNzbCheckbox = entry.querySelector('.service-enable-nzb');
|
||||
const enableFullseasonCheckbox = entry.querySelector('.service-enable-fullseason');
|
||||
const enableAutocacheCheckbox = entry.querySelector('.service-enable-autocache');
|
||||
|
||||
select.addEventListener('sl-change', (e) => {
|
||||
updateDebridLink(entry, e.target.value);
|
||||
|
|
@ -2890,6 +2948,7 @@
|
|||
if (selectedService === 'torbox') {
|
||||
usenetToggleField.style.display = 'block';
|
||||
fullseasonToggleField.style.display = 'block';
|
||||
autocacheToggleField.style.display = 'block';
|
||||
if (enableNzbCheckbox.checked) {
|
||||
fullseasonToggleField.style.pointerEvents = 'auto';
|
||||
fullseasonToggleField.style.opacity = '1';
|
||||
|
|
@ -2899,12 +2958,14 @@
|
|||
}
|
||||
} else if (selectedService === 'nzbdav') {
|
||||
usenetToggleField.style.display = 'none';
|
||||
autocacheToggleField.style.display = 'none';
|
||||
fullseasonToggleField.style.display = 'block';
|
||||
fullseasonToggleField.style.pointerEvents = 'auto';
|
||||
fullseasonToggleField.style.opacity = '1';
|
||||
} else {
|
||||
usenetToggleField.style.display = 'none';
|
||||
fullseasonToggleField.style.display = 'none';
|
||||
autocacheToggleField.style.display = 'none';
|
||||
}
|
||||
|
||||
checkWarningIndicator();
|
||||
|
|
@ -2938,6 +2999,13 @@
|
|||
});
|
||||
}
|
||||
|
||||
if (autocacheToggleField) {
|
||||
autocacheToggleField.addEventListener('click', () => {
|
||||
enableAutocacheCheckbox.checked = !enableAutocacheCheckbox.checked;
|
||||
autocacheToggleBtn.classList.toggle('active', enableAutocacheCheckbox.checked);
|
||||
});
|
||||
}
|
||||
|
||||
removeBtn.addEventListener('click', () => {
|
||||
entry.remove();
|
||||
updateServiceNumbers();
|
||||
|
|
@ -2988,6 +3056,7 @@
|
|||
const nzbdavWebdavPassInput = entry.querySelector('.nzbdavWebdavPassInput');
|
||||
const enableNzbCheckbox = entry.querySelector('.service-enable-nzb');
|
||||
const enableFullseasonCheckbox = entry.querySelector('.service-enable-fullseason');
|
||||
const enableAutocacheCheckbox = entry.querySelector('.service-enable-autocache');
|
||||
|
||||
if (select && input) {
|
||||
const service = select.value;
|
||||
|
|
@ -3016,6 +3085,7 @@
|
|||
if (service === 'torbox') {
|
||||
serviceEntry.enable_nzb = enableNzbCheckbox ? enableNzbCheckbox.checked : false;
|
||||
serviceEntry.enable_full_season = enableFullseasonCheckbox ? enableFullseasonCheckbox.checked : false;
|
||||
serviceEntry.auto_cache = enableAutocacheCheckbox ? enableAutocacheCheckbox.checked : false;
|
||||
}
|
||||
|
||||
if (service === 'nzbdav') {
|
||||
|
|
@ -3781,6 +3851,7 @@
|
|||
const webdavPasswordValue = serviceEntry.service === 'nzbdav' ? (config.webdav_password || '') : '';
|
||||
const enableNzb = serviceEntry.enable_nzb || false;
|
||||
const enableFullSeason = serviceEntry.enable_full_season || false;
|
||||
const autoCache = serviceEntry.auto_cache || false;
|
||||
addDebridServiceEntry(
|
||||
serviceEntry.service,
|
||||
serviceEntry.api_key,
|
||||
|
|
@ -3789,6 +3860,7 @@
|
|||
nzbdavUrlValue,
|
||||
enableNzb,
|
||||
enableFullSeason,
|
||||
autoCache,
|
||||
webdavUserValue,
|
||||
webdavPasswordValue
|
||||
);
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ class DebridServiceEntry(BaseModel):
|
|||
sources: List[str] = []
|
||||
enable_nzb: bool = False
|
||||
enable_full_season: bool = False
|
||||
auto_cache: bool = False
|
||||
|
||||
@field_validator("service")
|
||||
@classmethod
|
||||
|
|
|
|||
Loading…
Reference in a new issue