\n `}getFilterConfig(e){return{include:{label:"Include",placeholder:"Text that should be included in filename",badgeClass:"badge-primary"},exclude:{label:"Exclude",placeholder:"Text that should not be in filename",badgeClass:"badge-error"},regex:{label:"Regex Match",placeholder:"Regular expression pattern",badgeClass:"badge-warning"},not_regex:{label:"Regex Not Match",placeholder:"Regular expression pattern that should not match",badgeClass:"badge-error"},exact_match:{label:"Exact Match",placeholder:"Exact text to match",badgeClass:"badge-primary"},not_exact_match:{label:"Not Exact Match",placeholder:"Exact text that should not match",badgeClass:"badge-error"},starts_with:{label:"Starts With",placeholder:"Text that filename starts with",badgeClass:"badge-primary"},not_starts_with:{label:"Not Starts With",placeholder:"Text that filename should not start with",badgeClass:"badge-error"},ends_with:{label:"Ends With",placeholder:"Text that filename ends with",badgeClass:"badge-primary"},not_ends_with:{label:"Not Ends With",placeholder:"Text that filename should not end with",badgeClass:"badge-error"},size_gt:{label:"Size Greater Than",placeholder:"Size in bytes, KB, MB, GB (e.g. 700MB)",badgeClass:"badge-success"},size_lt:{label:"Size Less Than",placeholder:"Size in bytes, KB, MB, GB (e.g. 700MB)",badgeClass:"badge-warning"},last_added:{label:"Added in the last",placeholder:"Time duration (e.g. 24h, 7d, 30d)",badgeClass:"badge-info"}}[e]||{label:e.replace(/_/g," ").replace(/\b\w/g,e=>e.toUpperCase()),placeholder:"Filter value",badgeClass:"badge-ghost"}}showFilterHelp(){const e=document.createElement("dialog");e.className="modal",e.innerHTML='\n
\n
\n \n
\n
Directory Filter Types
\n
\n
\n
Text Filters
\n
\n
Include/Exclude: Simple text inclusion/exclusion
\n
Starts/Ends With: Matches beginning or end of filename
\n
Exact Match: Match the entire filename
\n
\n
\n
\n
Regex Filters
\n
\n
Regex: Use regular expressions for complex patterns
\n
Example: .*\\.mkv$ matches files ending with .mkv
\n
\n
\n
\n
Size Filters
\n
\n
Size Greater/Less Than: Filter by file size
\n
Examples: 1GB, 500MB, 2.5GB
\n
\n
\n
\n
Time Filters
\n
\n
Last Added: Show only recently added content
\n
Examples: 24h, 7d, 30d
\n
\n
\n
\n \n Negative filters (Not...) will exclude matches instead of including them.\n
\n `}async saveConfiguration(e){e.preventDefault(),this.refs.loadingOverlay.classList.remove("hidden");try{const e=this.collectFormData(),n=this.validateConfiguration(e);if(!n.valid)throw new Error(n.errors.join("\n"));const t=await window.decypharrUtils.fetcher("/api/config",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)});if(!t.ok){const e=await t.text();throw new Error(e||"Failed to save configuration")}window.decypharrUtils.createToast("Configuration saved successfully! Services are restarting...","success"),setTimeout(()=>{window.location.reload()},2e3)}catch(e){console.error("Error saving configuration:",e),window.decypharrUtils.createToast(`Error saving configuration: ${e.message}`,"error"),this.refs.loadingOverlay.classList.add("hidden")}}validateConfiguration(e){const n=[];return e.debrids.forEach((e,t)=>{e.name&&e.api_key&&e.folder||n.push(`Debrid service #${t+1}: Name, API key, and folder are required`)}),e.arrs.forEach((e,t)=>{e.name&&e.host||n.push(`Arr service #${t+1}: Name and host are required`),e.host&&!this.isValidUrl(e.host)&&n.push(`Arr service #${t+1}: Invalid host URL format`)}),e.usenet&&e.usenet.providers.forEach((e,t)=>{e.host||n.push(`Usenet server #${t+1}: Host is required`),e.port&&(e.port<1||e.port>65535)&&n.push(`Usenet server #${t+1}: Port must be between 1 and 65535`),e.connections&&e.connections<1&&n.push(`Usenet server #${t+1}: Connections must be more than 0`)}),e.repair.enabled&&(e.repair.interval||n.push("Repair interval is required when repair is enabled"),e.repair.workers&&(e.repair.workers<1||e.repair.workers>50)&&n.push("Repair workers must be between 1 and 50")),{valid:0===n.length,errors:n}}isValidUrl(e){try{return new URL(e),!0}catch(e){return!1}}collectFormData(){return{log_level:document.getElementById("log-level").value,url_base:document.getElementById("urlBase").value,bind_address:document.getElementById("bindAddress").value,port:document.getElementById("port").value?document.getElementById("port").value:null,discord_webhook_url:document.getElementById("discordWebhookUrl").value,allowed_file_types:document.getElementById("allowedExtensions").value.split(",").map(e=>e.trim()).filter(Boolean),min_file_size:document.getElementById("minFileSize").value,max_file_size:document.getElementById("maxFileSize").value,remove_stalled_after:document.getElementById("removeStalledAfter").value,debrids:this.collectDebridConfigs(),qbittorrent:this.collectQBittorrentConfig(),arrs:this.collectArrConfigs(),usenet:this.collectUsenetConfig(),sabnzbd:this.collectSABnzbdConfig(),repair:this.collectRepairConfig()}}collectDebridConfigs(){const e=[];for(let n=0;ne.trim()).filter(e=>e.length>0)),a.use_webdav){a.torrents_refresh_interval=document.querySelector(`[name="debrid[${n}].torrents_refresh_interval"]`).value,a.download_links_refresh_interval=document.querySelector(`[name="debrid[${n}].download_links_refresh_interval"]`).value,a.auto_expire_links_after=document.querySelector(`[name="debrid[${n}].auto_expire_links_after"]`).value,a.folder_naming=document.querySelector(`[name="debrid[${n}].folder_naming"]`).value,a.workers=parseInt(document.querySelector(`[name="debrid[${n}].workers"]`).value),a.rc_url=document.querySelector(`[name="debrid[${n}].rc_url"]`).value,a.rc_user=document.querySelector(`[name="debrid[${n}].rc_user"]`).value,a.rc_pass=document.querySelector(`[name="debrid[${n}].rc_pass"]`).value,a.rc_refresh_dirs=document.querySelector(`[name="debrid[${n}].rc_refresh_dirs"]`).value,a.serve_from_rclone=document.querySelector(`[name="debrid[${n}].serve_from_rclone"]`).checked,a.directories={};const e=this.debridDirectoryCounts[n]||0;for(let t=0;t0&&this.populateUsenetData(this.usenetProviderCount,e),this.usenetProviderCount++}populateUsenetData(e,n){Object.entries(n).forEach(([n,t])=>{const a=document.querySelector(`[name="usenet[${e}].${n}"]`);a&&("checkbox"===a.type?a.checked=t:a.value=t)})}getUsenetTemplate(e,n={}){return`\n
\n
\n
\n
\n \n Usenet Server #${e+1}\n
\n \n
\n \n
\n
\n \n \n
\n Usenet Name\n
\n
\n
\n \n \n
\n Usenet server hostname\n
\n
\n\n
\n \n \n
\n Server port (119 for standard, 563 for SSL)\n
\n
\n
\n \n \n
\n Maximum simultaneous connections\n
\n
\n
\n \n \n
\n Username for authentication\n
\n
\n\n
\n \n
\n \n \n
\n
\n Password for authentication\n
\n
\n
\n\n
\n
\n \n
\n Use SSL encryption\n
\n
\n\n
\n \n
\n Use TLS encryption\n
\n
\n
\n
\n
\n `}populateSABnzbdSettings(e){if(!e)return;["download_folder","refresh_interval"].forEach(n=>{const t=document.querySelector(`[name="sabnzbd.${n}"]`);t&&void 0!==e[n]&&("checkbox"===t.type?t.checked=e[n]:t.value=e[n])});const n=document.querySelector('[name="sabnzbd.categories"]');n&&e.categories&&(n.value=e.categories.join(", "))}collectUsenetConfig(){const e=[];for(let n=0;ne.trim()).filter(Boolean)}}collectRepairConfig(){return{enabled:document.querySelector('[name="repair.enabled"]').checked,interval:document.querySelector('[name="repair.interval"]').value,zurg_url:document.querySelector('[name="repair.zurg_url"]').value,strategy:document.querySelector('[name="repair.strategy"]').value,workers:parseInt(document.querySelector('[name="repair.workers"]').value)||1,use_webdav:document.querySelector('[name="repair.use_webdav"]').checked,auto_process:document.querySelector('[name="repair.auto_process"]').checked}}setupMagnetHandler(){if(window.registerMagnetLinkHandler=()=>{if("registerProtocolHandler"in navigator)try{navigator.registerProtocolHandler("magnet",`${window.location.origin}${window.urlBase}download?magnet=%s`,"Decypharr"),localStorage.setItem("magnetHandler","true");const e=document.getElementById("registerMagnetLink");e.innerHTML='Magnet Handler Registered',e.classList.remove("btn-primary"),e.classList.add("btn-success"),e.disabled=!0,window.decypharrUtils.createToast("Magnet link handler registered successfully")}catch(e){console.error("Failed to register magnet link handler:",e),window.decypharrUtils.createToast("Failed to register magnet link handler","error")}else window.decypharrUtils.createToast("Magnet link registration not supported in this browser","warning")},"true"===localStorage.getItem("magnetHandler")){const e=document.getElementById("registerMagnetLink");e&&(e.innerHTML='Magnet Handler Registered',e.classList.remove("btn-primary"),e.classList.add("btn-success"),e.disabled=!0)}}}