wetvlo 0.0.24 → 0.0.25

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
@@ -1,20 +1,20 @@
1
1
  #!/usr/bin/env node
2
- import{fileURLToPath as Mt}from"url";import{run as Lt}from"cmd-ts";import*as We from"readline";import{boolean as He,command as Rt,flag as Qe,option as Nt,string as $t}from"cmd-ts";import{existsSync as Ye,readFileSync as Xe}from"fs";import{writeFile as Je}from"fs/promises";import{isAbsolute as Ze,join as et}from"path";import{z as _e}from"zod";function J(n){let e=Object.fromEntries(n.map(t=>[t.toUpperCase(),t]));return{values:n,object:e,schema:_e.enum(n),type:null}}var be=J(["debug","info","success","highlight","warning","error"]),a=be.object,pe=be.schema,G={debug:0,info:1,success:2,highlight:3,warning:4,error:5};function xe(){return{version:"3.0.0",series:{}}}var Z=class n{static locks=new Map;isDownloaded(e,t,o){try{let r=this.loadState(e).series[t];if(!r)return!1;let s=String(o).padStart(2,"0");return r.includes(s)}catch(i){return this.handleError(i,`Failed to check episode status for ${t}`),!1}}async addDownloadedEpisode(e,t,o){return this.withLock(e,async()=>{try{let i=this.loadState(e);i.series[t]||(i.series[t]=[]);let r=String(o).padStart(2,"0");i.series[t].includes(r)||(i.series[t].push(r),i.series[t].sort()),await this.saveState(e,i)}catch(i){throw this.handleError(i,`Failed to add episode for ${t}`),i}})}getSeriesEpisodes(e,t){try{return this.loadState(e).series[t]??[]}catch(o){return this.handleError(o,`Failed to get episodes for ${t}`),[]}}async withLock(e,t){let o=n.locks.get(e);for(;o;)await o,o=n.locks.get(e);let i=(async()=>{try{return await t()}finally{n.locks.delete(e)}})();return n.locks.set(e,i),i}loadState(e){let t=this.resolvePath(e);if(!Ye(t))return xe();try{let o=Xe(t,"utf-8");return JSON.parse(o)}catch(o){throw new Error(`Failed to load state from ${t}: ${o instanceof Error?o.message:String(o)}`)}}async saveState(e,t){let o=this.resolvePath(e);try{let i={};Object.keys(t.series).sort().forEach(s=>{let l=t.series[s];l&&(i[s]=[...l].sort())}),t.series=i;let r=JSON.stringify(t,null,2);await Je(o,r,"utf-8")}catch(i){throw new Error(`Failed to save state to ${o}: ${i instanceof Error?i.message:String(i)}`)}}resolvePath(e){return Ze(e)?e:et(process.cwd(),e)}handleError(e,t){let o=e instanceof Error?e.message:String(e),i=`${t}: ${o}`,r=d.getNotifier();r?r.notify(a.ERROR,i):console.error(i)}};var d=class n{static configRegistry;static notifier;static stateManager;static initialize(e,t,o){n.configRegistry=e,n.notifier=t,n.stateManager=o||(t?new Z:void 0)}static getConfig(){if(!n.configRegistry)throw new Error("AppContext not initialized. Call AppContext.initialize() first.");return n.configRegistry}static getNotifier(){if(!n.notifier)throw new Error("AppContext not initialized. Call AppContext.initialize() first.");return n.notifier}static getStateManager(){if(!n.stateManager)throw new Error("AppContext not initialized. Call AppContext.initialize() first.");return n.stateManager}static reloadConfig(e){n.configRegistry=e}static setNotifier(e){n.notifier=e}static isInitialized(){return n.configRegistry!==void 0}static reset(){n.configRegistry=void 0,n.notifier=void 0,n.stateManager=void 0}};import{existsSync as ct}from"fs";import{readFile as dt}from"fs/promises";import{join as ut}from"path";import*as De from"js-yaml";var I=class extends Error{constructor(e){super(e),this.name="WetvloError"}},P=class extends I{constructor(e){super(e),this.name="ConfigError"}};var M=class extends I{constructor(t,o){super(t);this.url=o;this.name="HandlerError"}},ee=class extends I{constructor(t,o){super(t);this.url=o;this.name="DownloadError"}},te=class extends I{constructor(e){super(e),this.name="NotificationError"}},oe=class extends I{constructor(e){super(e),this.name="CookieError"}},ie=class extends I{constructor(e){super(e),this.name="SchedulerError"}};function tt(n){return typeof n!="string"?n:n.replace(/\$\{([^}]+)\}/g,(e,t)=>{let o=process.env[t];if(o===void 0)throw new Error(`Environment variable "${t}" is not set`);return o})}function re(n){if(typeof n=="string")return tt(n);if(Array.isArray(n))return n.map(e=>re(e));if(n!==null&&typeof n=="object"){let e={};for(let[t,o]of Object.entries(n))e[t]=re(o);return e}return n}import{z as b}from"zod";var ge=J(["available","vip","svip","preview","locked","teaser","express"]),x=ge.object,Ce=ge.schema,Zt=ge.values;var ot=b.object({count:b.number().positive().optional().describe("Number of episodes to check"),checkInterval:b.number().positive().optional().describe("Interval between checks in seconds"),downloadTypes:b.array(Ce).optional().describe("Episode types to download")}),it=b.object({downloadDir:b.string().optional().describe("Directory to save downloaded episodes"),tempDir:b.string().optional().describe("Directory for temporary files"),downloadDelay:b.number().nonnegative().optional().describe("Delay between downloads in milliseconds"),maxRetries:b.number().int().nonnegative().optional().describe("Maximum number of retry attempts"),initialTimeout:b.number().positive().optional().describe("Initial timeout for operations in milliseconds"),backoffMultiplier:b.number().positive().optional().describe("Multiplier for exponential backoff"),jitterPercentage:b.number().int().min(0).max(100).optional().describe("Jitter percentage for retry delays"),minDuration:b.number().nonnegative().optional().describe("Minimum duration in seconds for downloads")}),rt=b.object({botToken:b.string().describe("Telegram bot token"),chatId:b.string().describe("Telegram chat ID"),minLevel:pe.optional().describe("Minimum notification level for Telegram")}),Ee=b.enum(["chrome","firefox","safari","chromium","edge"]),me=b.object({check:ot.optional().describe("Check settings"),download:it.optional().describe("Download settings"),notifications:b.object({consoleMinLevel:pe.optional().describe("Minimum notification level for console output")}).optional().describe("Notification settings"),telegram:rt.optional().describe("Telegram notification configuration"),stateFile:b.string().optional().describe("Path to state file"),browser:Ee.optional().describe("Browser to use for scraping"),cookieFile:b.string().optional().describe("Path to cookie file"),subLangs:b.array(b.string()).optional().describe("List of subtitle languages to download"),cookieRefreshBrowser:Ee.optional().describe("Browser to use for Playwright cookie refresh"),playwrightHeadless:b.boolean().optional().describe("Run Playwright browser in headless mode")}),st=me,nt=me.extend({domain:b.string().describe('Domain name (e.g., "weTV")')}),at=me.extend({name:b.string().describe("Series name"),url:b.url().describe("Series URL"),startTime:b.string().regex(/^\d{1,2}:\d{2}$/,{message:'Must be in HH:MM format (e.g., "20:00")'}).optional().describe("Start time in HH:MM format"),cron:b.string().optional().describe("Cron expression for scheduling")}),lt=b.object({series:b.array(at).min(1,"Cannot be empty").describe("List of series to monitor"),domainConfigs:b.array(nt).optional().describe("Domain-specific configurations"),globalConfig:st.optional().describe("Global configuration defaults")});function ke(n){lt.parse(n);let e=[];if(n.globalConfig){let t=n.globalConfig;t.downloadDir&&!t.download?.downloadDir&&e.push(`'downloadDir' found directly under 'globalConfig'. It should be placed under 'globalConfig.download'. Current value: "${t.downloadDir}"`),t.tempDir&&!t.download?.tempDir&&e.push(`'tempDir' found directly under 'globalConfig'. It should be placed under 'globalConfig.download'. Current value: "${t.tempDir}"`),t.count&&!t.check?.count&&e.push("'count' found directly under 'globalConfig'. It should be placed under 'globalConfig.check'."),t.checkInterval&&!t.check?.checkInterval&&e.push("'checkInterval' found directly under 'globalConfig'. It should be placed under 'globalConfig.check'.")}n.globalConfigs&&e.push("'globalConfigs' found. Did you mean 'globalConfig'?"),n.domainConfigs&&Array.isArray(n.domainConfigs)&&n.domainConfigs.forEach((t,o)=>{t.downloadDir&&!t.download?.downloadDir&&e.push(`'downloadDir' found directly under 'domainConfigs[${o}]. It should be placed under 'domainConfigs[${o}].download'.`)}),e.length>0&&(console.warn(`
2
+ import{fileURLToPath as Mt}from"url";import{run as Lt}from"cmd-ts";import*as je from"readline";import{boolean as He,command as Nt,flag as Qe,option as Rt,string as $t}from"cmd-ts";import{existsSync as Ye,readFileSync as Je}from"fs";import{writeFile as Xe}from"fs/promises";import{isAbsolute as Ze,join as et}from"path";import{z as Ke}from"zod";function Z(n){let e=Object.fromEntries(n.map(t=>[t.toUpperCase(),t]));return{values:n,object:e,schema:Ke.enum(n),type:null}}var be=Z(["debug","info","success","highlight","warning","error"]),a=be.object,ge=be.schema,B={debug:0,info:1,success:2,highlight:3,warning:4,error:5};function xe(){return{version:"3.0.0",series:{}}}var ee=class n{static locks=new Map;isDownloaded(e,t,o){try{let r=this.loadState(e).series[t];if(!r)return!1;let s=String(o).padStart(2,"0");return r.includes(s)}catch(i){return this.handleError(i,`Failed to check episode status for ${t}`),!1}}async addDownloadedEpisode(e,t,o){return this.withLock(e,async()=>{try{let i=this.loadState(e);i.series[t]||(i.series[t]=[]);let r=String(o).padStart(2,"0");i.series[t].includes(r)||(i.series[t].push(r),i.series[t].sort()),await this.saveState(e,i)}catch(i){throw this.handleError(i,`Failed to add episode for ${t}`),i}})}getSeriesEpisodes(e,t){try{return this.loadState(e).series[t]??[]}catch(o){return this.handleError(o,`Failed to get episodes for ${t}`),[]}}async withLock(e,t){let o=n.locks.get(e);for(;o;)await o,o=n.locks.get(e);let i=(async()=>{try{return await t()}finally{n.locks.delete(e)}})();return n.locks.set(e,i),i}loadState(e){let t=this.resolvePath(e);if(!Ye(t))return xe();try{let o=Je(t,"utf-8");return JSON.parse(o)}catch(o){throw new Error(`Failed to load state from ${t}: ${o instanceof Error?o.message:String(o)}`)}}async saveState(e,t){let o=this.resolvePath(e);try{let i={};Object.keys(t.series).sort().forEach(s=>{let l=t.series[s];l&&(i[s]=[...l].sort())}),t.series=i;let r=JSON.stringify(t,null,2);await Xe(o,r,"utf-8")}catch(i){throw new Error(`Failed to save state to ${o}: ${i instanceof Error?i.message:String(i)}`)}}resolvePath(e){return Ze(e)?e:et(process.cwd(),e)}handleError(e,t){let o=e instanceof Error?e.message:String(e),i=`${t}: ${o}`,r=f.getNotifier();r?r.notify(a.ERROR,i):console.error(i)}};var f=class n{static configRegistry;static notifier;static stateManager;static initialize(e,t,o){n.configRegistry=e,n.notifier=t,n.stateManager=o||(t?new ee:void 0)}static getConfig(){if(!n.configRegistry)throw new Error("AppContext not initialized. Call AppContext.initialize() first.");return n.configRegistry}static getNotifier(){if(!n.notifier)throw new Error("AppContext not initialized. Call AppContext.initialize() first.");return n.notifier}static getStateManager(){if(!n.stateManager)throw new Error("AppContext not initialized. Call AppContext.initialize() first.");return n.stateManager}static reloadConfig(e){n.configRegistry=e}static setNotifier(e){n.notifier=e}static isInitialized(){return n.configRegistry!==void 0}static reset(){n.configRegistry=void 0,n.notifier=void 0,n.stateManager=void 0}};import{existsSync as ct}from"fs";import{readFile as dt}from"fs/promises";import{join as ut}from"path";import*as Te from"js-yaml";var I=class extends Error{constructor(e){super(e),this.name="WetvloError"}},A=class extends I{constructor(e){super(e),this.name="ConfigError"}};var M=class extends I{constructor(t,o){super(t);this.url=o;this.name="HandlerError"}},te=class extends I{constructor(t,o){super(t);this.url=o;this.name="DownloadError"}},oe=class extends I{constructor(e){super(e),this.name="NotificationError"}},ie=class extends I{constructor(e){super(e),this.name="CookieError"}},re=class extends I{constructor(e){super(e),this.name="SchedulerError"}};function tt(n){return typeof n!="string"?n:n.replace(/\$\{([^}]+)\}/g,(e,t)=>{let o=process.env[t];if(o===void 0)throw new Error(`Environment variable "${t}" is not set`);return o})}function se(n){if(typeof n=="string")return tt(n);if(Array.isArray(n))return n.map(e=>se(e));if(n!==null&&typeof n=="object"){let e={};for(let[t,o]of Object.entries(n))e[t]=se(o);return e}return n}import{z as x}from"zod";var me=Z(["available","vip","svip","preview","locked","teaser","express"]),b=me.object,Ce=me.schema,Zt=me.values;var ot=x.object({count:x.number().positive().optional().describe("Number of episodes to check"),checkInterval:x.number().positive().optional().describe("Interval between checks in seconds"),downloadTypes:x.array(Ce).optional().describe("Episode types to download")}),it=x.object({downloadDir:x.string().optional().describe("Directory to save downloaded episodes"),tempDir:x.string().optional().describe("Directory for temporary files"),downloadDelay:x.number().nonnegative().optional().describe("Delay between downloads in milliseconds"),maxRetries:x.number().int().nonnegative().optional().describe("Maximum number of retry attempts"),initialTimeout:x.number().positive().optional().describe("Initial timeout for operations in milliseconds"),backoffMultiplier:x.number().positive().optional().describe("Multiplier for exponential backoff"),jitterPercentage:x.number().int().min(0).max(100).optional().describe("Jitter percentage for retry delays"),minDuration:x.number().nonnegative().optional().describe("Minimum duration in seconds for downloads")}),rt=x.object({botToken:x.string().describe("Telegram bot token"),chatId:x.string().describe("Telegram chat ID"),minLevel:ge.optional().describe("Minimum notification level for Telegram")}),Ee=x.enum(["chrome","firefox","safari","chromium","edge"]),he=x.object({check:ot.optional().describe("Check settings"),download:it.optional().describe("Download settings"),notifications:x.object({consoleMinLevel:ge.optional().describe("Minimum notification level for console output")}).optional().describe("Notification settings"),telegram:rt.optional().describe("Telegram notification configuration"),stateFile:x.string().optional().describe("Path to state file"),browser:Ee.optional().describe("Browser to use for scraping"),cookieFile:x.string().optional().describe("Path to cookie file"),subLangs:x.array(x.string()).optional().describe("List of subtitle languages to download"),cookieRefreshBrowser:Ee.optional().describe("Browser to use for Playwright cookie refresh"),playwrightHeadless:x.boolean().optional().describe("Run Playwright browser in headless mode")}),st=he,nt=he.extend({domain:x.string().describe('Domain name (e.g., "weTV")')}),at=he.extend({name:x.string().describe("Series name"),url:x.url().describe("Series URL"),startTime:x.string().regex(/^\d{1,2}:\d{2}$/,{message:'Must be in HH:MM format (e.g., "20:00")'}).optional().describe("Start time in HH:MM format"),cron:x.string().optional().describe("Cron expression for scheduling")}),lt=x.object({series:x.array(at).min(1,"Cannot be empty").describe("List of series to monitor"),domainConfigs:x.array(nt).optional().describe("Domain-specific configurations"),globalConfig:st.optional().describe("Global configuration defaults")});function ke(n){lt.parse(n);let e=[];if(n.globalConfig){let t=n.globalConfig;t.downloadDir&&!t.download?.downloadDir&&e.push(`'downloadDir' found directly under 'globalConfig'. It should be placed under 'globalConfig.download'. Current value: "${t.downloadDir}"`),t.tempDir&&!t.download?.tempDir&&e.push(`'tempDir' found directly under 'globalConfig'. It should be placed under 'globalConfig.download'. Current value: "${t.tempDir}"`),t.count&&!t.check?.count&&e.push("'count' found directly under 'globalConfig'. It should be placed under 'globalConfig.check'."),t.checkInterval&&!t.check?.checkInterval&&e.push("'checkInterval' found directly under 'globalConfig'. It should be placed under 'globalConfig.check'.")}n.globalConfigs&&e.push("'globalConfigs' found. Did you mean 'globalConfig'?"),n.domainConfigs&&Array.isArray(n.domainConfigs)&&n.domainConfigs.forEach((t,o)=>{t.downloadDir&&!t.download?.downloadDir&&e.push(`'downloadDir' found directly under 'domainConfigs[${o}]. It should be placed under 'domainConfigs[${o}].download'.`)}),e.length>0&&(console.warn(`
3
3
  \u26A0\uFE0F Configuration Warnings:`),console.warn("The following configuration issues were detected:"),e.forEach((t,o)=>{console.warn(`${o+1}. ${t}`)}),console.warn(`Please fix these issues in your config.yaml file.
4
- `))}var ft="./config.yaml";async function Te(n=ft){let e=ut(process.cwd(),n);if(!ct(e))throw new P(`Configuration file not found: "${e}". Create a config.yaml file or specify a different path.`);let t=await dt(e,"utf-8"),o;try{o=De.load(t)}catch(r){throw new P(`Failed to parse YAML: ${r instanceof Error?r.message:String(r)}`)}return ke(o),re(o)}function B(n,e){if(!e)return n;let t={...n};for(let o in e)if(Object.hasOwn(e,o)){let i=e[o],r=t[o];Se(i)&&Se(r)?t[o]=B(r,i):t[o]=i}return t}function Se(n){return typeof n=="object"&&n!==null&&!Array.isArray(n)}function T(n){try{return new URL(n).hostname}catch{throw new Error(`Invalid URL: "${n}"`)}}var pt={check:{count:3,checkInterval:600,downloadTypes:[x.AVAILABLE]},download:{downloadDir:"./downloads",tempDir:"./downloads",downloadDelay:10,maxRetries:3,initialTimeout:5,backoffMultiplier:2,jitterPercentage:10,minDuration:0},telegram:{minLevel:a.ERROR},notifications:{consoleMinLevel:a.INFO},stateFile:"wetvlo-state.json",browser:"chrome",playwrightHeadless:!0};function Re(){return pt}var V=class{map=new Map;seriesByUrl=new Map;constructor(e){let t=Re(),o=B(t,e.globalConfig);this.setConfig("global",o);for(let i of e.domainConfigs||[]){let r=B(o,i);this.setConfig(`domain:${i.domain}`,r)}for(let i of e.series){let r=T(i.url),s=this.getConfig(`domain:${r}`);if(!s){let c=this.getConfig("global");s=B(c,{domain:r})}let l=B(s,i);this.setConfig(`series:${i.url}`,l),this.seriesByUrl.set(i.url,l)}}getConfig(e){return this.map.get(e)}setConfig(e,t){this.map.set(e,t)}resolve(e,t){if(t==="global"){let r=this.getConfig("global");if(!r)throw new Error("Global configuration not found");return r}if(t==="domain"){let r=T(e),s=this.getConfig(`domain:${r}`);if(!s){let l=this.getConfig("global");if(!l)throw new Error("Global configuration not found");return Object.assign(l,{domain:r})}return s}let o=this.getConfig(`series:${e}`);if(!o)throw new Error(`No configuration found for URL: ${e}`);let i=o;return this.validate(i),i}listSeries(){return Array.from(this.seriesByUrl.values())}listSeriesUrls(){return Array.from(this.seriesByUrl.keys())}listDomains(){let e=new Set;for(let t of this.seriesByUrl.keys())e.add(T(t));return Array.from(e)}validate(e){if(!e.check)throw new Error("Missing check configuration");if(!e.download)throw new Error("Missing download configuration");let{check:t,download:o}=e;if(t.count<1)throw new Error(`Invalid check count: ${t.count}`);if(t.checkInterval<0)throw new Error(`Invalid check interval: ${t.checkInterval}`);if(o.downloadDelay<0)throw new Error(`Invalid download delay: ${o.downloadDelay}`);if(o.maxRetries<0)throw new Error(`Invalid max retries: ${o.maxRetries}`);if(o.initialTimeout<0)throw new Error(`Invalid initial timeout: ${o.initialTimeout}`);if(o.backoffMultiplier<1)throw new Error(`Invalid backoff multiplier: ${o.backoffMultiplier}`);if(o.minDuration<0)throw new Error(`Invalid min duration: ${o.minDuration}`)}};import*as Q from"fs";import*as N from"fs/promises";import{basename as A,join as Ae,resolve as H}from"path";function Ne(n){return n.replace(/[<>:"/\\|?*]/g,"_").replace(/[\x00-\x1F]/g,"").replace(/[\s.]+$/,"")}import{execa as gt}from"execa";async function $e(n,e){try{let{stdout:t}=await gt("ffprobe",["-v","error","-show_entries","format=duration","-of","default=noprint_wrappers=1:nokey=1",n]),o=parseFloat(t.trim());return Number.isNaN(o)?0:o}catch(t){let o=`Failed to get video duration for ${n}: ${t instanceof Error?t.message:String(t)}`;return e&&e.notify(a.ERROR,o),0}}function Ie(n){return{downloadDir:n.download.downloadDir,tempDir:n.download.tempDir,cookieFile:n.cookieFile,minDuration:n.download.minDuration,subLangs:n.subLangs}}var se=class{};import*as Le from"fs/promises";import{join as ht}from"path";import{execa as Me}from"execa";var z=class{async download(e,t,o,i={}){let{args:r=[],cookieFile:s,subLangs:l,onProgress:c,onLog:u}=i,p=ht(o,`${t}.%(ext)s`);await Le.mkdir(o,{recursive:!0});let y=["--no-warnings","--newline","-o",p];s&&y.unshift("--cookies",s),l&&l.length>0&&y.push("--write-subs","--sub-lang",l.join(",")),y.push(...r),r.some(m=>m===e)||y.push(e);let h=null,v=new Set,f=[];try{let m=Me("yt-dlp",y,{all:!0});if(m.all)for await(let w of m.all){let g=w.toString().trim();if(!g)continue;f.push(g);let k=g.match(/\[download\] Destination:\s*(.+)/);k&&(h=k[1],h&&v.add(h));let E=g.match(/\[info\] Writing video subtitles to:\s*(.+)/);E?.[1]&&v.add(E[1]);let S=g.match(/\[merge\] Merging formats into "(.*)"/);if(S&&(h=S[1],h&&v.add(h)),![/:\s+Downloading webpage$/,/:\s+Downloading m3u8 information$/,/:\s+Downloading m3u8 manifest$/,/Downloading \d+ format\(s\)/,/FixupM3u8/].some(D=>D.test(g))){if(g.startsWith("[info]")||g.startsWith("[ffmpeg]")||g.startsWith("[merge]")||g.startsWith("[ExtractAudio]")||g.startsWith("[Metadata]")||g.startsWith("[Thumbnails]")){u?.(g);continue}if(g.startsWith("[download]")){let D=g.match(/\[download\]\s+(\d+\.?\d*)%\s+of\s+~?\s*([\d.]+\w+)\s+at\s+~?\s*([\d.]+\w+\/s)\s+ETA\s+(\S+)/);if(D){let[,j,ve,W,Ke]=D;c?.(`[download] ${j}% of ${ve} at ${W} ETA ${Ke}`)}else u?.(g);continue}u?.(g)}}if(await m,!h)throw new Error("Could not determine downloaded filename from output");return{filename:h,allFiles:Array.from(v)}}catch(m){let w=m instanceof Error?m.message:String(m),g=f.join(`
5
- `);throw new Error(`yt-dlp failed: ${w}
4
+ `))}var pt="./config.yaml";async function Se(n=pt){let e=ut(process.cwd(),n);if(!ct(e))throw new A(`Configuration file not found: "${e}". Create a config.yaml file or specify a different path.`);let t=await dt(e,"utf-8"),o;try{o=Te.load(t)}catch(r){throw new A(`Failed to parse YAML: ${r instanceof Error?r.message:String(r)}`)}return ke(o),se(o)}function q(n,e){if(!e)return n;let t={...n};for(let o in e)if(Object.hasOwn(e,o)){let i=e[o],r=t[o];De(i)&&De(r)?t[o]=q(r,i):t[o]=i}return t}function De(n){return typeof n=="object"&&n!==null&&!Array.isArray(n)}function S(n){try{return new URL(n).hostname}catch{throw new Error(`Invalid URL: "${n}"`)}}var ft={check:{count:3,checkInterval:600,downloadTypes:[b.AVAILABLE]},download:{downloadDir:"./downloads",tempDir:"./downloads",downloadDelay:10,maxRetries:3,initialTimeout:5,backoffMultiplier:2,jitterPercentage:10,minDuration:0},telegram:{minLevel:a.ERROR},notifications:{consoleMinLevel:a.INFO},stateFile:"wetvlo-state.json",browser:"chrome",playwrightHeadless:!0};function Ne(){return ft}var W=class{map=new Map;seriesByUrl=new Map;constructor(e){let t=Ne(),o=q(t,e.globalConfig);this.setConfig("global",o);for(let i of e.domainConfigs||[]){let r=q(o,i);this.setConfig(`domain:${i.domain}`,r)}for(let i of e.series){let r=S(i.url),s=this.getConfig(`domain:${r}`);if(!s){let c=this.getConfig("global");s=q(c,{domain:r})}let l=q(s,i);this.setConfig(`series:${i.url}`,l),this.seriesByUrl.set(i.url,l)}}getConfig(e){return this.map.get(e)}setConfig(e,t){this.map.set(e,t)}resolve(e,t){if(t==="global"){let r=this.getConfig("global");if(!r)throw new Error("Global configuration not found");return r}if(t==="domain"){let r=S(e),s=this.getConfig(`domain:${r}`);if(!s){let l=this.getConfig("global");if(!l)throw new Error("Global configuration not found");return Object.assign(l,{domain:r})}return s}let o=this.getConfig(`series:${e}`);if(!o)throw new Error(`No configuration found for URL: ${e}`);let i=o;return this.validate(i),i}listSeries(){return Array.from(this.seriesByUrl.values())}listSeriesUrls(){return Array.from(this.seriesByUrl.keys())}listDomains(){let e=new Set;for(let t of this.seriesByUrl.keys())e.add(S(t));return Array.from(e)}validate(e){if(!e.check)throw new Error("Missing check configuration");if(!e.download)throw new Error("Missing download configuration");let{check:t,download:o}=e;if(t.count<1)throw new Error(`Invalid check count: ${t.count}`);if(t.checkInterval<0)throw new Error(`Invalid check interval: ${t.checkInterval}`);if(o.downloadDelay<0)throw new Error(`Invalid download delay: ${o.downloadDelay}`);if(o.maxRetries<0)throw new Error(`Invalid max retries: ${o.maxRetries}`);if(o.initialTimeout<0)throw new Error(`Invalid initial timeout: ${o.initialTimeout}`);if(o.backoffMultiplier<1)throw new Error(`Invalid backoff multiplier: ${o.backoffMultiplier}`);if(o.minDuration<0)throw new Error(`Invalid min duration: ${o.minDuration}`)}};import*as V from"fs";import*as R from"fs/promises";import{basename as U,join as Ae,resolve as Q}from"path";function Re(n){return n.replace(/[<>:"/\\|?*]/g,"_").replace(/[\x00-\x1F]/g,"").replace(/[\s.]+$/,"")}import{execa as gt}from"execa";async function $e(n,e){try{let{stdout:t}=await gt("ffprobe",["-v","error","-show_entries","format=duration","-of","default=noprint_wrappers=1:nokey=1",n]),o=parseFloat(t.trim());return Number.isNaN(o)?0:o}catch(t){let o=`Failed to get video duration for ${n}: ${t instanceof Error?t.message:String(t)}`;return e&&e.notify(a.ERROR,o),0}}function Ie(n){return{downloadDir:n.download.downloadDir,tempDir:n.download.tempDir,cookieFile:n.cookieFile,minDuration:n.download.minDuration,subLangs:n.subLangs}}var ne=class{};import*as Le from"fs/promises";import{join as ht}from"path";import{execa as Me}from"execa";var z=class{async download(e,t,o,i={}){let{args:r=[],cookieFile:s,subLangs:l,onProgress:c,onLog:u}=i,g=ht(o,`${t}.%(ext)s`);await Le.mkdir(o,{recursive:!0});let h=["--no-warnings","--newline","-o",g];s&&h.unshift("--cookies",s),l&&l.length>0&&h.push("--write-subs","--sub-lang",l.join(",")),h.push(...r),r.some(m=>m===e)||h.push(e);let p=null,w=new Set,d=[];try{let m=Me("yt-dlp",h,{all:!0});if(m.all)for await(let v of m.all){let y=v.toString().trim();if(!y)continue;d.push(y);let E=y.match(/\[download\] Destination:\s*(.+)/);E&&(p=E[1],p&&w.add(p));let k=y.match(/\[info\] Writing video subtitles to:\s*(.+)/);k?.[1]&&w.add(k[1]);let T=y.match(/\[merge\] Merging formats into "(.*)"/);if(T&&(p=T[1],p&&w.add(p)),![/:\s+Downloading webpage$/,/:\s+Downloading m3u8 information$/,/:\s+Downloading m3u8 manifest$/,/Downloading \d+ format\(s\)/,/FixupM3u8/].some(D=>D.test(y))){if(y.startsWith("[info]")||y.startsWith("[ffmpeg]")||y.startsWith("[merge]")||y.startsWith("[ExtractAudio]")||y.startsWith("[Metadata]")||y.startsWith("[Thumbnails]")){u?.(y);continue}if(y.startsWith("[download]")){let D=y.match(/\[download\]\s+(\d+\.?\d*)%\s+of\s+~?\s*([\d.]+\w+)\s+at\s+~?\s*([\d.]+\w+\/s)\s+ETA\s+(\S+)/);if(D){let[,P,X,j,_e]=D;c?.(`[download] ${P}% of ${X} at ${j} ETA ${_e}`)}else u?.(y);continue}u?.(y)}}if(await m,!p)throw new Error("Could not determine downloaded filename from output");return{filename:p,allFiles:Array.from(w)}}catch(m){let v=m instanceof Error?m.message:String(m),y=d.join(`
5
+ `);throw new Error(`yt-dlp failed: ${v}
6
6
 
7
7
  Log output:
8
- ${g}`)}}static async checkInstalled(){try{return await Me("yt-dlp",["--version"]),!0}catch{return!1}}};var q=class extends se{wrapper=new z;getName(){return"yt-dlp"}supports(e){return!0}async download(e,t,o,i){return this.wrapper.download(e.url,o,t,{args:[],cookieFile:i?.cookieFile,subLangs:i?.subLangs,onProgress:i?.onProgress,onLog:i?.onLog})}static async checkInstalled(){return z.checkInstalled()}};var he=class{downloaders=[];defaultDownloader;constructor(){this.defaultDownloader=new q}register(e){this.downloaders.push(e)}getDownloader(e){for(let t of this.downloaders)if(t.supports(e))return t;return this.defaultDownloader}},Pe=new he;function yt(n){return n.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}var K=class{stateManager;constructor(){this.stateManager=d.getStateManager()}async download(e,t){let o=d.getNotifier(),r=d.getConfig().resolve(e,"series"),s=r.stateFile,l=r.name,c=Ie(r);if(this.stateManager.isDownloaded(s,l,t.number))return!1;let u=Pe.getDownloader(t.url),p=String(t.number).padStart(2,"0");o.notify(a.HIGHLIGHT,`${l} - ${p}: downloading`);let h=`${Ne(l)} - ${p}`,v=c.tempDir||c.downloadDir;try{await this.cleanupEpisodeArtifacts(v,h);let f=await u.download(t,v,h,{cookieFile:c.cookieFile,subLangs:c.subLangs,onProgress:w=>o.progress(w),onLog:w=>o.notify(a.INFO,w)});o.endProgress();let m=this.verifyDownload(f.filename);if(m===0)throw await this.cleanupFiles(f.allFiles),new Error("Downloaded file is empty or does not exist");if(c.minDuration>0){let w=H(f.filename),g=await $e(w,o);if(g<c.minDuration)throw await this.cleanupFiles(f.allFiles),new Error(`Video duration ${g}s is less than minimum ${c.minDuration}s`)}if(c.tempDir&&c.tempDir!==c.downloadDir){o.notify(a.INFO,`Moving to ${c.downloadDir}...`),await N.mkdir(c.downloadDir,{recursive:!0});for(let w of f.allFiles)try{let g=H(w);if(!Q.existsSync(g)){o.notify(a.WARNING,`Skip: ${A(g)}`);continue}let k=A(g),E=Ae(c.downloadDir,k);await N.rename(g,E),g===H(f.filename)&&(f.filename=E)}catch{o.notify(a.ERROR,`Move failed: ${A(w)}`)}}return await this.stateManager.addDownloadedEpisode(s,l,t.number),o.notify(a.SUCCESS,`${l} - ${p}: ${A(f.filename)} (${this.formatSize(m)})`),!0}catch(f){o.endProgress(),await this.cleanupEpisodeArtifacts(v,h);let m=`${l} - ${p}: ${f instanceof Error?f.message:String(f)}`;throw o.notify(a.ERROR,m),new ee(m,t.url)}}async cleanupFiles(e){for(let t of e)try{let o=H(t);Q.existsSync(o)&&await N.unlink(o)}catch(o){d.getNotifier().notify(a.DEBUG,`Delete failed: ${A(t)} - ${o}`)}}async cleanupEpisodeArtifacts(e,t){let o=d.getNotifier();try{let i=H(e);if(!Q.existsSync(i))return;let r=await N.readdir(i),s=new RegExp(`^${yt(t)}\\..*$`),l=0;for(let c of r)if(s.test(c)){let u=Ae(i,c);try{await N.unlink(u),l++,d.getNotifier().notify(a.DEBUG,`Cleaned artifact: ${A(c)}`)}catch(p){d.getNotifier().notify(a.DEBUG,`Delete artifact failed: ${A(c)} - ${p}`)}}l>0&&o.notify(a.INFO,`Cleaned ${l} artifacts`)}catch(i){o.notify(a.WARNING,`Cleanup failed: ${i}`)}}verifyDownload(e){let t=H(e);try{return Q.statSync(t).size}catch{return 0}}formatSize(e){let t=["B","KB","MB","GB"],o=e,i=0;for(;o>=1024&&i<t.length-1;)o/=1024,i++;return`${o.toFixed(2)} ${t[i]}`}static async checkYtDlpInstalled(){return q.checkInstalled()}};var ye=class{handlers=new Map;register(e){this.handlers.set(e.getDomain(),e)}getHandler(e){let t=T(e);if(this.handlers.has(t))return this.handlers.get(t);for(let[o,i]of this.handlers.entries())if(t===o||t.endsWith(`.${o}`)||o.endsWith(`.${t}`))return i}getDomains(){return Array.from(this.handlers.keys())}getHandlerOrThrow(e){let t=this.getHandler(e);if(!t)throw new M(`No handler found for domain: "${T(e)}". Supported domains: ${this.getDomains().join(", ")}`,e);return t}},U=new ye;import*as Ue from"cheerio";var L=class{supports(e){try{let t=T(e);return t===this.getDomain()||t.endsWith(`.${this.getDomain()}`)}catch{return!1}}async fetchHtml(e,t){let o={"User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",Accept:"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8","Accept-Language":"en-US,en;q=0.9"};t&&(o.Cookie=t);try{let i=await fetch(e,{headers:o});if(!i.ok)throw new M(`HTTP ${i.status}: ${i.statusText}`,e);return await i.text()}catch(i){throw i instanceof M?i:new M(`Failed to fetch page: ${i instanceof Error?i.message:String(i)}`,e)}}parseHtml(e){return Ue.load(e)}parseEpisodeNumber(e){let t=e.match(/第(\d+)集/);if(t?.[1])return parseInt(t[1],10);let o=e.match(/ep\s?(\d+)/i);if(o?.[1])return parseInt(o[1],10);let i=e.match(/(?:episode|e)\s?(\d+)/i);if(i?.[1])return parseInt(i[1],10);let r=e.match(/\b(\d+)\b/);return r?.[1]?parseInt(r[1],10):null}parseEpisodeType(e,t){let o=t(e),i=o.attr("class")||"",r=o.text().toLowerCase();return i.includes("vip")||r.includes("vip")||r.includes("\u4F1A\u5458")?"vip":i.includes("preview")||i.includes("trailer")||r.includes("preview")||r.includes("\u9884\u544A")?"preview":i.includes("locked")||i.includes("lock")||r.includes("locked")||r.includes("\u9501\u5B9A")?"locked":"available"}};var ne=class extends L{getDomain(){return"iq.com"}async extractEpisodes(e,t){let o=await this.fetchHtml(e,t),i=this.extractFromNextData(o);return i.length>0?i:this.extractFromHtml(o)}extractFromNextData(e){let t=[];try{let o=e.match(/<script id="__NEXT_DATA__"[^>]*>(.+?)<\/script>/s);if(!o||!o[1])return t;let r=JSON.parse(o[1]).props?.pageProps?.data;if(!r)return t;let s=JSON.parse(r),{albumInfo:l,videoList:c=[]}=s,{albumId:u,title:p}=l||{};for(let y of c){let{vid:h,episode:v,order:f,isTrailer:m,subTitle:w,name:g,title:k}=y;if(m)continue;let E=f;if(E||(E=this.parseEpisodeNumber(v||w||g||k||"")),!E)continue;let S=`https://www.iq.com/play/${u}-${h}?lang=en_us`,R=this.determineType(y),D=w||g||k||(v?`Episode ${v}`:void 0),j=p&&D?`${p} - ${D}`:D;t.push({number:E,url:S,type:R,title:j,extractedAt:new Date})}}catch(o){console.error("Failed to extract from __NEXT_DATA__:",o)}return t.sort((o,i)=>o.number-i.number),t}determineType(e){let{payStatus:t,payMark:o,episodeType:i}=e;return o==="preview"||i===1?x.PREVIEW:o==="VIP_MARK"||t===6?x.VIP:x.AVAILABLE}extractFromHtml(e){let t=this.parseHtml(e),o=[],i=['ul li a[href*="/play/"]','.album-episode-item a[href*="/play/"]','.episode-item a[href*="/play/"]','.intl-play-item a[href*="/play/"]','[data-episode] a[href*="/play/"]'];for(let r of i){let s=t(r);if(s.length>0&&(s.each((l,c)=>{this.processEpisodeLink(t,c,o)}),o.length>0))break}return o.sort((r,s)=>r.number-s.number),o}processEpisodeLink(e,t,o){let i=e(t),r=i.attr("href");if(!r)return;let s=r.startsWith("http")?r:`https://www.iq.com${r}`,l=i.text().trim(),c=i.attr("title")||void 0;if(l.toUpperCase().includes("BTS"))return;let u=this.parseEpisodeNumber(l);if(u||(u=this.parseEpisodeNumber(r)),!u||o.some(h=>h.number===u))return;let y=this.determineEpisodeType(e,t);o.push({number:u,url:s,type:y,title:c,extractedAt:new Date})}determineEpisodeType(e,t){let o=e(t).closest("li, div");return o.length&&(o.text()||"").toUpperCase().includes("VIP")?x.VIP:x.AVAILABLE}};var ae=class extends L{getDomain(){return"mgtv.com"}async extractEpisodes(e,t){let o=this.extractVideoId(e);if(!o)throw new Error("Could not extract video ID from URL");let i=[],r=0,s=1;do{let l=`https://tinker.glb.mgtv.com/episode/list?src=intelmgtv&abroad=10&_support=10000000&version=5.5.35&video_id=${o}&page=${r}&size=50&platform=4`,c=await this.fetchHtml(l,t),u;try{u=JSON.parse(c)}catch{throw new Error("Failed to parse MGTV API response")}if(u.code!==200)throw new Error(`MGTV API error: ${u.msg}`);s=u.data.total_page;for(let p of u.data.list){let y=this.parseEpisodeNumber(p.t1);if(!y)continue;let h=`https://w.mgtv.com${p.url}`;i.push({number:y,title:p.t2||p.t4||`Episode ${y}`,url:h,type:p.isvip==="1"?x.VIP:x.AVAILABLE,extractedAt:new Date})}r++}while(r<s);return this.deduplicateEpisodes(i)}extractVideoId(e){let t=e.match(/\/b\/\d+\/(\d+)\.html/);return t&&t[1]||null}deduplicateEpisodes(e){let t=new Map;for(let o of e)t.has(o.number)||t.set(o.number,o);return Array.from(t.values()).sort((o,i)=>o.number-i.number)}};var le=class extends L{getDomain(){return"wetv.vip"}async extractEpisodes(e,t){let o=await this.fetchHtml(e,t),i=this.extractFromNextData(o);return i.length>0?i:this.extractFromHtml(o)}extractFromNextData(e){let t=[];try{let o=e.match(/<script id="__NEXT_DATA__"[^>]*>(.+?)<\/script>/s);if(!o||!o[1])return t;let r=JSON.parse(o[1]).props?.pageProps?.data;if(!r)return t;let s=JSON.parse(r),{coverInfo:l,videoList:c=[]}=s,{cid:u,title:p}=l,y=c[0]?.coverList?.[0]||u;for(let h of c){let{vid:v,episode:f,isTrailer:m}=h;if(m)continue;let w=this.parseEpisodeNumber(f);if(!w)continue;let g=encodeURIComponent(p),k=`https://wetv.vip/en/play/${y}/${v}-EP${f}%3A${g}`,E=this.determineTypeFromVideo(h);t.push({number:w,url:k,type:E,title:`${p} - Episode ${f}`,extractedAt:new Date})}}catch(o){console.error("Failed to extract from __NEXT_DATA__:",o)}return t.sort((o,i)=>o.number-i.number),t}extractFromHtml(e){let t=this.parseHtml(e),o=[],i=t('a.play-video__link[href*="/play/"][href*="EP"]');return i.length===0?t('a[href*="/play/"]').filter((s,l)=>(t(l).attr("href")||"").includes("EP")).each((s,l)=>{this.processEpisodeLink(t,l,o)}):i.each((r,s)=>{this.processEpisodeLink(t,s,o)}),o.sort((r,s)=>r.number-s.number),o}determineTypeFromVideo(e){let{labels:t,payStatus:o,defaultPayStatus:i}=e;if(t)for(let s in t){let l=t[s];if(!l)continue;let c=l.text?.toLowerCase()||"";if(c==="express")return x.EXPRESS;if(c==="teaser")return x.TEASER;if(c==="vip")return x.VIP}let r=o||i;return r===6?x.VIP:r===12?x.EXPRESS:x.AVAILABLE}processEpisodeLink(e,t,o){let i=e(t),r=i.attr("href");if(!r)return;let s=r.startsWith("http")?r:`https://wetv.vip${r}`,l=i.attr("aria-label")||"",c=this.parseEpisodeNumber(l);if(!c||o.some(y=>y.number===c))return;let p=this.determineEpisodeType(e,t);o.push({number:c,url:s,type:p,title:i.attr("title")||void 0,extractedAt:new Date})}determineEpisodeType(e,t){let o=e(t).closest("li");if(o.length){let i=o.find("span.play-video__label").first();if(i.length){let s=i.text().trim().toLowerCase();if(s==="vip"||s.includes("vip"))return x.VIP;if(s==="teaser"||s.includes("teaser"))return x.TEASER;if(s==="express"||s.includes("express"))return x.EXPRESS}let r=o.text()||"";if(r.includes("VIP")&&!r.includes("Teaser"))return x.VIP;if(r.includes("Teaser"))return x.TEASER;if(r.includes("Express"))return x.EXPRESS}return x.AVAILABLE}};var _=class{notifiers=[];add(e,t=0){this.notifiers.push({notifier:e,priority:t}),this.sort()}remove(e){this.notifiers=this.notifiers.filter(t=>t.notifier!==e)}sort(){this.notifiers.sort((e,t)=>t.priority-e.priority)}async notify(e,t){await Promise.all(this.notifiers.map(async({notifier:o})=>{try{await o.notify(e,t)}catch(i){console.error(`Notifier error: ${i instanceof Error?i.message:String(i)}`)}}))}progress(e){for(let{notifier:t}of this.notifiers)try{t.progress(e)}catch(o){console.error(`Progress error: ${o instanceof Error?o.message:String(o)}`)}}endProgress(){for(let{notifier:e}of this.notifiers)try{e.endProgress()}catch(t){console.error(`End progress error: ${t instanceof Error?t.message:String(t)}`)}}};var C={DEBUG:"DEBUG",INFO:"INFO",SUCCESS:"SUCCESS",WARNING:"WARNING",ERROR:"ERROR",HIGHLIGHT:"HIGHLIGHT"},$={reset:"\x1B[0m",bright:"\x1B[1m",dim:"\x1B[2m",black:"\x1B[30m",red:"\x1B[31m",green:"\x1B[32m",yellow:"\x1B[33m",blue:"\x1B[34m",magenta:"\x1B[35m",cyan:"\x1B[36m",white:"\x1B[37m",bgRed:"\x1B[41m",bgGreen:"\x1B[42m",bgYellow:"\x1B[43m"},we=class{config;constructor(e={}){this.config={level:e.level??C.INFO,useColors:e.useColors??!0}}getEmoji(e){switch(e){case C.DEBUG:return"\u{1F50D}";case C.INFO:return"\u2139\uFE0F";case C.SUCCESS:return"\u2705";case C.WARNING:return"\u26A0\uFE0F";case C.ERROR:return"\u274C";case C.HIGHLIGHT:return"\u{1F31F}";default:return"\u2022"}}formatDate(e){let t=(e.getMonth()+1).toString().padStart(2,"0"),o=e.getDate().toString().padStart(2,"0"),i=e.getHours().toString().padStart(2,"0"),r=e.getMinutes().toString().padStart(2,"0"),s=e.getSeconds().toString().padStart(2,"0");return`${t}-${o} ${i}:${r}:${s}`}format(e,t){let o=this.formatDate(new Date),i=this.getEmoji(e);return`${o} ${i} ${t}`}colorize(e,t){return this.config.useColors?`${t}${e}${$.reset}`:e}debug(e){this.shouldLog(C.DEBUG)&&console.log(this.format(C.DEBUG,this.colorize(e,$.dim)))}info(e){this.shouldLog(C.INFO)&&console.log(this.format(C.INFO,this.colorize(e,$.dim+$.white)))}success(e){this.shouldLog(C.SUCCESS)&&console.log(this.format(C.SUCCESS,this.colorize(e,$.green)))}warning(e){this.shouldLog(C.WARNING)&&console.log(this.format(C.WARNING,this.colorize(e,$.yellow)))}error(e){this.shouldLog(C.ERROR)&&console.error(this.format(C.ERROR,this.colorize(e,$.red)))}highlight(e){this.shouldLog(C.HIGHLIGHT)&&console.log(this.format(C.HIGHLIGHT,this.colorize(e,$.bright+$.magenta)))}shouldLog(e){let t=[C.DEBUG,C.INFO,C.SUCCESS,C.WARNING,C.ERROR,C.HIGHLIGHT];return t.indexOf(e)>=t.indexOf(this.config.level)}setLevel(e){this.config.level=e}},F=new we;var Y=class{lastProgressLength=0;minLevel;constructor(e=a.INFO){this.minLevel=e}shouldNotify(e){return G[e]>=G[this.minLevel]}notify(e,t){if(this.shouldNotify(e))switch(this.lastProgressLength>0&&(process.stdout.write(`\r${" ".repeat(this.lastProgressLength)}\r`),this.lastProgressLength=0),e){case a.DEBUG:F.debug(t);break;case a.INFO:F.info(t);break;case a.SUCCESS:F.success(t);break;case a.WARNING:F.warning(t);break;case a.ERROR:F.error(t);break;case a.HIGHLIGHT:F.highlight(t);break}}progress(e){this.lastProgressLength>0&&process.stdout.write(`\r${" ".repeat(this.lastProgressLength)}\r`),process.stdout.write(`\r${e}`),this.lastProgressLength=e.length}endProgress(){this.lastProgressLength>0&&(process.stdout.write(`
9
- `),this.lastProgressLength=0)}};var X=class{config;apiUrl;constructor(e){this.config=e,this.apiUrl=`https://api.telegram.org/bot${e.botToken}/sendMessage`}shouldNotify(e){let t=this.config.minLevel??a.ERROR;return G[e]>=G[t]}async notify(e,t){if(this.shouldNotify(e))try{let o=this.getEmoji(e),i=4e3,r=t;r.length>i&&(r=`${r.substring(0,i)}
8
+ ${y}`)}}static async checkInstalled(){try{return await Me("yt-dlp",["--version"]),!0}catch{return!1}}};var H=class extends ne{wrapper=new z;getName(){return"yt-dlp"}supports(e){return!0}async download(e,t,o,i){return this.wrapper.download(e.url,o,t,{args:[],cookieFile:i?.cookieFile,subLangs:i?.subLangs,onProgress:i?.onProgress,onLog:i?.onLog})}static async checkInstalled(){return z.checkInstalled()}};var ye=class{downloaders=[];defaultDownloader;constructor(){this.defaultDownloader=new H}register(e){this.downloaders.push(e)}getDownloader(e){for(let t of this.downloaders)if(t.supports(e))return t;return this.defaultDownloader}},Pe=new ye;function yt(n){return n.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}var _=class{stateManager;constructor(){this.stateManager=f.getStateManager()}async download(e,t){let o=f.getNotifier(),r=f.getConfig().resolve(e,"series"),s=r.stateFile,l=r.name,c=Ie(r);if(this.stateManager.isDownloaded(s,l,t.number))return!1;let u=Pe.getDownloader(t.url),g=String(t.number).padStart(2,"0");o.notify(a.HIGHLIGHT,`${l} - ${g}: downloading`);let p=`${Re(l)} - ${g}`,w=c.tempDir||c.downloadDir;try{await this.cleanupEpisodeArtifacts(w,p);let d=await u.download(t,w,p,{cookieFile:c.cookieFile,subLangs:c.subLangs,onProgress:v=>o.progress(v),onLog:v=>o.notify(a.INFO,v)});o.endProgress();let m=this.verifyDownload(d.filename);if(m===0)throw await this.cleanupFiles(d.allFiles),new Error("Downloaded file is empty or does not exist");if(c.minDuration>0){let v=Q(d.filename),y=await $e(v,o);if(y<c.minDuration)throw await this.cleanupFiles(d.allFiles),new Error(`Video duration ${y}s is less than minimum ${c.minDuration}s`)}if(c.tempDir&&c.tempDir!==c.downloadDir){o.notify(a.INFO,`Moving to ${c.downloadDir}...`),await R.mkdir(c.downloadDir,{recursive:!0});for(let v of d.allFiles)try{let y=Q(v);if(!V.existsSync(y)){o.notify(a.WARNING,`Skip: ${U(y)}`);continue}let E=U(y),k=Ae(c.downloadDir,E);await R.rename(y,k),y===Q(d.filename)&&(d.filename=k)}catch{o.notify(a.ERROR,`Move failed: ${U(v)}`)}}return await this.stateManager.addDownloadedEpisode(s,l,t.number),o.notify(a.SUCCESS,`${l} - ${g}: ${U(d.filename)} (${this.formatSize(m)})`),!0}catch(d){o.endProgress(),await this.cleanupEpisodeArtifacts(w,p);let m=`${l} - ${g}: ${d instanceof Error?d.message:String(d)}`;throw o.notify(a.ERROR,m),new te(m,t.url)}}async cleanupFiles(e){for(let t of e)try{let o=Q(t);V.existsSync(o)&&await R.unlink(o)}catch(o){f.getNotifier().notify(a.DEBUG,`Delete failed: ${U(t)} - ${o}`)}}async cleanupEpisodeArtifacts(e,t){let o=f.getNotifier();try{let i=Q(e);if(!V.existsSync(i))return;let r=await R.readdir(i),s=new RegExp(`^${yt(t)}\\..*$`),l=0;for(let c of r)if(s.test(c)){let u=Ae(i,c);try{await R.unlink(u),l++,f.getNotifier().notify(a.DEBUG,`Cleaned artifact: ${U(c)}`)}catch(g){f.getNotifier().notify(a.DEBUG,`Delete artifact failed: ${U(c)} - ${g}`)}}l>0&&o.notify(a.INFO,`Cleaned ${l} artifacts`)}catch(i){o.notify(a.WARNING,`Cleanup failed: ${i}`)}}verifyDownload(e){let t=Q(e);try{return V.statSync(t).size}catch{return 0}}formatSize(e){let t=["B","KB","MB","GB"],o=e,i=0;for(;o>=1024&&i<t.length-1;)o/=1024,i++;return`${o.toFixed(2)} ${t[i]}`}static async checkYtDlpInstalled(){return H.checkInstalled()}};var we=class{handlers=new Map;register(e){this.handlers.set(e.getDomain(),e)}getHandler(e){let t=S(e);if(this.handlers.has(t))return this.handlers.get(t);for(let[o,i]of this.handlers.entries())if(t===o||t.endsWith(`.${o}`)||o.endsWith(`.${t}`))return i}getDomains(){return Array.from(this.handlers.keys())}getHandlerOrThrow(e){let t=this.getHandler(e);if(!t)throw new M(`No handler found for domain: "${S(e)}". Supported domains: ${this.getDomains().join(", ")}`,e);return t}},F=new we;import*as Ue from"cheerio";var L=class{supports(e){try{let t=S(e);return t===this.getDomain()||t.endsWith(`.${this.getDomain()}`)}catch{return!1}}async fetchHtml(e,t){let o={"User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",Accept:"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8","Accept-Language":"en-US,en;q=0.9"};t&&(o.Cookie=t);try{let i=await fetch(e,{headers:o});if(!i.ok)throw new M(`HTTP ${i.status}: ${i.statusText}`,e);return await i.text()}catch(i){throw i instanceof M?i:new M(`Failed to fetch page: ${i instanceof Error?i.message:String(i)}`,e)}}parseHtml(e){return Ue.load(e)}parseEpisodeNumber(e){let t=e.match(/第(\d+)集/);if(t?.[1])return parseInt(t[1],10);let o=e.match(/ep\s?(\d+)/i);if(o?.[1])return parseInt(o[1],10);let i=e.match(/(?:episode|e)\s?(\d+)/i);if(i?.[1])return parseInt(i[1],10);let r=e.match(/\b(\d+)\b/);return r?.[1]?parseInt(r[1],10):null}parseEpisodeType(e,t){let o=t(e),i=o.attr("class")||"",r=o.text().toLowerCase();return i.includes("vip")||r.includes("vip")||r.includes("\u4F1A\u5458")?"vip":i.includes("preview")||i.includes("trailer")||r.includes("preview")||r.includes("\u9884\u544A")?"preview":i.includes("locked")||i.includes("lock")||r.includes("locked")||r.includes("\u9501\u5B9A")?"locked":"available"}};var ae=class extends L{API_BASE="https://pcw-api.iq.com/api/v2";PAGE_SIZE=50;getDomain(){return"iq.com"}async extractEpisodes(e,t){let o=await this.fetchHtml(e,t),i=await this.extractFromNextData(o);return i.length>0?i:this.extractFromHtml(o)}async extractFromNextData(e){let t=[];try{let o=e.match(/<script id="__NEXT_DATA__"[^>]*>(.+?)<\/script>/s);if(!o||!o[1])return t;let i=JSON.parse(o[1]),r=i.props?.initialState?.play?.cachePlayList;if(r){let l=Object.keys(r).find(c=>Array.isArray(r[c]));if(l){let c=r[l];if(Array.isArray(c))for(let u of c){let{albumPlayUrl:g,episode:h,order:p,subTitle:w,name:d,title:m}=u;if(!g)continue;let v=p;if(v||(v=this.parseEpisodeNumber(h||w||d||m||"")),!v)continue;let y=g.startsWith("//")?`https:${g}`:g,E=this.determineTypeFromCache(u),T=w||d||m||(h?`Episode ${h}`:void 0);t.push({number:v,url:y,type:E,title:T,extractedAt:new Date})}}}if(t.length>0){let l=this.extractAlbumId(e),c=this.extractTotalCount(e);if(l&&c&&c>t.length){console.log(`[iq.com] Found ${t.length} episodes in cache, fetching ${c-t.length} more from API...`);let u=await this.fetchEpisodesFromAPI(l,t.length+1,c);t.push(...u)}return t.sort((u,g)=>u.number-g.number),t}let s=i.props?.pageProps?.data;if(s){let l=JSON.parse(s),{albumInfo:c,videoList:u=[]}=l,{albumId:g,title:h}=c||{};for(let p of u){let{vid:w,episode:d,order:m,isTrailer:v,subTitle:y,name:E,title:k}=p;if(v)continue;let T=m;if(T||(T=this.parseEpisodeNumber(d||y||E||k||"")),!T)continue;let N=`https://www.iq.com/play/${g}-${w}?lang=en_us`,D=this.determineType(p),P=y||E||k||(d?`Episode ${d}`:void 0),X=h&&P?`${h} - ${P}`:P;t.push({number:T,url:N,type:D,title:X,extractedAt:new Date})}}}catch(o){console.error("Failed to extract from __NEXT_DATA__:",o)}return t.sort((o,i)=>o.number-i.number),t}determineType(e){let{payStatus:t,payMark:o,episodeType:i}=e;return o==="preview"||i===1?b.PREVIEW:o==="VIP_MARK"||t===6?b.VIP:b.AVAILABLE}determineTypeFromCache(e){let{isVip:t,payMark:o,payStatus:i,episodeType:r}=e;return o==="preview"||r===1?b.PREVIEW:t===1||t===!0||o==="VIP_MARK"||i===6?b.VIP:b.AVAILABLE}extractAlbumId(e){return e.match(/"albumId"\s*:\s*"?(\d+)"?/)?.[1]??null}extractTotalCount(e){let t=e.match(/"albumLocSuffix"[^}]*"total"\s*:\s*(\d+)/);return t?.[1]?parseInt(t[1],10):null}async fetchEpisodesFromAPI(e,t,o){let i=[],r=this.PAGE_SIZE;for(let s=t;s<=o;s+=r){let l=Math.min(s+r-1,o);try{let c=`${this.API_BASE}/episodeListSource/${e}?platformId=3&modeCode=intl&langCode=en_us&startOrder=${s}&endOrder=${l}&isVip=false`,g=await(await fetch(c)).text(),h=JSON.parse(g);if(h?.data?.epg&&Array.isArray(h.data.epg))for(let p of h.data.epg){let w=p.order;if(!w)continue;let d=p.playLocSuffix;if(!d)continue;let m=d.startsWith("/")?`https://www.iq.com${d}`:`https://www.iq.com/play/${d}`,v=p.vipInfo?.isVip===1||p.vipInfo?.isVip===!0;i.push({number:w,url:m,type:v?b.VIP:b.AVAILABLE,title:p.name||p.shortName||void 0,extractedAt:new Date})}else if(h?.data?.episodes)for(let p of h.data.episodes){let w=p.order||this.parseEpisodeNumber(p.episode||p.title||"");if(!w)continue;let d=p.playUrl||p.albumPlayUrl;if(!d)continue;let m=d.startsWith("//")?`https:${d}`:d;i.push({number:w,url:m,type:p.isVip===1?b.VIP:b.AVAILABLE,title:p.title||p.subtitle||void 0,extractedAt:new Date})}}catch(c){console.error(`[iq.com] Failed to fetch episodes ${s}-${l}:`,c)}}return i}extractFromHtml(e){let t=this.parseHtml(e),o=[],i=['ul li a[href*="/play/"]','.album-episode-item a[href*="/play/"]','.episode-item a[href*="/play/"]','.intl-play-item a[href*="/play/"]','[data-episode] a[href*="/play/"]'];for(let r of i){let s=t(r);if(s.length>0&&(s.each((l,c)=>{this.processEpisodeLink(t,c,o)}),o.length>0))break}return o.sort((r,s)=>r.number-s.number),o}processEpisodeLink(e,t,o){let i=e(t),r=i.attr("href");if(!r)return;let s=r.startsWith("http")?r:`https://www.iq.com${r}`,l=i.text().trim(),c=i.attr("title")||void 0;if(l.toUpperCase().includes("BTS"))return;let u=this.parseEpisodeNumber(l);if(u||(u=this.parseEpisodeNumber(r)),!u||o.some(p=>p.number===u))return;let h=this.determineEpisodeType(e,t);o.push({number:u,url:s,type:h,title:c,extractedAt:new Date})}determineEpisodeType(e,t){let o=e(t).closest("li, div");return o.length&&(o.text()||"").toUpperCase().includes("VIP")?b.VIP:b.AVAILABLE}};var le=class extends L{getDomain(){return"mgtv.com"}async extractEpisodes(e,t){let o=this.extractVideoId(e);if(!o)throw new Error("Could not extract video ID from URL");let i=[],r=0,s=1;do{let l=`https://tinker.glb.mgtv.com/episode/list?src=intelmgtv&abroad=10&_support=10000000&version=5.5.35&video_id=${o}&page=${r}&size=50&platform=4`,c=await this.fetchHtml(l,t),u;try{u=JSON.parse(c)}catch{throw new Error("Failed to parse MGTV API response")}if(u.code!==200)throw new Error(`MGTV API error: ${u.msg}`);s=u.data.total_page;for(let g of u.data.list){let h=this.parseEpisodeNumber(g.t1);if(!h)continue;let p=`https://w.mgtv.com${g.url}`;i.push({number:h,title:g.t2||g.t4||`Episode ${h}`,url:p,type:g.isvip==="1"?b.VIP:b.AVAILABLE,extractedAt:new Date})}r++}while(r<s);return this.deduplicateEpisodes(i)}extractVideoId(e){let t=e.match(/\/b\/\d+\/(\d+)\.html/);return t&&t[1]||null}deduplicateEpisodes(e){let t=new Map;for(let o of e)t.has(o.number)||t.set(o.number,o);return Array.from(t.values()).sort((o,i)=>o.number-i.number)}};var ce=class extends L{getDomain(){return"wetv.vip"}async extractEpisodes(e,t){let o=await this.fetchHtml(e,t),i=this.extractFromNextData(o);return i.length>0?i:this.extractFromHtml(o)}extractFromNextData(e){let t=[];try{let o=e.match(/<script id="__NEXT_DATA__"[^>]*>(.+?)<\/script>/s);if(!o||!o[1])return t;let r=JSON.parse(o[1]).props?.pageProps?.data;if(!r)return t;let s=JSON.parse(r),{coverInfo:l,videoList:c=[]}=s,{cid:u,title:g}=l,h=c[0]?.coverList?.[0]||u;for(let p of c){let{vid:w,episode:d,isTrailer:m}=p;if(m)continue;let v=this.parseEpisodeNumber(d);if(!v)continue;let y=encodeURIComponent(g),E=`https://wetv.vip/en/play/${h}/${w}-EP${d}%3A${y}`,k=this.determineTypeFromVideo(p);t.push({number:v,url:E,type:k,title:`${g} - Episode ${d}`,extractedAt:new Date})}}catch(o){console.error("Failed to extract from __NEXT_DATA__:",o)}return t.sort((o,i)=>o.number-i.number),t}extractFromHtml(e){let t=this.parseHtml(e),o=[],i=t('a.play-video__link[href*="/play/"][href*="EP"]');return i.length===0?t('a[href*="/play/"]').filter((s,l)=>(t(l).attr("href")||"").includes("EP")).each((s,l)=>{this.processEpisodeLink(t,l,o)}):i.each((r,s)=>{this.processEpisodeLink(t,s,o)}),o.sort((r,s)=>r.number-s.number),o}determineTypeFromVideo(e){let{labels:t,payStatus:o,defaultPayStatus:i}=e;if(t)for(let s in t){let l=t[s];if(!l)continue;let c=l.text?.toLowerCase()||"";if(c==="express")return b.EXPRESS;if(c==="teaser")return b.TEASER;if(c==="vip")return b.VIP}let r=o||i;return r===6?b.VIP:r===12?b.EXPRESS:b.AVAILABLE}processEpisodeLink(e,t,o){let i=e(t),r=i.attr("href");if(!r)return;let s=r.startsWith("http")?r:`https://wetv.vip${r}`,l=i.attr("aria-label")||"",c=this.parseEpisodeNumber(l);if(!c||o.some(h=>h.number===c))return;let g=this.determineEpisodeType(e,t);o.push({number:c,url:s,type:g,title:i.attr("title")||void 0,extractedAt:new Date})}determineEpisodeType(e,t){let o=e(t).closest("li");if(o.length){let i=o.find("span.play-video__label").first();if(i.length){let s=i.text().trim().toLowerCase();if(s==="vip"||s.includes("vip"))return b.VIP;if(s==="teaser"||s.includes("teaser"))return b.TEASER;if(s==="express"||s.includes("express"))return b.EXPRESS}let r=o.text()||"";if(r.includes("VIP")&&!r.includes("Teaser"))return b.VIP;if(r.includes("Teaser"))return b.TEASER;if(r.includes("Express"))return b.EXPRESS}return b.AVAILABLE}};var K=class{notifiers=[];add(e,t=0){this.notifiers.push({notifier:e,priority:t}),this.sort()}remove(e){this.notifiers=this.notifiers.filter(t=>t.notifier!==e)}sort(){this.notifiers.sort((e,t)=>t.priority-e.priority)}async notify(e,t){await Promise.all(this.notifiers.map(async({notifier:o})=>{try{await o.notify(e,t)}catch(i){console.error(`Notifier error: ${i instanceof Error?i.message:String(i)}`)}}))}progress(e){for(let{notifier:t}of this.notifiers)try{t.progress(e)}catch(o){console.error(`Progress error: ${o instanceof Error?o.message:String(o)}`)}}endProgress(){for(let{notifier:e}of this.notifiers)try{e.endProgress()}catch(t){console.error(`End progress error: ${t instanceof Error?t.message:String(t)}`)}}};var C={DEBUG:"DEBUG",INFO:"INFO",SUCCESS:"SUCCESS",WARNING:"WARNING",ERROR:"ERROR",HIGHLIGHT:"HIGHLIGHT"},$={reset:"\x1B[0m",bright:"\x1B[1m",dim:"\x1B[2m",black:"\x1B[30m",red:"\x1B[31m",green:"\x1B[32m",yellow:"\x1B[33m",blue:"\x1B[34m",magenta:"\x1B[35m",cyan:"\x1B[36m",white:"\x1B[37m",bgRed:"\x1B[41m",bgGreen:"\x1B[42m",bgYellow:"\x1B[43m"},ve=class{config;constructor(e={}){this.config={level:e.level??C.INFO,useColors:e.useColors??!0}}getEmoji(e){switch(e){case C.DEBUG:return"\u{1F50D}";case C.INFO:return"\u2139\uFE0F";case C.SUCCESS:return"\u2705";case C.WARNING:return"\u26A0\uFE0F";case C.ERROR:return"\u274C";case C.HIGHLIGHT:return"\u{1F31F}";default:return"\u2022"}}formatDate(e){let t=(e.getMonth()+1).toString().padStart(2,"0"),o=e.getDate().toString().padStart(2,"0"),i=e.getHours().toString().padStart(2,"0"),r=e.getMinutes().toString().padStart(2,"0"),s=e.getSeconds().toString().padStart(2,"0");return`${t}-${o} ${i}:${r}:${s}`}format(e,t){let o=this.formatDate(new Date),i=this.getEmoji(e);return`${o} ${i} ${t}`}colorize(e,t){return this.config.useColors?`${t}${e}${$.reset}`:e}debug(e){this.shouldLog(C.DEBUG)&&console.log(this.format(C.DEBUG,this.colorize(e,$.dim)))}info(e){this.shouldLog(C.INFO)&&console.log(this.format(C.INFO,this.colorize(e,$.dim+$.white)))}success(e){this.shouldLog(C.SUCCESS)&&console.log(this.format(C.SUCCESS,this.colorize(e,$.green)))}warning(e){this.shouldLog(C.WARNING)&&console.log(this.format(C.WARNING,this.colorize(e,$.yellow)))}error(e){this.shouldLog(C.ERROR)&&console.error(this.format(C.ERROR,this.colorize(e,$.red)))}highlight(e){this.shouldLog(C.HIGHLIGHT)&&console.log(this.format(C.HIGHLIGHT,this.colorize(e,$.bright+$.magenta)))}shouldLog(e){let t=[C.DEBUG,C.INFO,C.SUCCESS,C.WARNING,C.ERROR,C.HIGHLIGHT];return t.indexOf(e)>=t.indexOf(this.config.level)}setLevel(e){this.config.level=e}},O=new ve;var Y=class{lastProgressLength=0;minLevel;constructor(e=a.INFO){this.minLevel=e}shouldNotify(e){return B[e]>=B[this.minLevel]}notify(e,t){if(this.shouldNotify(e))switch(this.lastProgressLength>0&&(process.stdout.write(`\r${" ".repeat(this.lastProgressLength)}\r`),this.lastProgressLength=0),e){case a.DEBUG:O.debug(t);break;case a.INFO:O.info(t);break;case a.SUCCESS:O.success(t);break;case a.WARNING:O.warning(t);break;case a.ERROR:O.error(t);break;case a.HIGHLIGHT:O.highlight(t);break}}progress(e){this.lastProgressLength>0&&process.stdout.write(`\r${" ".repeat(this.lastProgressLength)}\r`),process.stdout.write(`\r${e}`),this.lastProgressLength=e.length}endProgress(){this.lastProgressLength>0&&(process.stdout.write(`
9
+ `),this.lastProgressLength=0)}};var J=class{config;apiUrl;constructor(e){this.config=e,this.apiUrl=`https://api.telegram.org/bot${e.botToken}/sendMessage`}shouldNotify(e){let t=this.config.minLevel??a.ERROR;return B[e]>=B[t]}async notify(e,t){if(this.shouldNotify(e))try{let o=this.getEmoji(e),i=4e3,r=t;r.length>i&&(r=`${r.substring(0,i)}
10
10
  ... (truncated)`);let s=this.escapeHtml(r),l=`${o} <b>wetvlo ${this.getLevelLabel(e)}</b>
11
11
 
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 te(`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 O=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(){d.getNotifier().notify(a.DEBUG,"Reinitializing cookie refresh browser due to config reload"),await this.closeBrowser()}async performRefresh(e){let t=d.getConfig(),o=d.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;d.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),d.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
- `),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="/",p="FALSE",y="0",h="",v=""]=s;o.push({domain:l,includeSubdomains:c.toUpperCase()==="TRUE",path:u,secure:p.toUpperCase()==="TRUE",expiry:Number(y)||0,name:h,value:v})}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,p=o.value;t.push([i,r,s,l,String(c),u,p].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 ce=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 de=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 ce(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 ue=class{stateManager;downloadManager;scheduler;running=!1;domainHandlers=new Map;constructor(e,t){this.stateManager=d.getStateManager(),this.downloadManager=e;let o=t||(i=>new de(i));this.scheduler=o(async(i,r)=>{await this.executeTask(i,r)}),this.scheduler.setOnWait((i,r)=>{let s=d.getNotifier(),l=Math.round(r/1e3),c=i.split(":"),u=c[0],p=c[1];u==="download"?s.notify(a.INFO,`[${p}] Next download: ${l}s`):u==="check"&&s.notify(a.INFO,`[${p}] Next check: ${l}s`)})}addSeriesCheck(e){let t=d.getConfig(),o=T(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),d.getNotifier().notify(a.DEBUG,`[${o}] Added ${r} to check queue`)}addEpisodes(e,t){let o=d.getNotifier(),i=d.getConfig();if(t.length===0)return;let r=i.resolve(e,"series"),s=r.name,l=T(e);this.registerDownloadQueue(l);let{downloadDelay:c}=r.download;for(let u=0;u<t.length;u++){let p=t[u];if(!p)continue;let y={seriesUrl:e,episode:p,retryCount:0},h=`download:${l}`,v=u*c*1e3;this.scheduler.addTask(h,y,v)}o.notify(a.SUCCESS,`[${l}] ${s}: queued ${t.length} episodes`)}updateConfig(){d.getNotifier().notify(a.DEBUG,"[QueueManager] Configuration will be reloaded")}clearQueues(){this.scheduler.clearQueues(),d.getNotifier().notify(a.DEBUG,"[QueueManager] Cleared all queues")}resetQueues(){this.scheduler.resetQueues(),d.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(),d.getNotifier().notify(a.DEBUG,"[QueueManager] Started queue processing")}async stop(){this.running&&(d.getNotifier().notify(a.DEBUG,"[QueueManager] Stopping queue processing..."),this.scheduler.stop(),this.running=!1,d.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=d.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=d.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=U.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=d.getNotifier(),r=d.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 p=r.resolve(s,"series"),y=p.name,{count:h,checkInterval:v}=p.check;try{let f=await this.performCheck(u,s,p,l,t);if(f.hasNewEpisodes){if(i.notify(a.SUCCESS,`[${t}] ${y}: ${f.episodes.length} new episodes (attempt ${l}/${h})`),p.cookieRefreshBrowser)try{await O.getInstance().refreshCookies(s)}catch(m){let w=m instanceof Error?m.message:String(m);i.notify(a.ERROR,`[${t}] ${y}: cookie refresh failed - ${w}`)}this.addEpisodes(s,f.episodes),this.scheduler.markTaskComplete(o,v*1e3)}else if(l<h){let m=v*1e3,w=f.requeueDelay??m;i.notify(a.INFO,`[${t}] ${y}: no new episodes, retry in ${Math.round(w/1e3)}s (attempt ${l}/${h})`);let g={seriesUrl:s,attemptNumber:l+1,retryCount:0};this.scheduler.addTask(o,g,w),this.scheduler.markTaskComplete(o,v*1e3)}else i.notify(a.INFO,`[${t}] ${y}: exhausted after ${h} attempts`),this.scheduler.markTaskComplete(o,v*1e3)}catch(f){let m=f instanceof Error?f.message:String(f),{maxRetries:w,initialTimeout:g,backoffMultiplier:k,jitterPercentage:E}=p.download;if(c<w){let S=this.calculateBackoff(c,g*1e3,k,E);i.notify(a.WARNING,`[${t}] ${y}: check retry ${c+1}/${w} in ${Math.round(S/1e3)}s`);let R={seriesUrl:s,attemptNumber:l,retryCount:c+1};this.scheduler.addPriorityTask(o,R,S),this.scheduler.markTaskComplete(o,v*1e3)}else i.notify(a.ERROR,`[${t}] ${y}: check failed - ${m}`),this.scheduler.markTaskComplete(o,v*1e3)}}async executeDownload(e,t,o){let i=d.getNotifier(),r=d.getConfig(),{seriesUrl:s,episode:l,retryCount:c=0}=e,u=r.resolve(s,"series"),p=u.name,{downloadDelay:y}=u.download;try{await this.downloadManager.download(s,l),this.scheduler.markTaskComplete(o,y*1e3)}catch(h){let v=h instanceof Error?h.message:String(h),{maxRetries:f,initialTimeout:m,backoffMultiplier:w,jitterPercentage:g}=u.download;if(c<f){let k=this.calculateBackoff(c,m*1e3,w,g);i.notify(a.WARNING,`[${t}] ${p} - ${String(l.number).padStart(2,"0")}: retry ${c+1}/${f} in ${Math.round(k/1e3)}s`);let E={seriesUrl:s,episode:l,retryCount:c+1};this.scheduler.addPriorityTask(o,E,k),this.scheduler.markTaskComplete(o,y*1e3)}else c===0?(i.notify(a.ERROR,`[${t}] ${p} - ${String(l.number).padStart(2,"0")}: ${v}`),i.notify(a.INFO,`[${t}] Episode URL: ${l.url}`)):i.notify(a.ERROR,`[${t}] ${p} - ${String(l.number).padStart(2,"0")}: ${v}`),this.scheduler.markTaskComplete(o,y*1e3)}}async performCheck(e,t,o,i,r){let s=d.getNotifier(),l=o.name,c=o.check.count;s.notify(a.INFO,`[${r}] ${l}: checking (attempt ${i}/${c})`),d.getNotifier().notify(a.DEBUG,`[${r}] Checking URL: ${t}`);let u=await e.extractEpisodes(t),p=new Map;u.forEach(f=>{let m=p.get(f.type)||0;p.set(f.type,m+1)});let y=Array.from(p.entries()).map(([f,m])=>`${f}: ${m}`).join(", ");s.notify(a.INFO,`[${r}] ${l}: ${u.length} episodes (${y})`);let{downloadTypes:h}=o.check,v=u.filter(f=>{let m=h.includes(f.type),w=o.stateFile,g=!this.stateManager.isDownloaded(w,l,f.number);return m&&g});if(u.length!==v.length){let f=u.length-v.length;s.notify(a.INFO,`[${r}] Filtering: ${v.length} to download, ${f} skipped`)}return v.length>0?{hasNewEpisodes:!0,episodes:v}:{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 Dt(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=Dt(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 ue(l));this.queueManager=s(this.downloadManager)}async start(){if(this.running)throw new ie("Scheduler is already running");this.running=!0,this.stopped=!1,this.queueManager.start();let e=d.getNotifier();if(this.options.mode==="once")d.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=d.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(){d.getNotifier().notify(a.DEBUG,"Stopping scheduler..."),this.stopped=!0,this.scheduleTimer&&(clearTimeout(this.scheduleTimer),this.scheduleTimer=null),await this.queueManager.stop(),this.running=!1,d.getNotifier().notify(a.DEBUG,"Scheduler stopped")}async reload(e){d.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(){d.getNotifier().notify(a.DEBUG,"Triggering immediate checks for all series...");for(let e of this.configs)this.queueManager.addSeriesCheck(e.url)}triggerImmediateChecks(){let e=d.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(){d.getNotifier().notify(a.DEBUG,"Clearing queues..."),this.queueManager.clearQueues()}groupConfigsBySchedule(){let e=d.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=d.getNotifier();for(let i of e){if(this.stopped)break;this.queueManager.addSeriesCheck(i.url)}let o=this.queueManager.getQueueStats();d.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);d.getNotifier().notify(a.DEBUG,"Single-run complete")}isRunning(){return this.running&&!this.stopped}getQueueManager(){return this.queueManager}};import{existsSync as Tt}from"fs";import{readFile as St}from"fs/promises";import{homedir as Qi}from"os";import{join as Wi}from"path";async function qe(n){if(!Tt(n))throw new oe(`Cookie file not found: "${n}"`);let t=(await St(n,"utf-8")).split(`
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 Ve={loadConfig:Te,checkYtDlpInstalled:K.checkYtDlpInstalled,readCookieFile:qe,createDownloadManager:()=>new K,createScheduler:(n,e,t)=>new fe(n,e,t)};async function je(n,e){e.notify(a.DEBUG,"Shutting down gracefully...");try{await O.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=Ve,o=!1){let i=await t.loadConfig(n),r=new V(i),s=new _,l=r.resolve("","global"),c=new Y(o?a.DEBUG:l.notifications.consoleMinLevel);if(s.add(c,0),l.telegram)try{s.add(new X(l.telegram),10)}catch(f){s.notify(a.WARNING,`Failed to set up Telegram: ${f instanceof Error?f.message:String(f)}`)}if(s.notify(a.INFO,`Mode: ${e}`),d.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:
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(`
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(`
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
19
- - Windows: winget install yt-dlp`);s.notify(a.DEBUG,"Configuration loaded"),U.register(new le),U.register(new ne),U.register(new ae),s.notify(a.DEBUG,`Registered handlers: ${U.getDomains().join(", ")}`);let p=t.createDownloadManager(),y;e==="scheduled"&&process.stdin.isTTY&&(y=()=>{let m=d.getNotifier();m.notify(a.INFO,"Interactive mode enabled:"),m.notify(a.INFO," [r] Reload configuration"),m.notify(a.INFO," [c] Clear queues and trigger checks"),m.notify(a.INFO," [q] Quit")}),s.notify(a.DEBUG,"Using queue-based scheduler");let h=t.createScheduler(r.listSeries(),p,{mode:e,onIdle:y}),v=async()=>{let f=d.getNotifier();await je(h,f),process.exit(0)};process.on("SIGINT",v),process.on("SIGTERM",v),e==="scheduled"&&process.stdin.isTTY&&(We.emitKeypressEvents(process.stdin),process.stdin.setRawMode(!0),process.stdin.on("keypress",async(f,m)=>{if(!m)return;let w=m.name||"",g=f||"";if(w==="q"||w==="\u0439"||g==="\u0439"||m.ctrl&&w==="c"){let k=d.getNotifier();await je(h,k),process.exit(0)}else if(w==="r"||w==="\u043A"||g==="\u043A"){let k=d.getNotifier();try{k.notify(a.DEBUG,`Reloading configuration from ${n}...`);let E=await t.loadConfig(n),S=new V(E),R=new _,D=S.resolve("","global"),j=new Y(o?a.DEBUG:D.notifications.consoleMinLevel);if(R.add(j,0),D.telegram)try{R.add(new X(D.telegram),10)}catch(W){R.notify(a.WARNING,`Failed to set up Telegram: ${W instanceof Error?W.message:String(W)}`)}d.setNotifier(R),d.reloadConfig(S),await h.reload(S.listSeries()),await O.getInstance().reinitialize(),R.notify(a.SUCCESS,"Configuration reloaded successfully")}catch(E){k.notify(a.ERROR,`Failed to reload config: ${E instanceof Error?E.message:String(E)}`)}}else(w==="c"||w==="\u0441"||g==="\u0441")&&h.triggerImmediateChecks()})),await h.start()}var ze=Rt({name:"wetvlo",description:"CLI Video Downloader for Chinese streaming sites",version:"0.0.1",args:{config:Nt({type:$t,long:"config",short:"c",defaultValue:()=>"./config.yaml",description:"Path to configuration file (default: ./config.yaml)"}),once:Qe({type:He,long:"once",short:"o",description:"Run in single-run mode (check once and exit)"}),debug:Qe({type:He,long:"debug",short:"d",description:"Enable debug logging"})},handler:async({config:n,once:e,debug:t})=>{try{await It(n,e?"once":"scheduled",Ve,t)}catch(o){o instanceof P?console.error(`Configuration error: ${o.message}`):console.error(`Fatal error: ${o instanceof Error?o.message:String(o)}`),process.exit(1)}}});async function Pt(n=process.argv.slice(2)){await Lt(ze,n)}var At=import.meta.main||process.argv[1]&&process.argv[1]===Mt(import.meta.url);At&&await Pt();export{Pt as main};
19
+ - Windows: winget install yt-dlp`);s.notify(a.DEBUG,"Configuration loaded"),F.register(new ce),F.register(new ae),F.register(new le),s.notify(a.DEBUG,`Registered handlers: ${F.getDomains().join(", ")}`);let g=t.createDownloadManager(),h;e==="scheduled"&&process.stdin.isTTY&&(h=()=>{let m=f.getNotifier();m.notify(a.INFO,"Interactive mode enabled:"),m.notify(a.INFO," [r] Reload configuration"),m.notify(a.INFO," [c] Clear queues and trigger checks"),m.notify(a.INFO," [q] Quit")}),s.notify(a.DEBUG,"Using queue-based scheduler");let p=t.createScheduler(r.listSeries(),g,{mode:e,onIdle:h}),w=async()=>{let d=f.getNotifier();await Ve(p,d),process.exit(0)};process.on("SIGINT",w),process.on("SIGTERM",w),e==="scheduled"&&process.stdin.isTTY&&(je.emitKeypressEvents(process.stdin),process.stdin.setRawMode(!0),process.stdin.on("keypress",async(d,m)=>{if(!m)return;let v=m.name||"",y=d||"";if(v==="q"||v==="\u0439"||y==="\u0439"||m.ctrl&&v==="c"){let E=f.getNotifier();await Ve(p,E),process.exit(0)}else if(v==="r"||v==="\u043A"||y==="\u043A"){let E=f.getNotifier();try{E.notify(a.DEBUG,`Reloading configuration from ${n}...`);let k=await t.loadConfig(n),T=new W(k),N=new K,D=T.resolve("","global"),P=new Y(o?a.DEBUG:D.notifications.consoleMinLevel);if(N.add(P,0),D.telegram?.botToken&&D.telegram?.chatId)try{N.add(new J(D.telegram),10)}catch(j){N.notify(a.WARNING,`Failed to set up Telegram: ${j instanceof Error?j.message:String(j)}`)}f.setNotifier(N),f.reloadConfig(T),await p.reload(T.listSeries()),await G.getInstance().reinitialize(),N.notify(a.SUCCESS,"Configuration reloaded successfully")}catch(k){E.notify(a.ERROR,`Failed to reload config: ${k instanceof Error?k.message:String(k)}`)}}else(v==="c"||v==="\u0441"||y==="\u0441")&&p.triggerImmediateChecks()})),await p.start()}var ze=Nt({name:"wetvlo",description:"CLI Video Downloader for Chinese streaming sites",version:"0.0.1",args:{config:Rt({type:$t,long:"config",short:"c",defaultValue:()=>"./config.yaml",description:"Path to configuration file (default: ./config.yaml)"}),once:Qe({type:He,long:"once",short:"o",description:"Run in single-run mode (check once and exit)"}),debug:Qe({type:He,long:"debug",short:"d",description:"Enable debug logging"})},handler:async({config:n,once:e,debug:t})=>{try{await It(n,e?"once":"scheduled",We,t)}catch(o){o instanceof A?console.error(`Configuration error: ${o.message}`):console.error(`Fatal error: ${o instanceof Error?o.message:String(o)}`),process.exit(1)}}});async function Pt(n=process.argv.slice(2)){await Lt(ze,n)}var At=import.meta.main||process.argv[1]&&process.argv[1]===Mt(import.meta.url);At&&await Pt();export{Pt as main};
20
20
  //# sourceMappingURL=index.js.map