From 6e8d052bdfea5d585b3703041e1a983eddb5469c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isma=C3=ABl=20Moret?= <30985701+BellezaEmporium@users.noreply.github.com> Date: Mon, 6 Apr 2026 14:02:48 +0000 Subject: [PATCH] CRLF madness --- sites/distro.tv/distro.tv.config.js | 160 ++++---- .../watch.whaletvplus.com.config.js | 374 +++++++++--------- 2 files changed, 267 insertions(+), 267 deletions(-) diff --git a/sites/distro.tv/distro.tv.config.js b/sites/distro.tv/distro.tv.config.js index b3c00b4d..9c85ae8d 100644 --- a/sites/distro.tv/distro.tv.config.js +++ b/sites/distro.tv/distro.tv.config.js @@ -1,81 +1,81 @@ -const axios = require('axios') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -const HEADERS = { - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36 Edg/144.0.0.0', - 'Referer': 'https://distro.tv/', - 'Origin': 'https://distro.tv' -} - -dayjs.extend(utc) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'distro.tv', - days: 3, - request: { - headers: HEADERS, - timeout: 60000 - }, - url({ channel, date }) { - const diff = date.diff(dayjs.utc().startOf('d'), 'd') - let range - - if (diff <= 0) { - range = 'now,24h' - } else { - const start = diff * 24 - const end = (diff + 1) * 24 - range = `${start}h,${end}h` - } - - return `https://tv.jsrdn.com/epg/query.php?range=${range}&id=${channel.site_id},` - }, - parser({ content, channel }) { - if (!content || !channel) return [] - let programs - try { - const data = JSON.parse(content) - if (!data.epg || !data.epg[channel.site_id] || !Array.isArray(data.epg[channel.site_id].slots)) { - return [] - } - programs = data.epg[channel.site_id].slots.map(program => ({ - title: program.title, - description: program.description || null, - icon: program.img_thumbh || null, - start: dayjs.utc(program.start, 'YYYY-MM-DD HH:mm:ss'), - stop: dayjs.utc(program.end, 'YYYY-MM-DD HH:mm:ss') - })) - } catch { - return [] - } - - return programs.filter(p => p.title && p.start.isValid() && p.stop.isValid()) - }, - async channels() { - const { data } = await axios.get('https://tv.jsrdn.com/tv_v5/getfeed.php?type=live', { - headers: HEADERS - }) - - const channels = [] - if (data && data.shows) { - Object.values(data.shows).forEach(show => { - const episode = show.seasons?.[0]?.episodes?.[0] - if (episode && episode.id) { - channels.push({ - lang: 'en', - // lang: show.language || 'en', - site_id: episode.id.toString(), - name: show.title, - // logo: show.img_logo, - // url: episode.content?.url || null - }) - } - }) - } - - return channels - } +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +const HEADERS = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36 Edg/144.0.0.0', + 'Referer': 'https://distro.tv/', + 'Origin': 'https://distro.tv' +} + +dayjs.extend(utc) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'distro.tv', + days: 3, + request: { + headers: HEADERS, + timeout: 60000 + }, + url({ channel, date }) { + const diff = date.diff(dayjs.utc().startOf('d'), 'd') + let range + + if (diff <= 0) { + range = 'now,24h' + } else { + const start = diff * 24 + const end = (diff + 1) * 24 + range = `${start}h,${end}h` + } + + return `https://tv.jsrdn.com/epg/query.php?range=${range}&id=${channel.site_id},` + }, + parser({ content, channel }) { + if (!content || !channel) return [] + let programs + try { + const data = JSON.parse(content) + if (!data.epg || !data.epg[channel.site_id] || !Array.isArray(data.epg[channel.site_id].slots)) { + return [] + } + programs = data.epg[channel.site_id].slots.map(program => ({ + title: program.title, + description: program.description || null, + icon: program.img_thumbh || null, + start: dayjs.utc(program.start, 'YYYY-MM-DD HH:mm:ss'), + stop: dayjs.utc(program.end, 'YYYY-MM-DD HH:mm:ss') + })) + } catch { + return [] + } + + return programs.filter(p => p.title && p.start.isValid() && p.stop.isValid()) + }, + async channels() { + const { data } = await axios.get('https://tv.jsrdn.com/tv_v5/getfeed.php?type=live', { + headers: HEADERS + }) + + const channels = [] + if (data && data.shows) { + Object.values(data.shows).forEach(show => { + const episode = show.seasons?.[0]?.episodes?.[0] + if (episode && episode.id) { + channels.push({ + lang: 'en', + // lang: show.language || 'en', + site_id: episode.id.toString(), + name: show.title, + // logo: show.img_logo, + // url: episode.content?.url || null + }) + } + }) + } + + return channels + } } \ No newline at end of file diff --git a/sites/watch.whaletvplus.com/watch.whaletvplus.com.config.js b/sites/watch.whaletvplus.com/watch.whaletvplus.com.config.js index 3e04bd6e..e704035b 100644 --- a/sites/watch.whaletvplus.com/watch.whaletvplus.com.config.js +++ b/sites/watch.whaletvplus.com/watch.whaletvplus.com.config.js @@ -1,188 +1,188 @@ -const axios = require('axios') -const dayjs = require('dayjs') - -const HEADERS = { - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:147.0) Gecko/20100101 Firefox/147.0', - 'Referer': 'https://watch.whaletvplus.com/', - 'Origin': 'https://watch.whaletvplus.com' -} -const apiToken = '4ef13b5f3d2744e3b0a569feb8dde298' - -let authTokenPromise = null - -module.exports = { - site: 'watch.whaletvplus.com', - days: 2, - - request: { - cache: { - ttl: 60 * 60 * 1000 // Cache 1 hour - }, - headers: async function() { - const token = await getAuthToken() - return { - ...HEADERS, - 'token': token - } - } - }, - - url: function ({ channel, date }) { - const start = date.valueOf() - const end = date.add(1, 'day').valueOf() - - return `https://rlaxx.zeasn.tv/livetv/api/device/browser/v1/epg?channelIds=${channel.site_id}&startTime=${start}&endTime=${end}` - }, - - parser: async function ({ content }) { - let json - try { - json = JSON.parse(content) - } catch (e) { - console.error('Error parsing JSON:', e) - return [] - } - - if (!json.data || !Array.isArray(json.data) || !json.data[0] || !Array.isArray(json.data[0].ptList)) { - return [] - } - - const programs = json.data[0].ptList - const detailsCache = {} - - return await limit(programs, async (p) => { - const program = { - title: p.prgTitle, - start: dayjs(Number(p.prgStm)), - stop: dayjs(Number(p.prgEtm)) - } - - if (p.prgchId) { - if (!detailsCache[p.prgchId]) { - detailsCache[p.prgchId] = fetchProgramDetail(p.prgchId) - } - const detail = await detailsCache[p.prgchId] - if (detail) { - program.description = detail.prgDesc || null - program.season = detail.seasonNumber || null - program.episode = detail.episodeNumber || null - program.sub_title = detail.prgTitle || detail.seriesTitle || null - - if (program.title === program.sub_title) { - program.sub_title = null - } - - if (detail.images && Array.isArray(detail.images)) { - const bestImg = detail.images.find((i) => i.pimgWidth === '1920') || detail.images[0] - if (bestImg) program.image = bestImg.pimgUrl - } - } - } - return program - }) - }, - - async channels() { - const token = await getAuthToken() - - const countries = [ - 'IN', 'AU', 'NZ', 'ZA', 'US', 'BR', 'MX', 'AR', 'CO', 'CL', 'CA', - 'GB', 'DE', 'FR', 'IT', 'ES', 'PL', 'TR', 'AT', 'CH', 'NL', 'PT', - 'BE', 'SE', 'NO', 'DK', 'FI' - ] - - const requests = countries.map(country => - axios.get('https://rlaxx.zeasn.tv/livetv/api/device/browser/v1/category/channels', { - params: { countryCode: country, langCode: 'en' }, - headers: { ...HEADERS, token } - }).then(r => r.data?.data || []).catch(() => []) - ) - - const results = await Promise.all(requests) - const allChannels = results.flat().flatMap(group => group.channels || []) - - const uniqueChannels = new Map() - for (const ch of allChannels) { - if (!uniqueChannels.has(ch.chlId)) { - uniqueChannels.set(ch.chlId, { - lang: (ch.chlLangCode ? ch.chlLangCode.split('-')[0] : 'en'), - site_id: ch.chlId, - name: ch.chlName.trim(), - short_title: ch.chlShortTitle, - // logo: ch.imageIdentifier ? `https://d3b6luslimvglo.cloudfront.net/images/79/rlaxximages/channels-rescaled/icon-white/${ch.imageIdentifier}_white.png` : null - }) - } - } - - return handleDuplicateNames(Array.from(uniqueChannels.values())) - } -} - -async function limit(items, fn, concurrency = 20) { - const results = [] - for (let i = 0; i < items.length; i += concurrency) { - const batch = items.slice(i, i + concurrency) - results.push(...(await Promise.all(batch.map(fn)))) - } - return results -} - -async function getAuthToken() { - if (authTokenPromise) return authTokenPromise - - authTokenPromise = (async () => { - try { - const response = await axios.get('https://rlaxx.zeasn.tv/livetv/api/v1/auth/access', { - params: { uuid: '1', apiToken, langCode: 'en' }, - headers: HEADERS - }) - - if (response.data && response.data.data && response.data.data.token) { - return response.data.data.token - } - - throw new Error('apiToken invalid or expired. Please update config.') - } catch (error) { - authTokenPromise = null - throw new Error(error.message, { cause: error }) - } - })() - - return authTokenPromise -} - -async function fetchProgramDetail(programId) { - const token = await getAuthToken() - try { - const response = await axios.get(`https://rlaxx.zeasn.tv/livetv/api/device/browser/v1/epg/detail/${programId}`, { - headers: { - ...HEADERS, - 'token': token - }, - timeout: 5000 - }) - return response.data && response.data.data ? response.data.data : null - } catch { - return null - } -} - -function handleDuplicateNames(channels) { - const counts = {} - channels.forEach(ch => counts[ch.name] = (counts[ch.name] || 0) + 1) - - channels.forEach(ch => { - if (counts[ch.name] > 1) { - let suffix = ch.short_title && ch.short_title.split('_').slice(1).join('_') - if (suffix) { - if (suffix.startsWith('en-') && suffix.length > 3) suffix = suffix.slice(3) - ch.name += ` (${suffix.replace(/-/g, '/').toUpperCase()})` - } else if (ch.lang) { - ch.name += ` (${ch.lang.toUpperCase()})` - } - } - delete ch.short_title - }) - - return channels +const axios = require('axios') +const dayjs = require('dayjs') + +const HEADERS = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:147.0) Gecko/20100101 Firefox/147.0', + 'Referer': 'https://watch.whaletvplus.com/', + 'Origin': 'https://watch.whaletvplus.com' +} +const apiToken = '4ef13b5f3d2744e3b0a569feb8dde298' + +let authTokenPromise = null + +module.exports = { + site: 'watch.whaletvplus.com', + days: 2, + + request: { + cache: { + ttl: 60 * 60 * 1000 // Cache 1 hour + }, + headers: async function() { + const token = await getAuthToken() + return { + ...HEADERS, + 'token': token + } + } + }, + + url: function ({ channel, date }) { + const start = date.valueOf() + const end = date.add(1, 'day').valueOf() + + return `https://rlaxx.zeasn.tv/livetv/api/device/browser/v1/epg?channelIds=${channel.site_id}&startTime=${start}&endTime=${end}` + }, + + parser: async function ({ content }) { + let json + try { + json = JSON.parse(content) + } catch (e) { + console.error('Error parsing JSON:', e) + return [] + } + + if (!json.data || !Array.isArray(json.data) || !json.data[0] || !Array.isArray(json.data[0].ptList)) { + return [] + } + + const programs = json.data[0].ptList + const detailsCache = {} + + return await limit(programs, async (p) => { + const program = { + title: p.prgTitle, + start: dayjs(Number(p.prgStm)), + stop: dayjs(Number(p.prgEtm)) + } + + if (p.prgchId) { + if (!detailsCache[p.prgchId]) { + detailsCache[p.prgchId] = fetchProgramDetail(p.prgchId) + } + const detail = await detailsCache[p.prgchId] + if (detail) { + program.description = detail.prgDesc || null + program.season = detail.seasonNumber || null + program.episode = detail.episodeNumber || null + program.sub_title = detail.prgTitle || detail.seriesTitle || null + + if (program.title === program.sub_title) { + program.sub_title = null + } + + if (detail.images && Array.isArray(detail.images)) { + const bestImg = detail.images.find((i) => i.pimgWidth === '1920') || detail.images[0] + if (bestImg) program.image = bestImg.pimgUrl + } + } + } + return program + }) + }, + + async channels() { + const token = await getAuthToken() + + const countries = [ + 'IN', 'AU', 'NZ', 'ZA', 'US', 'BR', 'MX', 'AR', 'CO', 'CL', 'CA', + 'GB', 'DE', 'FR', 'IT', 'ES', 'PL', 'TR', 'AT', 'CH', 'NL', 'PT', + 'BE', 'SE', 'NO', 'DK', 'FI' + ] + + const requests = countries.map(country => + axios.get('https://rlaxx.zeasn.tv/livetv/api/device/browser/v1/category/channels', { + params: { countryCode: country, langCode: 'en' }, + headers: { ...HEADERS, token } + }).then(r => r.data?.data || []).catch(() => []) + ) + + const results = await Promise.all(requests) + const allChannels = results.flat().flatMap(group => group.channels || []) + + const uniqueChannels = new Map() + for (const ch of allChannels) { + if (!uniqueChannels.has(ch.chlId)) { + uniqueChannels.set(ch.chlId, { + lang: (ch.chlLangCode ? ch.chlLangCode.split('-')[0] : 'en'), + site_id: ch.chlId, + name: ch.chlName.trim(), + short_title: ch.chlShortTitle, + // logo: ch.imageIdentifier ? `https://d3b6luslimvglo.cloudfront.net/images/79/rlaxximages/channels-rescaled/icon-white/${ch.imageIdentifier}_white.png` : null + }) + } + } + + return handleDuplicateNames(Array.from(uniqueChannels.values())) + } +} + +async function limit(items, fn, concurrency = 20) { + const results = [] + for (let i = 0; i < items.length; i += concurrency) { + const batch = items.slice(i, i + concurrency) + results.push(...(await Promise.all(batch.map(fn)))) + } + return results +} + +async function getAuthToken() { + if (authTokenPromise) return authTokenPromise + + authTokenPromise = (async () => { + try { + const response = await axios.get('https://rlaxx.zeasn.tv/livetv/api/v1/auth/access', { + params: { uuid: '1', apiToken, langCode: 'en' }, + headers: HEADERS + }) + + if (response.data && response.data.data && response.data.data.token) { + return response.data.data.token + } + + throw new Error('apiToken invalid or expired. Please update config.') + } catch (error) { + authTokenPromise = null + throw new Error(error.message, { cause: error }) + } + })() + + return authTokenPromise +} + +async function fetchProgramDetail(programId) { + const token = await getAuthToken() + try { + const response = await axios.get(`https://rlaxx.zeasn.tv/livetv/api/device/browser/v1/epg/detail/${programId}`, { + headers: { + ...HEADERS, + 'token': token + }, + timeout: 5000 + }) + return response.data && response.data.data ? response.data.data : null + } catch { + return null + } +} + +function handleDuplicateNames(channels) { + const counts = {} + channels.forEach(ch => counts[ch.name] = (counts[ch.name] || 0) + 1) + + channels.forEach(ch => { + if (counts[ch.name] > 1) { + let suffix = ch.short_title && ch.short_title.split('_').slice(1).join('_') + if (suffix) { + if (suffix.startsWith('en-') && suffix.length > 3) suffix = suffix.slice(3) + ch.name += ` (${suffix.replace(/-/g, '/').toUpperCase()})` + } else if (ch.lang) { + ch.name += ` (${ch.lang.toUpperCase()})` + } + } + delete ch.short_title + }) + + return channels } \ No newline at end of file