import { build } from 'esbuild'; import fs from 'node:fs'; import path from 'node:path'; const root = process.cwd(); const tmp = path.join(root, '.build'); const sdk = path.join(tmp, 'sdk'); const shim = path.join(tmp, 'shim'); const www = path.join(root, 'www'); fs.rmSync(tmp, { recursive: true, force: true }); fs.rmSync(www, { recursive: true, force: true }); fs.mkdirSync(shim, { recursive: true }); fs.mkdirSync(path.join(www, 'js'), { recursive: true }); fs.cpSync(path.join(root, 'node_modules/@vyla-entertainment/sdk/src'), sdk, { recursive: true }); const helpers = path.join(sdk, 'utils/helpers.js'); fs.writeFileSync(helpers, fs.readFileSync(helpers, 'utf8').replace(/\}, 60000\)\.unref\(\);/, '}, 60000);')); fs.writeFileSync(path.join(shim, 'crypto.js'), `export const webcrypto = globalThis.crypto; export function createHash() { throw new Error('unsupported'); } export function createDecipheriv() { throw new Error('unsupported'); } export default { webcrypto, createHash, createDecipheriv }; `); fs.writeFileSync(path.join(shim, 'inject.js'), `import { Buffer } from 'buffer'; globalThis.Buffer = globalThis.Buffer || Buffer; globalThis.process = globalThis.process || { env: {} }; if (!AbortSignal.timeout) { AbortSignal.timeout = ms => { const c = new AbortController(); setTimeout(() => c.abort(), ms); return c.signal; }; } export { Buffer }; `); const files = [...new Set([...fs.readFileSync(path.join(sdk, 'config.js'), 'utf8').matchAll(/sourceFile:\s*'([^']+)'/g)].map(m => m[1]))]; const entry = `import SOURCES from './sdk/config.js'; import { createStreamArgs } from './sdk/utils/helpers.js'; import { fetchSubtitles, getSubPathsMovie, getSubPathsTv } from './sdk/subtitles.js'; ${files.map((f, i) => `import * as m${i} from './sdk/sources/${f}.js';`).join('\n')} const MODS = { ${files.map((f, i) => `'${f}': m${i}`).join(', ')} }; const core = { tmdbApiKey: null, penguManifest: null }; const live = () => SOURCES.filter(c => !c.disabled && MODS[c.sourceFile]); globalThis.VylaLocal = { init(key) { core.tmdbApiKey = key; globalThis.process.env.TMDB_API_KEY = key; }, sources() { return live(); }, async stream(key, id, s, e, ip) { const cfg = live().find(c => c.key === key); if (!cfg) throw new Error('Source not found'); return await MODS[cfg.sourceFile].getStream(await createStreamArgs(cfg, core, id, s, e, ip)); }, async subtitles(id, s, e) { const paths = s != null && e != null ? await getSubPathsTv(id, s, e) : await getSubPathsMovie(id); return await fetchSubtitles(paths); } }; `; fs.writeFileSync(path.join(tmp, 'entry.js'), entry); await build({ entryPoints: [path.join(tmp, 'entry.js')], bundle: true, format: 'iife', target: 'chrome70', platform: 'browser', minify: true, alias: { 'node:crypto': path.join(shim, 'crypto.js') }, inject: [path.join(shim, 'inject.js')], outfile: path.join(www, 'js/vyla-local.js'), logLevel: 'info' }); for (const p of ['index.html', 'css', 'js', 'assets']) { const from = path.join(root, p); if (fs.existsSync(from)) fs.cpSync(from, path.join(www, p), { recursive: true, filter: s => !s.endsWith('vyla-local.js') }); } fs.copyFileSync(path.join(root, 'node_modules/hls.js/dist/hls.min.js'), path.join(www, 'js/hls.min.js')); const androidAssets = path.join(root, 'android/app/src/main/assets'); if (!fs.existsSync(androidAssets)) fs.mkdirSync(androidAssets, { recursive: true }); fs.copyFileSync(path.join(www, 'js/vyla-local.js'), path.join(androidAssets, 'vyla-local.js'));