wetvlo 0.0.25 → 0.0.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -10,9 +10,9 @@ ${y}`)}}static async checkInstalled(){try{return await Me("yt-dlp",["--version"]
10
10
  ... (truncated)`);let s=this.escapeHtml(r),l=`${o} <b>wetvlo ${this.getLevelLabel(e)}</b>
11
11
 
12
12
  <pre>${s}</pre>`,c=await fetch(this.apiUrl,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({chat_id:this.config.chatId,text:l,parse_mode:"HTML"})});if(!c.ok){let u=await c.text();throw new oe(`Failed to send Telegram notification: ${c.status} ${c.statusText}
13
- ${u}`)}}catch(o){console.error("Telegram notification failed:",o)}}escapeHtml(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}getEmoji(e){switch(e){case a.DEBUG:return"\u{1F50D}";case a.INFO:return"\u2139\uFE0F";case a.SUCCESS:return"\u2705";case a.WARNING:return"\u26A0\uFE0F";case a.ERROR:return"\u274C";case a.HIGHLIGHT:return"\u{1F514}";default:return""}}getLevelLabel(e){switch(e){case a.DEBUG:return"Debug";case a.INFO:return"Info";case a.SUCCESS:return"Success";case a.WARNING:return"Warning";case a.ERROR:return"Error";case a.HIGHLIGHT:return"Notification";default:return"Message"}}async progress(e){}async endProgress(){}};import{createHash as Et}from"crypto";import{existsSync as wt}from"fs";import{readFile as vt,writeFile as bt}from"fs/promises";import"url";import{chromium as Fe,firefox as xt,webkit as Ct}from"playwright";var G=class n{static instance;browser=null;context=null;cleanupTimer=null;INACTIVITY_TIMEOUT=900*1e3;refreshInProgress=null;pendingRefreshes=[];constructor(){}static getInstance(){return n.instance||(n.instance=new n),n.instance}async refreshCookies(e){return this.refreshInProgress?new Promise((t,o)=>{this.pendingRefreshes.push({seriesUrl:e,resolve:t,reject:o})}):(this.refreshInProgress=(async()=>{try{await this.performRefresh(e),this.resetCleanupTimer()}finally{this.refreshInProgress=null,await this.processPendingRefreshes()}})(),this.refreshInProgress)}async shutdown(){await this.closeBrowser()}async reinitialize(){f.getNotifier().notify(a.DEBUG,"Reinitializing cookie refresh browser due to config reload"),await this.closeBrowser()}async performRefresh(e){let t=f.getConfig(),o=f.getNotifier(),i=t.resolve(e,"series"),r=i.cookieFile,s=i.cookieRefreshBrowser;if(!r){o.notify(a.DEBUG,"No cookieFile configured");return}if(!s){o.notify(a.DEBUG,"No cookieRefreshBrowser configured");return}let l=i.playwrightHeadless??!0;if(this.browser||await this.initializeBrowser(s,l,o),!this.context)throw new Error("Browser context not initialized");await this.loadCookiesIntoContext(this.context,r,o);let c=await this.context.newPage();try{await c.goto(e,{waitUntil:"domcontentloaded",timeout:3e4}),await c.waitForLoadState("networkidle",{timeout:15e3}).catch(()=>{}),await this.saveContextCookies(this.context,r,o)}finally{await c.close()}}async processPendingRefreshes(){if(this.pendingRefreshes.length===0)return;f.getNotifier().notify(a.DEBUG,`Processing ${this.pendingRefreshes.length} pending cookie refresh requests`);let t=this.pendingRefreshes.splice(0);for(let o of t)try{await this.performRefresh(o.seriesUrl),this.resetCleanupTimer(),o.resolve()}catch(i){let r=i instanceof Error?i:new Error(String(i));o.reject(r)}}async initializeBrowser(e,t,o){let i=this.mapBrowserToPlaywright(e);o.notify(a.INFO,`Launching Playwright browser (${e}) for cookie refresh...`),this.browser=await i.launch({headless:t}),this.context=await this.browser.newContext(),o.notify(a.SUCCESS,"Playwright browser launched for cookie refresh")}async closeBrowser(){this.cleanupTimer&&(clearTimeout(this.cleanupTimer),this.cleanupTimer=null),this.context&&(await this.context.close(),this.context=null),this.browser&&(await this.browser.close(),this.browser=null),f.getNotifier().notify(a.INFO,"Playwright browser closed for cookie refresh")}resetCleanupTimer(){this.cleanupTimer&&clearTimeout(this.cleanupTimer),this.cleanupTimer=setTimeout(async()=>{await this.closeBrowser()},this.INACTIVITY_TIMEOUT)}async loadCookiesIntoContext(e,t,o){if(!wt(t)){o.notify(a.WARNING,`Cookie file not found at ${t}, skipping import`);return}let i=await vt(t,"utf-8"),r=this.parseNetscapeCookies(i),s=this.toPlaywrightCookies(r);s.length>0&&(await e.addCookies(s),o.notify(a.INFO,`Imported ${s.length} cookies into Playwright context`))}async saveContextCookies(e,t,o){let i=await e.cookies(),r=this.serializeNetscapeCookies(i);await bt(t,r,"utf-8"),o.notify(a.SUCCESS,`Saved ${i.length} cookies to ${t}`)}parseNetscapeCookies(e){let t=e.split(`
13
+ ${u}`)}}catch(o){console.error("Telegram notification failed:",o)}}escapeHtml(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}getEmoji(e){switch(e){case a.DEBUG:return"\u{1F50D}";case a.INFO:return"\u2139\uFE0F";case a.SUCCESS:return"\u2705";case a.WARNING:return"\u26A0\uFE0F";case a.ERROR:return"\u274C";case a.HIGHLIGHT:return"\u{1F514}";default:return""}}getLevelLabel(e){switch(e){case a.DEBUG:return"Debug";case a.INFO:return"Info";case a.SUCCESS:return"Success";case a.WARNING:return"Warning";case a.ERROR:return"Error";case a.HIGHLIGHT:return"Notification";default:return"Message"}}async progress(e){}async endProgress(){}};import{createHash as Et}from"crypto";import{existsSync as wt}from"fs";import{readFile as vt,writeFile as bt}from"fs/promises";import{chromium as Fe,firefox as xt,webkit as Ct}from"playwright";var G=class n{static instance;browser=null;context=null;cleanupTimer=null;INACTIVITY_TIMEOUT=900*1e3;refreshInProgress=null;pendingRefreshes=[];constructor(){}static getInstance(){return n.instance||(n.instance=new n),n.instance}async refreshCookies(e){return this.refreshInProgress?new Promise((t,o)=>{this.pendingRefreshes.push({seriesUrl:e,resolve:t,reject:o})}):(this.refreshInProgress=(async()=>{try{await this.performRefresh(e),this.resetCleanupTimer()}finally{this.refreshInProgress=null,await this.processPendingRefreshes()}})(),this.refreshInProgress)}async shutdown(){await this.closeBrowser()}async reinitialize(){f.getNotifier().notify(a.DEBUG,"Reinitializing cookie refresh browser due to config reload"),await this.closeBrowser()}async performRefresh(e){let t=f.getConfig(),o=f.getNotifier(),i=t.resolve(e,"series"),r=i.cookieFile,s=i.cookieRefreshBrowser;if(!r){o.notify(a.DEBUG,"No cookieFile configured");return}if(!s){o.notify(a.DEBUG,"No cookieRefreshBrowser configured");return}let l=i.playwrightHeadless??!0;if(this.browser||await this.initializeBrowser(s,l,o),!this.context)throw new Error("Browser context not initialized");await this.loadCookiesIntoContext(this.context,r,o);let c=await this.context.newPage();try{await c.goto(e,{waitUntil:"domcontentloaded",timeout:3e4}),await c.waitForLoadState("networkidle",{timeout:15e3}).catch(()=>{}),await this.saveContextCookies(this.context,r,o)}finally{await c.close()}}async processPendingRefreshes(){if(this.pendingRefreshes.length===0)return;f.getNotifier().notify(a.DEBUG,`Processing ${this.pendingRefreshes.length} pending cookie refresh requests`);let t=this.pendingRefreshes.splice(0);for(let o of t)try{await this.performRefresh(o.seriesUrl),this.resetCleanupTimer(),o.resolve()}catch(i){let r=i instanceof Error?i:new Error(String(i));o.reject(r)}}async initializeBrowser(e,t,o){let i=this.mapBrowserToPlaywright(e);o.notify(a.INFO,`Launching Playwright browser (${e}) for cookie refresh...`),this.browser=await i.launch({headless:t}),this.context=await this.browser.newContext(),o.notify(a.SUCCESS,"Playwright browser launched for cookie refresh")}async closeBrowser(){this.cleanupTimer&&(clearTimeout(this.cleanupTimer),this.cleanupTimer=null),this.context&&(await this.context.close(),this.context=null),this.browser&&(await this.browser.close(),this.browser=null),f.getNotifier().notify(a.INFO,"Playwright browser closed for cookie refresh")}resetCleanupTimer(){this.cleanupTimer&&clearTimeout(this.cleanupTimer),this.cleanupTimer=setTimeout(async()=>{await this.closeBrowser()},this.INACTIVITY_TIMEOUT)}async loadCookiesIntoContext(e,t,o){if(!wt(t)){o.notify(a.WARNING,`Cookie file not found at ${t}, skipping import`);return}let i=await vt(t,"utf-8"),r=this.parseNetscapeCookies(i),s=this.toPlaywrightCookies(r);s.length>0&&(await e.addCookies(s),o.notify(a.INFO,`Imported ${s.length} cookies into Playwright context`))}async saveContextCookies(e,t,o){let i=await e.cookies(),r=this.serializeNetscapeCookies(i);await bt(t,r,"utf-8"),o.notify(a.SUCCESS,`Saved ${i.length} cookies to ${t}`)}parseNetscapeCookies(e){let t=e.split(`
14
14
  `),o=[];for(let i of t){let r=i.trim();if(!r||r.startsWith("#"))continue;let s=r.split(" ");if(s.length<7)continue;let[l="",c="FALSE",u="/",g="FALSE",h="0",p="",w=""]=s;o.push({domain:l,includeSubdomains:c.toUpperCase()==="TRUE",path:u,secure:g.toUpperCase()==="TRUE",expiry:Number(h)||0,name:p,value:w})}return o}toPlaywrightCookies(e){return e.map(t=>({name:t.name,value:t.value,domain:t.domain,path:t.path||"/",expires:t.expiry||-1,httpOnly:!1,secure:t.secure,sameSite:"Lax"}))}serializeNetscapeCookies(e){let t=[];t.push("# Netscape HTTP Cookie File"),t.push("# This file is generated by Wetvlo via Playwright. Do not edit."),t.push("");for(let o of e){let i=o.domain||"",r=i.startsWith(".")?"TRUE":"FALSE",s=o.path||"/",l=o.secure?"TRUE":"FALSE",c=typeof o.expires=="number"?Math.max(0,Math.floor(o.expires)):0,u=o.name,g=o.value;t.push([i,r,s,l,String(c),u,g].join(" "))}return t.join(`
15
- `)}mapBrowserToPlaywright(e){switch((e||"chrome").toLowerCase()){case"chrome":case"chromium":case"edge":return Fe;case"firefox":return xt;case"safari":return Ct;default:return Fe}}};var de=class{queue=[];isExecuting=!1;nextAvailableAt=new Date(0);cooldownMs;constructor(e=0){this.cooldownMs=e}add(e,t){let o=new Date(Date.now()+(t??0));this.queue.push({data:e,addedAt:o})}addFirst(e,t){let o=new Date(Date.now()+(t??0));this.queue.unshift({data:e,addedAt:o})}getNext(){return this.queue.length===0?null:this.queue.shift()?.data??null}peekNext(){return this.queue.length===0?null:this.queue[0]?.data??null}canStart(e){if(this.isExecuting||e<this.nextAvailableAt)return!1;let t=this.queue[0];return!(t&&e<t.addedAt)}markStarted(){this.isExecuting=!0}markCompleted(e){this.isExecuting=!1,this.cooldownMs=e,this.nextAvailableAt=new Date(Date.now()+e)}markFailed(e){this.isExecuting=!1,this.cooldownMs=e,this.nextAvailableAt=new Date(Date.now()+e)}hasTasks(){return this.queue.length>0}getNextAvailableTime(){let e=this.nextAvailableAt,t=this.queue[0];return t&&t.addedAt>e&&(e=t.addedAt),e}getQueueLength(){return this.queue.length}getIsExecuting(){return this.isExecuting}getCooldownMs(){return this.cooldownMs}setCooldownMs(e){this.cooldownMs=e}clear(){this.queue=[]}reset(){this.queue=[],this.isExecuting=!1,this.nextAvailableAt=new Date(0)}getStatus(){let e=new Date;return{queueLength:this.queue.length,isExecuting:this.isExecuting,nextAvailableAt:this.nextAvailableAt,cooldownMs:this.cooldownMs,canStartNow:this.canStart(e)}}};var ue=class{queues=new Map;queueCooldowns=new Map;executorBusy=!1;timerId=null;roundRobinIndex=0;stopped=!1;executor;onWait;constructor(e){this.executor=e}setOnWait(e){this.onWait=e}registerQueue(e,t){if(this.queues.has(e))throw new Error(`Queue ${e} is already registered`);let o=new de(t);this.queues.set(e,o),this.queueCooldowns.set(e,t)}hasQueue(e){return this.queues.has(e)}unregisterQueue(e){this.queues.delete(e),this.queueCooldowns.delete(e)}clearQueues(){for(let e of this.queues.values())e.clear();this.clearTimer(),this.roundRobinIndex=0}resetQueues(){for(let e of this.queues.values())e.reset();this.clearTimer(),this.roundRobinIndex=0}addTask(e,t,o){let i=this.queues.get(e);if(!i)throw new Error(`Queue ${e} is not registered`);i.add(t,o),this.stopped||this.scheduleNext()}addPriorityTask(e,t,o){let i=this.queues.get(e);if(!i)throw new Error(`Queue ${e} is not registered`);i.addFirst(t,o),this.stopped||this.scheduleNext()}markTaskComplete(e,t){let o=this.queues.get(e);if(!o)throw new Error(`Queue ${e} is not registered`);let i=t??this.queueCooldowns.get(e)??0;o.markCompleted(i),this.executorBusy=!1,this.stopped||this.scheduleNext()}markTaskFailed(e,t){let o=this.queues.get(e);if(!o)throw new Error(`Queue ${e} is not registered`);let i=t??this.queueCooldowns.get(e)??0;o.markFailed(i),this.executorBusy=!1,this.stopped||this.scheduleNext()}scheduleNext(){if(this.stopped||(this.clearTimer(),this.trySchedule())||this.executorBusy)return;let t=this.getEarliestAvailableTime();if(t){let o=Date.now(),i=Math.max(0,t.time.getTime()-o);this.scheduleTimer(i,t.queueName,t.time)}}trySchedule(){if(this.executorBusy)return!1;let e=new Date,t=Array.from(this.queues.keys());if(t.length===0)return!1;for(let o=0;o<t.length;o++){let i=(this.roundRobinIndex+o)%t.length,r=t[i];if(!r)continue;let s=this.queues.get(r);if(s&&s.hasTasks()&&s.canStart(e)){let l=s.getNext();if(l)return s.markStarted(),this.executorBusy=!0,this.roundRobinIndex=(i+1)%t.length,this.executeTask(r,l).catch(c=>{console.error(`[UniversalScheduler] Task execution failed: ${c}`),this.markTaskFailed(r)}),!0}}return!1}async executeTask(e,t){await this.executor(t,e)}scheduleTimer(e,t,o){this.clearTimer(),this.onWait&&e>1e3&&this.onWait(t,e,o),this.timerId=setTimeout(()=>{this.timerId=null,this.scheduleNext()},e)}clearTimer(){this.timerId!==null&&(clearTimeout(this.timerId),this.timerId=null)}getEarliestAvailableTime(){let e=null;for(let[t,o]of this.queues.entries()){if(!o.hasTasks())continue;let i=o.getNextAvailableTime();(e===null||i<e.time)&&(e={time:i,queueName:t})}return e}stop(){this.stopped=!0,this.clearTimer()}resume(){this.stopped=!1,this.scheduleNext()}getStats(){let e=new Map;for(let[t,o]of this.queues.entries()){let i=o.getStatus();e.set(t,{queueLength:i.queueLength,isExecuting:i.isExecuting,nextAvailableAt:i.nextAvailableAt})}return e}isExecutorBusy(){return this.executorBusy}hasPendingTasks(){for(let e of this.queues.values())if(e.hasTasks())return!0;return!1}getTotalPendingTasks(){let e=0;for(let t of this.queues.values())e+=t.getQueueLength();return e}};var pe=class{stateManager;downloadManager;scheduler;running=!1;domainHandlers=new Map;constructor(e,t){this.stateManager=f.getStateManager(),this.downloadManager=e;let o=t||(i=>new ue(i));this.scheduler=o(async(i,r)=>{await this.executeTask(i,r)}),this.scheduler.setOnWait((i,r)=>{let s=f.getNotifier(),l=Math.round(r/1e3),c=i.split(":"),u=c[0],g=c[1];u==="download"?s.notify(a.INFO,`[${g}] Next download: ${l}s`):u==="check"&&s.notify(a.INFO,`[${g}] Next check: ${l}s`)})}addSeriesCheck(e){let t=f.getConfig(),o=S(e),r=t.resolve(e,"series").name;this.registerDownloadQueue(o);let s=this.registerSeriesCheckQueue(o,e),l={seriesUrl:e,attemptNumber:1,retryCount:0};this.scheduler.addTask(s,l),f.getNotifier().notify(a.DEBUG,`[${o}] Added ${r} to check queue`)}addEpisodes(e,t){let o=f.getNotifier(),i=f.getConfig();if(t.length===0)return;let r=i.resolve(e,"series"),s=r.name,l=S(e);this.registerDownloadQueue(l);let{downloadDelay:c}=r.download;for(let u=0;u<t.length;u++){let g=t[u];if(!g)continue;let h={seriesUrl:e,episode:g,retryCount:0},p=`download:${l}`,w=u*c*1e3;this.scheduler.addTask(p,h,w)}o.notify(a.SUCCESS,`[${l}] ${s}: queued ${t.length} episodes`)}updateConfig(){f.getNotifier().notify(a.DEBUG,"[QueueManager] Configuration will be reloaded")}clearQueues(){this.scheduler.clearQueues(),f.getNotifier().notify(a.DEBUG,"[QueueManager] Cleared all queues")}resetQueues(){this.scheduler.resetQueues(),f.getNotifier().notify(a.DEBUG,"[QueueManager] Reset all queue states")}start(){if(this.running)throw new Error("QueueManager is already running");this.running=!0,this.scheduler.resume(),f.getNotifier().notify(a.DEBUG,"[QueueManager] Started queue processing")}async stop(){this.running&&(f.getNotifier().notify(a.DEBUG,"[QueueManager] Stopping queue processing..."),this.scheduler.stop(),this.running=!1,f.getNotifier().notify(a.DEBUG,"[QueueManager] Queue processing stopped"))}hasActiveProcessing(){return this.scheduler.isExecutorBusy()||this.scheduler.hasPendingTasks()}getQueueStats(){let e=this.scheduler.getStats(),t={},o={};for(let[i,r]of e.entries())if(i.startsWith("check:")){let l=i.split(":")[1];if(!l)continue;t[l]||(t[l]={length:0,processing:!1}),t[l].length+=r.queueLength,r.isExecuting&&(t[l].processing=!0)}else if(i.startsWith("download:")){let s=i.slice(9);o[s]={length:r.queueLength,processing:r.isExecuting}}return{checkQueues:t,downloadQueues:o}}registerDownloadQueue(e){let t=f.getConfig(),o=`download:${e}`;if(this.scheduler.hasQueue(o))return;let i=`https://${e}/`,r=t.resolve(i,"domain"),{downloadDelay:s}=r.download;this.scheduler.registerQueue(o,s*1e3)}registerSeriesCheckQueue(e,t){let o=f.getConfig(),i=Et("md5").update(t).digest("hex").substring(0,12),r=`check:${e}:${i}`;if(this.scheduler.hasQueue(r))return r;let s=o.resolve(t,"series"),{checkInterval:l}=s.check;if(this.scheduler.registerQueue(r,l*1e3),!this.domainHandlers.has(e)){let c=F.getHandlerOrThrow(`https://${e}/`);this.domainHandlers.set(e,c)}return r}async executeTask(e,t){let o=t.split(":"),i=o[0],r=o[1];if(!i||!r)throw new Error(`Invalid queue name format: ${t}`);if(i==="check")await this.executeCheck(e,r,t);else if(i==="download")await this.executeDownload(e,r,t);else throw new Error(`Unknown queue type: ${i}`)}async executeCheck(e,t,o){let i=f.getNotifier(),r=f.getConfig(),{seriesUrl:s,attemptNumber:l,retryCount:c=0}=e,u=this.domainHandlers.get(t);if(!u)throw new Error(`No handler found for domain ${t}`);let g=r.resolve(s,"series"),h=g.name,{count:p,checkInterval:w}=g.check;try{let d=await this.performCheck(u,s,g,l,t);if(d.hasNewEpisodes){if(i.notify(a.SUCCESS,`[${t}] ${h}: ${d.episodes.length} new episodes (attempt ${l}/${p})`),g.cookieRefreshBrowser)try{await G.getInstance().refreshCookies(s)}catch(m){let v=m instanceof Error?m.message:String(m);i.notify(a.ERROR,`[${t}] ${h}: cookie refresh failed - ${v}`)}this.addEpisodes(s,d.episodes),this.scheduler.markTaskComplete(o,w*1e3)}else if(l<p){let m=w*1e3,v=d.requeueDelay??m;i.notify(a.INFO,`[${t}] ${h}: no new episodes, retry in ${Math.round(v/1e3)}s (attempt ${l}/${p})`);let y={seriesUrl:s,attemptNumber:l+1,retryCount:0};this.scheduler.addTask(o,y,v),this.scheduler.markTaskComplete(o,w*1e3)}else i.notify(a.INFO,`[${t}] ${h}: exhausted after ${p} attempts`),this.scheduler.markTaskComplete(o,w*1e3)}catch(d){let m=d instanceof Error?d.message:String(d),{maxRetries:v,initialTimeout:y,backoffMultiplier:E,jitterPercentage:k}=g.download;if(c<v){let T=this.calculateBackoff(c,y*1e3,E,k);i.notify(a.WARNING,`[${t}] ${h}: check retry ${c+1}/${v} in ${Math.round(T/1e3)}s`);let N={seriesUrl:s,attemptNumber:l,retryCount:c+1};this.scheduler.addPriorityTask(o,N,T),this.scheduler.markTaskComplete(o,w*1e3)}else i.notify(a.ERROR,`[${t}] ${h}: check failed - ${m}`),this.scheduler.markTaskComplete(o,w*1e3)}}async executeDownload(e,t,o){let i=f.getNotifier(),r=f.getConfig(),{seriesUrl:s,episode:l,retryCount:c=0}=e,u=r.resolve(s,"series"),g=u.name,{downloadDelay:h}=u.download;try{await this.downloadManager.download(s,l),this.scheduler.markTaskComplete(o,h*1e3)}catch(p){let w=p instanceof Error?p.message:String(p),{maxRetries:d,initialTimeout:m,backoffMultiplier:v,jitterPercentage:y}=u.download;if(c<d){let E=this.calculateBackoff(c,m*1e3,v,y);i.notify(a.WARNING,`[${t}] ${g} - ${String(l.number).padStart(2,"0")}: retry ${c+1}/${d} in ${Math.round(E/1e3)}s`);let k={seriesUrl:s,episode:l,retryCount:c+1};this.scheduler.addPriorityTask(o,k,E),this.scheduler.markTaskComplete(o,h*1e3)}else c===0?(i.notify(a.ERROR,`[${t}] ${g} - ${String(l.number).padStart(2,"0")}: ${w}`),i.notify(a.INFO,`[${t}] Episode URL: ${l.url}`)):i.notify(a.ERROR,`[${t}] ${g} - ${String(l.number).padStart(2,"0")}: ${w}`),this.scheduler.markTaskComplete(o,h*1e3)}}async performCheck(e,t,o,i,r){let s=f.getNotifier(),l=o.name,c=o.check.count;s.notify(a.INFO,`[${r}] ${l}: checking (attempt ${i}/${c})`),f.getNotifier().notify(a.DEBUG,`[${r}] Checking URL: ${t}`);let u=await e.extractEpisodes(t),g=new Map;u.forEach(d=>{let m=g.get(d.type)||0;g.set(d.type,m+1)});let h=Array.from(g.entries()).map(([d,m])=>`${d}: ${m}`).join(", ");s.notify(a.INFO,`[${r}] ${l}: ${u.length} episodes (${h})`),u.forEach(d=>{s.notify(a.DEBUG,`[${r}] Episode ${d.number}: ${d.type} - ${d.url}${d.title?` (${d.title})`:""}`)});let{downloadTypes:p}=o.check,w=u.filter(d=>{let m=p.includes(d.type),v=o.stateFile,y=!this.stateManager.isDownloaded(v,l,d.number);return m&&y});if(u.length!==w.length){let d=u.length-w.length;s.notify(a.INFO,`[${r}] Filtering: ${w.length} to download, ${d} skipped`)}return w.length>0?{hasNewEpisodes:!0,episodes:w}:{hasNewEpisodes:!1,episodes:[],shouldRequeue:!0}}calculateBackoff(e,t,o,i){let r=t*o**e,s=r*i/100,l=(Math.random()*2-1)*s,c=Math.max(0,r+l);return Math.floor(c)}};import kt from"cron-parser";function Tt(n){let e=n.match(/^(\d{1,2}):(\d{2})$/);if(!e)throw new Error(`Invalid time format: "${n}". Expected HH:MM`);let[,t,o]=e,i=parseInt(t||"0",10),r=parseInt(o||"0",10);if(i<0||i>23)throw new Error(`Invalid hours: ${i}. Must be between 0 and 23`);if(r<0||r>59)throw new Error(`Invalid minutes: ${r}. Must be between 0 and 59`);let s=new Date;return s.setHours(i,r,0,0),s}function Oe(n){let e=Tt(n),t=new Date,o=new Date(t);return o.setHours(e.getHours(),e.getMinutes(),0,0),o<=t&&o.setDate(o.getDate()+1),o.getTime()-t.getTime()}function Ge(n){try{let t=kt.parse(n).next().toDate(),o=new Date;return t.getTime()-o.getTime()}catch(e){throw new Error(`Invalid cron expression: "${n}". Error: ${e instanceof Error?e.message:String(e)}`)}}function Be(n){return new Promise(e=>setTimeout(e,n))}var fe=class{configs;downloadManager;options;queueManager;running=!1;stopped=!0;timeProvider;scheduleTimer=null;constructor(e,t,o={mode:"scheduled"},i,r){this.configs=e,this.downloadManager=t,this.options=o,this.timeProvider=i||{getMsUntilTime:Oe,getMsUntilCron:Ge,sleep:Be};let s=r||(l=>new pe(l));this.queueManager=s(this.downloadManager)}async start(){if(this.running)throw new re("Scheduler is already running");this.running=!0,this.stopped=!1,this.queueManager.start();let e=f.getNotifier();if(this.options.mode==="once")f.getNotifier().notify(a.DEBUG,"Single-run mode: checking all series once"),await this.runOnce(),this.running=!1;else return e.notify(a.INFO,"Scheduler started"),this.scheduleNextBatch(),new Promise(t=>{let o=setInterval(()=>{this.running||(clearInterval(o),t())},100)})}scheduleNextBatch(){if(this.stopped)return;let e=f.getNotifier(),t=this.groupConfigsBySchedule(),o=null,i=Number.MAX_SAFE_INTEGER;for(let s of t.keys()){let l;try{/^\d{1,2}:\d{2}$/.test(s)?l=this.timeProvider.getMsUntilTime(s):l=this.timeProvider.getMsUntilCron(s),l<i&&(i=l,o=s)}catch(c){e.notify(a.ERROR,`Error calculating next run time for schedule "${s}": ${c instanceof Error?c.message:String(c)}`)}}if(!o){e.notify(a.WARNING,"No scheduled configs found.");return}let r=t.get(o);r&&(i>0&&(this.options.onIdle?.(),e.notify(a.INFO,`Next run: ${o} in ${Math.floor(i/1e3/60)}m`)),this.scheduleTimer=setTimeout(async()=>{this.stopped||(await this.runConfigs(r),await this.waitForQueueDrain(),this.scheduleNextBatch())},i))}async waitForQueueDrain(){for(;this.queueManager.hasActiveProcessing()&&!this.stopped;)await this.timeProvider.sleep(1e3)}async stop(){f.getNotifier().notify(a.DEBUG,"Stopping scheduler..."),this.stopped=!0,this.scheduleTimer&&(clearTimeout(this.scheduleTimer),this.scheduleTimer=null),await this.queueManager.stop(),this.running=!1,f.getNotifier().notify(a.DEBUG,"Scheduler stopped")}async reload(e){f.getNotifier().notify(a.DEBUG,"Reloading configuration..."),this.configs=e,this.queueManager.updateConfig(),this.running&&this.options.mode==="scheduled"&&(this.scheduleTimer&&(clearTimeout(this.scheduleTimer),this.scheduleTimer=null),this.scheduleNextBatch())}updateDownloadManager(e){this.downloadManager=e}async triggerAllChecks(){f.getNotifier().notify(a.DEBUG,"Triggering immediate checks for all series...");for(let e of this.configs)this.queueManager.addSeriesCheck(e.url)}triggerImmediateChecks(){let e=f.getNotifier();e.notify(a.DEBUG,"Triggering immediate checks for all series..."),this.scheduleTimer&&(clearTimeout(this.scheduleTimer),this.scheduleTimer=null,e.notify(a.DEBUG,"Cancelled pending scheduled run")),this.queueManager.resetQueues(),e.notify(a.DEBUG,"Reset all queue states");for(let t of this.configs)this.queueManager.addSeriesCheck(t.url);this.scheduleNextBatch()}clearQueues(){f.getNotifier().notify(a.DEBUG,"Clearing queues..."),this.queueManager.clearQueues()}groupConfigsBySchedule(){let e=f.getNotifier(),t=new Map;for(let o of this.configs){let i=o.cron||o.startTime;if(!i){e.notify(a.WARNING,`Skip ${o.name}: no schedule`);continue}let r=t.get(i)||[];r.push(o),t.set(i,r)}return t}async runConfigs(e){let t=f.getNotifier();for(let i of e){if(this.stopped)break;this.queueManager.addSeriesCheck(i.url)}let o=this.queueManager.getQueueStats();f.getNotifier().notify(a.DEBUG,`Queue stats: ${JSON.stringify(o)}`),t.notify(a.INFO,`Added ${e.length} series to queue`)}async runOnce(){for(let e of this.configs){if(this.stopped)break;this.queueManager.addSeriesCheck(e.url)}for(;this.queueManager.hasActiveProcessing()&&!this.stopped;)await this.timeProvider.sleep(1e3);f.getNotifier().notify(a.DEBUG,"Single-run complete")}isRunning(){return this.running&&!this.stopped}getQueueManager(){return this.queueManager}};import{existsSync as St}from"fs";import{readFile as Dt}from"fs/promises";import{homedir as Qi}from"os";import{join as ji}from"path";async function qe(n){if(!St(n))throw new ie(`Cookie file not found: "${n}"`);let t=(await Dt(n,"utf-8")).split(`
15
+ `)}mapBrowserToPlaywright(e){switch((e||"chrome").toLowerCase()){case"chrome":case"chromium":case"edge":return Fe;case"firefox":return xt;case"safari":return Ct;default:return Fe}}};var de=class{queue=[];isExecuting=!1;nextAvailableAt=new Date(0);cooldownMs;constructor(e=0){this.cooldownMs=e}add(e,t){let o=new Date(Date.now()+(t??0));this.queue.push({data:e,addedAt:o})}addFirst(e,t){let o=new Date(Date.now()+(t??0));this.queue.unshift({data:e,addedAt:o})}getNext(){return this.queue.length===0?null:this.queue.shift()?.data??null}peekNext(){return this.queue.length===0?null:this.queue[0]?.data??null}canStart(e){if(this.isExecuting||e<this.nextAvailableAt)return!1;let t=this.queue[0];return!(t&&e<t.addedAt)}markStarted(){this.isExecuting=!0}markCompleted(e){this.isExecuting=!1,this.cooldownMs=e,this.nextAvailableAt=new Date(Date.now()+e)}markFailed(e){this.isExecuting=!1,this.cooldownMs=e,this.nextAvailableAt=new Date(Date.now()+e)}hasTasks(){return this.queue.length>0}getNextAvailableTime(){let e=this.nextAvailableAt,t=this.queue[0];return t&&t.addedAt>e&&(e=t.addedAt),e}getQueueLength(){return this.queue.length}getIsExecuting(){return this.isExecuting}getCooldownMs(){return this.cooldownMs}setCooldownMs(e){this.cooldownMs=e}clear(){this.queue=[]}reset(){this.queue=[],this.isExecuting=!1,this.nextAvailableAt=new Date(0)}getStatus(){let e=new Date;return{queueLength:this.queue.length,isExecuting:this.isExecuting,nextAvailableAt:this.nextAvailableAt,cooldownMs:this.cooldownMs,canStartNow:this.canStart(e)}}};var ue=class{queues=new Map;queueCooldowns=new Map;executorBusy=!1;timerId=null;roundRobinIndex=0;stopped=!1;executor;onWait;constructor(e){this.executor=e}setOnWait(e){this.onWait=e}registerQueue(e,t){if(this.queues.has(e))throw new Error(`Queue ${e} is already registered`);let o=new de(t);this.queues.set(e,o),this.queueCooldowns.set(e,t)}hasQueue(e){return this.queues.has(e)}unregisterQueue(e){this.queues.delete(e),this.queueCooldowns.delete(e)}clearQueues(){for(let e of this.queues.values())e.clear();this.clearTimer(),this.roundRobinIndex=0}resetQueues(){for(let e of this.queues.values())e.reset();this.clearTimer(),this.roundRobinIndex=0}addTask(e,t,o){let i=this.queues.get(e);if(!i)throw new Error(`Queue ${e} is not registered`);i.add(t,o),this.stopped||this.scheduleNext()}addPriorityTask(e,t,o){let i=this.queues.get(e);if(!i)throw new Error(`Queue ${e} is not registered`);i.addFirst(t,o),this.stopped||this.scheduleNext()}markTaskComplete(e,t){let o=this.queues.get(e);if(!o)throw new Error(`Queue ${e} is not registered`);let i=t??this.queueCooldowns.get(e)??0;o.markCompleted(i),this.executorBusy=!1,this.stopped||this.scheduleNext()}markTaskFailed(e,t){let o=this.queues.get(e);if(!o)throw new Error(`Queue ${e} is not registered`);let i=t??this.queueCooldowns.get(e)??0;o.markFailed(i),this.executorBusy=!1,this.stopped||this.scheduleNext()}scheduleNext(){if(this.stopped||(this.clearTimer(),this.trySchedule())||this.executorBusy)return;let t=this.getEarliestAvailableTime();if(t){let o=Date.now(),i=Math.max(0,t.time.getTime()-o);this.scheduleTimer(i,t.queueName,t.time)}}trySchedule(){if(this.executorBusy)return!1;let e=new Date,t=Array.from(this.queues.keys());if(t.length===0)return!1;for(let o=0;o<t.length;o++){let i=(this.roundRobinIndex+o)%t.length,r=t[i];if(!r)continue;let s=this.queues.get(r);if(s&&s.hasTasks()&&s.canStart(e)){let l=s.getNext();if(l)return s.markStarted(),this.executorBusy=!0,this.roundRobinIndex=(i+1)%t.length,this.executeTask(r,l).catch(c=>{console.error(`[UniversalScheduler] Task execution failed: ${c}`),this.markTaskFailed(r)}),!0}}return!1}async executeTask(e,t){await this.executor(t,e)}scheduleTimer(e,t,o){this.clearTimer(),this.onWait&&e>1e3&&this.onWait(t,e,o),this.timerId=setTimeout(()=>{this.timerId=null,this.scheduleNext()},e)}clearTimer(){this.timerId!==null&&(clearTimeout(this.timerId),this.timerId=null)}getEarliestAvailableTime(){let e=null;for(let[t,o]of this.queues.entries()){if(!o.hasTasks())continue;let i=o.getNextAvailableTime();(e===null||i<e.time)&&(e={time:i,queueName:t})}return e}stop(){this.stopped=!0,this.clearTimer()}resume(){this.stopped=!1,this.scheduleNext()}getStats(){let e=new Map;for(let[t,o]of this.queues.entries()){let i=o.getStatus();e.set(t,{queueLength:i.queueLength,isExecuting:i.isExecuting,nextAvailableAt:i.nextAvailableAt})}return e}isExecutorBusy(){return this.executorBusy}hasPendingTasks(){for(let e of this.queues.values())if(e.hasTasks())return!0;return!1}getTotalPendingTasks(){let e=0;for(let t of this.queues.values())e+=t.getQueueLength();return e}};var pe=class{stateManager;downloadManager;scheduler;running=!1;domainHandlers=new Map;constructor(e,t){this.stateManager=f.getStateManager(),this.downloadManager=e;let o=t||(i=>new ue(i));this.scheduler=o(async(i,r)=>{await this.executeTask(i,r)}),this.scheduler.setOnWait((i,r)=>{let s=f.getNotifier(),l=Math.round(r/1e3),c=i.split(":"),u=c[0],g=c[1];u==="download"?s.notify(a.INFO,`[${g}] Next download: ${l}s`):u==="check"&&s.notify(a.INFO,`[${g}] Next check: ${l}s`)})}addSeriesCheck(e){let t=f.getConfig(),o=S(e),r=t.resolve(e,"series").name;this.registerDownloadQueue(o);let s=this.registerSeriesCheckQueue(o,e),l={seriesUrl:e,attemptNumber:1,retryCount:0};this.scheduler.addTask(s,l),f.getNotifier().notify(a.DEBUG,`[${o}] Added ${r} to check queue`)}addEpisodes(e,t){let o=f.getNotifier(),i=f.getConfig();if(t.length===0)return;let r=i.resolve(e,"series"),s=r.name,l=S(e);this.registerDownloadQueue(l);let{downloadDelay:c}=r.download;for(let u=0;u<t.length;u++){let g=t[u];if(!g)continue;let h={seriesUrl:e,episode:g,retryCount:0},p=`download:${l}`,w=u*c*1e3;this.scheduler.addTask(p,h,w)}o.notify(a.SUCCESS,`[${l}] ${s}: queued ${t.length} episodes`)}updateConfig(){f.getNotifier().notify(a.DEBUG,"[QueueManager] Configuration will be reloaded")}clearQueues(){this.scheduler.clearQueues(),f.getNotifier().notify(a.DEBUG,"[QueueManager] Cleared all queues")}resetQueues(){this.scheduler.resetQueues(),f.getNotifier().notify(a.DEBUG,"[QueueManager] Reset all queue states")}start(){if(this.running)throw new Error("QueueManager is already running");this.running=!0,this.scheduler.resume(),f.getNotifier().notify(a.DEBUG,"[QueueManager] Started queue processing")}async stop(){this.running&&(f.getNotifier().notify(a.DEBUG,"[QueueManager] Stopping queue processing..."),this.scheduler.stop(),this.running=!1,f.getNotifier().notify(a.DEBUG,"[QueueManager] Queue processing stopped"))}hasActiveProcessing(){return this.scheduler.isExecutorBusy()||this.scheduler.hasPendingTasks()}getQueueStats(){let e=this.scheduler.getStats(),t={},o={};for(let[i,r]of e.entries())if(i.startsWith("check:")){let l=i.split(":")[1];if(!l)continue;t[l]||(t[l]={length:0,processing:!1}),t[l].length+=r.queueLength,r.isExecuting&&(t[l].processing=!0)}else if(i.startsWith("download:")){let s=i.slice(9);o[s]={length:r.queueLength,processing:r.isExecuting}}return{checkQueues:t,downloadQueues:o}}registerDownloadQueue(e){let t=f.getConfig(),o=`download:${e}`;if(this.scheduler.hasQueue(o))return;let i=`https://${e}/`,r=t.resolve(i,"domain"),{downloadDelay:s}=r.download;this.scheduler.registerQueue(o,s*1e3)}registerSeriesCheckQueue(e,t){let o=f.getConfig(),i=Et("md5").update(t).digest("hex").substring(0,12),r=`check:${e}:${i}`;if(this.scheduler.hasQueue(r))return r;let s=o.resolve(t,"series"),{checkInterval:l}=s.check;if(this.scheduler.registerQueue(r,l*1e3),!this.domainHandlers.has(e)){let c=F.getHandlerOrThrow(`https://${e}/`);this.domainHandlers.set(e,c)}return r}async executeTask(e,t){let o=t.split(":"),i=o[0],r=o[1];if(!i||!r)throw new Error(`Invalid queue name format: ${t}`);if(i==="check")await this.executeCheck(e,r,t);else if(i==="download")await this.executeDownload(e,r,t);else throw new Error(`Unknown queue type: ${i}`)}async executeCheck(e,t,o){let i=f.getNotifier(),r=f.getConfig(),{seriesUrl:s,attemptNumber:l,retryCount:c=0}=e,u=this.domainHandlers.get(t);if(!u)throw new Error(`No handler found for domain ${t}`);let g=r.resolve(s,"series"),h=g.name,{count:p,checkInterval:w}=g.check;try{let d=await this.performCheck(u,s,g,l,t);if(d.hasNewEpisodes){if(i.notify(a.SUCCESS,`[${t}] ${h}: ${d.episodes.length} new episodes (attempt ${l}/${p})`),g.cookieRefreshBrowser)try{await G.getInstance().refreshCookies(s)}catch(m){let v=m instanceof Error?m.message:String(m);i.notify(a.ERROR,`[${t}] ${h}: cookie refresh failed - ${v}`)}this.addEpisodes(s,d.episodes),this.scheduler.markTaskComplete(o,w*1e3)}else if(l<p){let m=w*1e3,v=d.requeueDelay??m;i.notify(a.INFO,`[${t}] ${h}: no new episodes, retry in ${Math.round(v/1e3)}s (attempt ${l}/${p})`);let y={seriesUrl:s,attemptNumber:l+1,retryCount:0};this.scheduler.addTask(o,y,v),this.scheduler.markTaskComplete(o,w*1e3)}else i.notify(a.INFO,`[${t}] ${h}: exhausted after ${p} attempts`),this.scheduler.markTaskComplete(o,w*1e3)}catch(d){let m=d instanceof Error?d.message:String(d),{maxRetries:v,initialTimeout:y,backoffMultiplier:E,jitterPercentage:k}=g.download;if(c<v){let T=this.calculateBackoff(c,y*1e3,E,k);i.notify(a.WARNING,`[${t}] ${h}: check retry ${c+1}/${v} in ${Math.round(T/1e3)}s`);let N={seriesUrl:s,attemptNumber:l,retryCount:c+1};this.scheduler.addPriorityTask(o,N,T),this.scheduler.markTaskComplete(o,w*1e3)}else i.notify(a.ERROR,`[${t}] ${h}: check failed - ${m}`),this.scheduler.markTaskComplete(o,w*1e3)}}async executeDownload(e,t,o){let i=f.getNotifier(),r=f.getConfig(),{seriesUrl:s,episode:l,retryCount:c=0}=e,u=r.resolve(s,"series"),g=u.name,{downloadDelay:h}=u.download;try{await this.downloadManager.download(s,l),this.scheduler.markTaskComplete(o,h*1e3)}catch(p){let w=p instanceof Error?p.message:String(p),{maxRetries:d,initialTimeout:m,backoffMultiplier:v,jitterPercentage:y}=u.download;if(c<d){let E=this.calculateBackoff(c,m*1e3,v,y);i.notify(a.WARNING,`[${t}] ${g} - ${String(l.number).padStart(2,"0")}: retry ${c+1}/${d} in ${Math.round(E/1e3)}s`);let k={seriesUrl:s,episode:l,retryCount:c+1};this.scheduler.addPriorityTask(o,k,E),this.scheduler.markTaskComplete(o,h*1e3)}else c===0?(i.notify(a.ERROR,`[${t}] ${g} - ${String(l.number).padStart(2,"0")}: ${w}`),i.notify(a.INFO,`[${t}] Episode URL: ${l.url}`)):i.notify(a.ERROR,`[${t}] ${g} - ${String(l.number).padStart(2,"0")}: ${w}`),this.scheduler.markTaskComplete(o,h*1e3)}}async performCheck(e,t,o,i,r){let s=f.getNotifier(),l=o.name,c=o.check.count;s.notify(a.INFO,`[${r}] ${l}: checking (attempt ${i}/${c})`),f.getNotifier().notify(a.DEBUG,`[${r}] Checking URL: ${t}`);let u=await e.extractEpisodes(t),g=new Map;u.forEach(d=>{let m=g.get(d.type)||0;g.set(d.type,m+1)});let h=Array.from(g.entries()).map(([d,m])=>`${d}: ${m}`).join(", ");s.notify(a.INFO,`[${r}] ${l}: ${u.length} episodes (${h})`),u.forEach(d=>{s.notify(a.DEBUG,`[${r}] Episode ${d.number}: ${d.type} - ${d.url}${d.title?` (${d.title})`:""}`)});let{downloadTypes:p}=o.check,w=u.filter(d=>{let m=p.includes(d.type),v=o.stateFile,y=!this.stateManager.isDownloaded(v,l,d.number);return m&&y});if(u.length!==w.length){let d=u.length-w.length;s.notify(a.INFO,`[${r}] Filtering: ${w.length} to download, ${d} skipped`)}return w.length>0?{hasNewEpisodes:!0,episodes:w}:{hasNewEpisodes:!1,episodes:[],shouldRequeue:!0}}calculateBackoff(e,t,o,i){let r=t*o**e,s=r*i/100,l=(Math.random()*2-1)*s,c=Math.max(0,r+l);return Math.floor(c)}};import kt from"cron-parser";function Tt(n){let e=n.match(/^(\d{1,2}):(\d{2})$/);if(!e)throw new Error(`Invalid time format: "${n}". Expected HH:MM`);let[,t,o]=e,i=parseInt(t||"0",10),r=parseInt(o||"0",10);if(i<0||i>23)throw new Error(`Invalid hours: ${i}. Must be between 0 and 23`);if(r<0||r>59)throw new Error(`Invalid minutes: ${r}. Must be between 0 and 59`);let s=new Date;return s.setHours(i,r,0,0),s}function Oe(n){let e=Tt(n),t=new Date,o=new Date(t);return o.setHours(e.getHours(),e.getMinutes(),0,0),o<=t&&o.setDate(o.getDate()+1),o.getTime()-t.getTime()}function Ge(n){try{let t=kt.parse(n).next().toDate(),o=new Date;return t.getTime()-o.getTime()}catch(e){throw new Error(`Invalid cron expression: "${n}". Error: ${e instanceof Error?e.message:String(e)}`)}}function Be(n){return new Promise(e=>setTimeout(e,n))}var fe=class{configs;downloadManager;options;queueManager;running=!1;stopped=!0;timeProvider;scheduleTimer=null;constructor(e,t,o={mode:"scheduled"},i,r){this.configs=e,this.downloadManager=t,this.options=o,this.timeProvider=i||{getMsUntilTime:Oe,getMsUntilCron:Ge,sleep:Be};let s=r||(l=>new pe(l));this.queueManager=s(this.downloadManager)}async start(){if(this.running)throw new re("Scheduler is already running");this.running=!0,this.stopped=!1,this.queueManager.start();let e=f.getNotifier();if(this.options.mode==="once")f.getNotifier().notify(a.DEBUG,"Single-run mode: checking all series once"),await this.runOnce(),this.running=!1;else return e.notify(a.INFO,"Scheduler started"),this.scheduleNextBatch(),new Promise(t=>{let o=setInterval(()=>{this.running||(clearInterval(o),t())},100)})}scheduleNextBatch(){if(this.stopped)return;let e=f.getNotifier(),t=this.groupConfigsBySchedule(),o=null,i=Number.MAX_SAFE_INTEGER;for(let s of t.keys()){let l;try{/^\d{1,2}:\d{2}$/.test(s)?l=this.timeProvider.getMsUntilTime(s):l=this.timeProvider.getMsUntilCron(s),l<i&&(i=l,o=s)}catch(c){e.notify(a.ERROR,`Error calculating next run time for schedule "${s}": ${c instanceof Error?c.message:String(c)}`)}}if(!o){e.notify(a.WARNING,"No scheduled configs found.");return}let r=t.get(o);r&&(i>0&&(this.options.onIdle?.(),e.notify(a.INFO,`Next run: ${o} in ${Math.floor(i/1e3/60)}m`)),this.scheduleTimer=setTimeout(async()=>{this.stopped||(await this.runConfigs(r),this.scheduleNextBatch())},i))}async stop(){f.getNotifier().notify(a.DEBUG,"Stopping scheduler..."),this.stopped=!0,this.scheduleTimer&&(clearTimeout(this.scheduleTimer),this.scheduleTimer=null),await this.queueManager.stop(),this.running=!1,f.getNotifier().notify(a.DEBUG,"Scheduler stopped")}async reload(e){f.getNotifier().notify(a.DEBUG,"Reloading configuration..."),this.configs=e,this.queueManager.updateConfig(),this.running&&this.options.mode==="scheduled"&&(this.scheduleTimer&&(clearTimeout(this.scheduleTimer),this.scheduleTimer=null),this.scheduleNextBatch())}updateDownloadManager(e){this.downloadManager=e}async triggerAllChecks(){f.getNotifier().notify(a.DEBUG,"Triggering immediate checks for all series...");for(let e of this.configs)this.queueManager.addSeriesCheck(e.url)}triggerImmediateChecks(){let e=f.getNotifier();e.notify(a.DEBUG,"Triggering immediate checks for all series..."),this.scheduleTimer&&(clearTimeout(this.scheduleTimer),this.scheduleTimer=null,e.notify(a.DEBUG,"Cancelled pending scheduled run")),this.queueManager.resetQueues(),e.notify(a.DEBUG,"Reset all queue states");for(let t of this.configs)this.queueManager.addSeriesCheck(t.url);this.scheduleNextBatch()}clearQueues(){f.getNotifier().notify(a.DEBUG,"Clearing queues..."),this.queueManager.clearQueues()}groupConfigsBySchedule(){let e=f.getNotifier(),t=new Map;for(let o of this.configs){let i=o.cron||o.startTime;if(!i){e.notify(a.WARNING,`Skip ${o.name}: no schedule`);continue}let r=t.get(i)||[];r.push(o),t.set(i,r)}return t}async runConfigs(e){let t=f.getNotifier();for(let i of e){if(this.stopped)break;this.queueManager.addSeriesCheck(i.url)}let o=this.queueManager.getQueueStats();f.getNotifier().notify(a.DEBUG,`Queue stats: ${JSON.stringify(o)}`),t.notify(a.INFO,`Added ${e.length} series to queue`)}async runOnce(){for(let e of this.configs){if(this.stopped)break;this.queueManager.addSeriesCheck(e.url)}for(;this.queueManager.hasActiveProcessing()&&!this.stopped;)await this.timeProvider.sleep(1e3);f.getNotifier().notify(a.DEBUG,"Single-run complete")}isRunning(){return this.running&&!this.stopped}getQueueManager(){return this.queueManager}};import{existsSync as St}from"fs";import{readFile as Dt}from"fs/promises";import{homedir as qi}from"os";import{join as Qi}from"path";async function qe(n){if(!St(n))throw new ie(`Cookie file not found: "${n}"`);let t=(await Dt(n,"utf-8")).split(`
16
16
  `),o=[];for(let i of t){let r=i.trim();if(r.startsWith("#")||!r)continue;let s=i.split(" ");if(s.length>=7){let l=s[5],c=s[6];if(l&&c){let u=c.trim();u&&o.push(`${l}=${u}`)}}}return o.join("; ")}var We={loadConfig:Se,checkYtDlpInstalled:_.checkYtDlpInstalled,readCookieFile:qe,createDownloadManager:()=>new _,createScheduler:(n,e,t)=>new fe(n,e,t)};async function Ve(n,e){e.notify(a.DEBUG,"Shutting down gracefully...");try{await G.getInstance().shutdown(),await n.stop(),e.notify(a.DEBUG,"Shutdown complete")}catch(t){e.notify(a.ERROR,`Error during shutdown: ${t instanceof Error?t.message:String(t)}`)}}async function It(n,e,t=We,o=!1){let i=await t.loadConfig(n),r=new W(i),s=new K,l=r.resolve("","global"),c=new Y(o?a.DEBUG:l.notifications.consoleMinLevel);if(s.add(c,0),l.telegram?.botToken&&l.telegram?.chatId)try{s.add(new J(l.telegram),10)}catch(d){s.notify(a.WARNING,`Failed to set up Telegram: ${d instanceof Error?d.message:String(d)}`)}if(s.notify(a.INFO,`Mode: ${e}`),f.initialize(r,s),s.notify(a.DEBUG,"AppContext initialized"),s.notify(a.DEBUG,"Checking yt-dlp installation..."),!await t.checkYtDlpInstalled())throw new Error(`yt-dlp is not installed. Please install it first:
17
17
  - macOS: brew install yt-dlp
18
18
  - Linux: pip install yt-dlp