Fix linter issues in sites/

This commit is contained in:
freearhey 2025-01-01 12:27:22 +03:00
parent d6d20b6413
commit 5df982bb7c
129 changed files with 3316 additions and 3226 deletions

View file

@ -92,7 +92,7 @@ function parseItems(content, channel) {
const [, channelId] = channel.site_id.split('#')
const channelData = data.schedule.find(i => i.channel == channelId)
return channelData.listing && Array.isArray(channelData.listing) ? channelData.listing : []
} catch (err) {
} catch {
return []
}
}

View file

@ -1,4 +1,4 @@
const { parser, url, request } = require('./awilime.com.config.js')
const { parser, url } = require('./awilime.com.config.js')
const fs = require('fs')
const path = require('path')
const dayjs = require('dayjs')

View file

@ -1,5 +1,4 @@
const axios = require('axios')
const cheerio = require('cheerio')
const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
const timezone = require('dayjs/plugin/timezone')
@ -62,7 +61,7 @@ function parseItems(content) {
let data
try {
data = JSON.parse(content)
} catch (error) {
} catch {
return []
}

View file

@ -1,6 +1,4 @@
const { parser, url } = require('./beinsports.com.config.js')
const fs = require('fs')
const path = require('path')
const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
const customParseFormat = require('dayjs/plugin/customParseFormat')

View file

@ -1,13 +1,12 @@
const axios = require('axios');
const cheerio = require('cheerio');
const dayjs = require('dayjs');
const utc = require('dayjs/plugin/utc');
const timezone = require('dayjs/plugin/timezone');
const customParseFormat = require('dayjs/plugin/customParseFormat');
const cheerio = require('cheerio')
const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
const timezone = require('dayjs/plugin/timezone')
const customParseFormat = require('dayjs/plugin/customParseFormat')
dayjs.extend(utc);
dayjs.extend(timezone);
dayjs.extend(customParseFormat);
dayjs.extend(utc)
dayjs.extend(timezone)
dayjs.extend(customParseFormat)
module.exports = {
site: 'chada.ma',
@ -19,36 +18,38 @@ module.exports = {
}
},
url() {
return 'https://chada.ma/fr/chada-tv/grille-tv/';
return 'https://chada.ma/fr/chada-tv/grille-tv/'
},
parser: function ({ content }) {
const $ = cheerio.load(content);
const programs = [];
const $ = cheerio.load(content)
const programs = []
$('#stopfix .posts-area h2').each((i, element) => {
const timeRange = $(element).text().trim();
const [start, stop] = timeRange.split(' - ').map(t => parseProgramTime(t.trim()));
const timeRange = $(element).text().trim()
const [start, stop] = timeRange.split(' - ').map(t => parseProgramTime(t.trim()))
const titleElement = $(element).next('div').next('h3');
const title = titleElement.text().trim();
const titleElement = $(element).next('div').next('h3')
const title = titleElement.text().trim()
const description = titleElement.next('div').text().trim() || 'No description available';
const description = titleElement.next('div').text().trim() || 'No description available'
programs.push({
title,
description,
start,
stop
});
});
})
})
return programs;
return programs
}
};
}
function parseProgramTime(timeStr) {
const timeZone = 'Africa/Casablanca';
const currentDate = dayjs().format('YYYY-MM-DD');
const timeZone = 'Africa/Casablanca'
const currentDate = dayjs().format('YYYY-MM-DD')
return dayjs.tz(`${currentDate} ${timeStr}`, 'YYYY-MM-DD HH:mm', timeZone).format('YYYY-MM-DDTHH:mm:ssZ');
return dayjs
.tz(`${currentDate} ${timeStr}`, 'YYYY-MM-DD HH:mm', timeZone)
.format('YYYY-MM-DDTHH:mm:ssZ')
}

View file

@ -1,7 +1,5 @@
const { parser, url } = require('./chada.ma.config.js')
const axios = require('axios')
const dayjs = require('dayjs')
const cheerio = require('cheerio')
const utc = require('dayjs/plugin/utc')
const timezone = require('dayjs/plugin/timezone')
const customParseFormat = require('dayjs/plugin/customParseFormat')
@ -27,11 +25,11 @@ const mockHtmlContent = `
<div class="ssprogramme row"></div>
</div>
</div>
`;
`
it('can generate valid url', () => {
expect(url()).toBe('https://chada.ma/fr/chada-tv/grille-tv/')
});
})
it('can parse response', () => {
const content = mockHtmlContent
@ -44,8 +42,8 @@ it('can parse response', () => {
expect(result).toMatchObject([
{
title: "Bloc Prime + Clips",
description: "No description available",
title: 'Bloc Prime + Clips',
description: 'No description available',
start: dayjs.tz('00:00', 'HH:mm', 'Africa/Casablanca').format('YYYY-MM-DDTHH:mm:ssZ'),
stop: dayjs.tz('09:00', 'HH:mm', 'Africa/Casablanca').format('YYYY-MM-DDTHH:mm:ssZ')
}

View file

@ -29,7 +29,7 @@ module.exports = {
},
async channels() {
const html = await axios
.get(`https://chaines-tv.orange.fr/programme-tv?filtres=all`)
.get('https://chaines-tv.orange.fr/programme-tv?filtres=all')
.then(r => r.data)
.catch(console.log)

View file

@ -40,7 +40,7 @@ module.exports = {
},
async channels() {
const data = await axios
.get(`https://contenthub-api.eco.astro.com.my/channel/all.json`)
.get('https://contenthub-api.eco.astro.com.my/channel/all.json')
.then(r => r.data)
.catch(console.log)
@ -85,7 +85,7 @@ function parseItems(content, date) {
const schedules = data.response.schedule
return schedules[date.format('YYYY-MM-DD')] || []
} catch (e) {
} catch {
return []
}
}

View file

@ -16,12 +16,13 @@ module.exports = {
},
method: 'GET',
headers: {
'referer': 'https://www.cosmotetv.gr/',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
'Accept': '*/*',
referer: 'https://www.cosmotetv.gr/',
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
Accept: '*/*',
'Accept-Language': 'en-US,en;q=0.9',
'Accept-Encoding': 'gzip, deflate, br, zstd',
'Origin': 'https://www.cosmotetv.gr',
Origin: 'https://www.cosmotetv.gr',
'Sec-Ch-Ua': '"Not.A/Brand";v="24", "Chromium";v="131", "Google Chrome";v="131"',
'Sec-Ch-Ua-Mobile': '?0',
'Sec-Ch-Ua-Platform': '"Windows"',
@ -30,12 +31,12 @@ module.exports = {
'Sec-Fetch-Site': 'cross-site'
}
},
url: function ({date, channel}) {
url: function ({ date, channel }) {
const startOfDay = dayjs(date).startOf('day').utc().unix()
const endOfDay = dayjs(date).endOf('day').utc().unix()
return `https://mwapi-prod.cosmotetvott.gr/api/v3.4/epg/listings/el?from=${startOfDay}&to=${endOfDay}&callSigns=${channel.site_id}&endingIncludedInRange=false`
},
parser: function ({ date, content }) {
parser: function ({ content }) {
let programs = []
const data = JSON.parse(content)
data.channels.forEach(channel => {
@ -57,16 +58,19 @@ module.exports = {
async channels() {
const axios = require('axios')
try {
const response = await axios.get('https://mwapi-prod.cosmotetvott.gr/api/v3.4/epg/channels/all/el', {
headers: this.request.headers
})
const response = await axios.get(
'https://mwapi-prod.cosmotetvott.gr/api/v3.4/epg/channels/all/el',
{
headers: this.request.headers
}
)
const data = response.data
if (data && data.channels) {
return data.channels.map(item => ({
lang: 'el',
site_id: item.callSign,
name: item.title,
name: item.title
//logo: item.logos.square
}))
} else {

View file

@ -1,9 +1,8 @@
const { parser, url, channels } = require('./cosmotetv.gr.config.js')
const { parser, url } = require('./cosmotetv.gr.config.js')
const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
const customParseFormat = require('dayjs/plugin/customParseFormat')
const timezone = require('dayjs/plugin/timezone')
const axios = require('axios')
dayjs.extend(utc)
dayjs.extend(customParseFormat)
@ -14,34 +13,22 @@ jest.mock('axios')
const date = dayjs.utc('2024-12-26', 'YYYY-MM-DD').startOf('d')
const channel = { site_id: 'vouli', xmltv_id: 'HellenicParliamentTV.gr' }
const mockChannelData = {
"channels": [
{
"guid": "XTV100000954",
"title": "ΒΟΥΛΗ HD",
"callSign": "vouli",
"logos": {
"square": "https://tr.static.cdn.cosmotetvott.gr/ote-prod/channel_logos/vouli1-normal.png",
"wide": "https://tr.static.cdn.cosmotetvott.gr/ote-prod/channel_logos/vouli1-wide.png"
}
}
]
}
const mockEpgData = {
"channels": [
channels: [
{
"items": [
items: [
{
"startTime": "2024-12-26T23:00:00+00:00",
"endTime": "2024-12-27T00:00:00+00:00",
"title": "Τι Λέει ο Νόμος",
"description": "νημερωτική εκπομπή. Συζήτηση με τους εισηγητές των κομμάτων για το νομοθετικό έργο.",
"qoe": {
"genre": "Special"
startTime: '2024-12-26T23:00:00+00:00',
endTime: '2024-12-27T00:00:00+00:00',
title: 'Τι Λέει ο Νόμος',
description:
'νημερωτική εκπομπή. Συζήτηση με τους εισηγητές των κομμάτων για το νομοθετικό έργο.',
qoe: {
genre: 'Special'
},
"thumbnails": {
"standard": "https://gr-ermou-prod-cache05.static.cdn.cosmotetvott.gr/ote-prod/70/280/040029714812000800_1734415727199.jpg"
thumbnails: {
standard:
'https://gr-ermou-prod-cache05.static.cdn.cosmotetvott.gr/ote-prod/70/280/040029714812000800_1734415727199.jpg'
}
}
]
@ -52,7 +39,9 @@ const mockEpgData = {
it('can generate valid url', () => {
const startOfDay = dayjs(date).startOf('day').utc().unix()
const endOfDay = dayjs(date).endOf('day').utc().unix()
expect(url({ date, channel })).toBe(`https://mwapi-prod.cosmotetvott.gr/api/v3.4/epg/listings/el?from=${startOfDay}&to=${endOfDay}&callSigns=${channel.site_id}&endingIncludedInRange=false`)
expect(url({ date, channel })).toBe(
`https://mwapi-prod.cosmotetvott.gr/api/v3.4/epg/listings/el?from=${startOfDay}&to=${endOfDay}&callSigns=${channel.site_id}&endingIncludedInRange=false`
)
})
it('can parse response', () => {
@ -65,17 +54,23 @@ it('can parse response', () => {
expect(result).toMatchObject([
{
title: "Τι Λέει ο Νόμος",
description: "νημερωτική εκπομπή. Συζήτηση με τους εισηγητές των κομμάτων για το νομοθετικό έργο.",
category: "Special",
image: "https://gr-ermou-prod-cache05.static.cdn.cosmotetvott.gr/ote-prod/70/280/040029714812000800_1734415727199.jpg",
start: "2024-12-26T23:00:00.000Z",
stop: "2024-12-27T00:00:00.000Z"
title: 'Τι Λέει ο Νόμος',
description:
'νημερωτική εκπομπή. Συζήτηση με τους εισηγητές των κομμάτων για το νομοθετικό έργο.',
category: 'Special',
image:
'https://gr-ermou-prod-cache05.static.cdn.cosmotetvott.gr/ote-prod/70/280/040029714812000800_1734415727199.jpg',
start: '2024-12-26T23:00:00.000Z',
stop: '2024-12-27T00:00:00.000Z'
}
])
})
it('can handle empty guide', () => {
const result = parser({ date, channel, content: '{"date":"2024-12-26","categories":[],"channels":[]}' });
const result = parser({
date,
channel,
content: '{"date":"2024-12-26","categories":[],"channels":[]}'
})
expect(result).toMatchObject([])
})

View file

@ -9,7 +9,9 @@ module.exports = {
site: 'cubmu.com',
days: 2,
url({ channel, date }) {
return `https://servicebuss.transvision.co.id/v2/cms/getEPGData?app_id=cubmu&tvs_platform_id=standalone&schedule_date=${date.format('YYYY-MM-DD')}&channel_id=${channel.site_id}`
return `https://servicebuss.transvision.co.id/v2/cms/getEPGData?app_id=cubmu&tvs_platform_id=standalone&schedule_date=${date.format(
'YYYY-MM-DD'
)}&channel_id=${channel.site_id}`
},
parser({ content, channel }) {
const programs = []
@ -46,13 +48,19 @@ module.exports = {
}
}
// login to service bus
const token = await axios
.post(`https://servicebuss.transvision.co.id/tvs/login/external?email=${config.email}&password=${config.password}&deviceId=${config.deviceId}&deviceType=${config.deviceType}&deviceModel=${config.deviceModel}&deviceToken=&serial=&platformId=${config.platformId}`, options)
await axios
.post(
`https://servicebuss.transvision.co.id/tvs/login/external?email=${config.email}&password=${config.password}&deviceId=${config.deviceId}&deviceType=${config.deviceType}&deviceModel=${config.deviceModel}&deviceToken=&serial=&platformId=${config.platformId}`,
options
)
.then(response => response.data)
.catch(console.error)
// list channels
const subscribedChannels = await axios
.post(`https://servicebuss.transvision.co.id/tvs/subscribe_product/list?platformId=${config.platformId}`, options)
.post(
`https://servicebuss.transvision.co.id/tvs/subscribe_product/list?platformId=${config.platformId}`,
options
)
.then(response => response.data)
.catch(console.error)
@ -98,5 +106,9 @@ function parseStart(item) {
}
function parseStop(item) {
return dayjs.tz([item.schedule_date.split(' ')[0], item.schedule_end_time].join(' '), 'YYYY-MM-DD HH:mm:ss', 'Asia/Jakarta')
return dayjs.tz(
[item.schedule_date.split(' ')[0], item.schedule_end_time].join(' '),
'YYYY-MM-DD HH:mm:ss',
'Asia/Jakarta'
)
}

View file

@ -13,9 +13,9 @@ module.exports = {
site: 'dens.tv',
days: 2,
url({ channel, date }) {
return `https://www.dens.tv/api/dens3/tv/TvChannels/listEpgByDate?date=${date.format('YYYY-MM-DD')}&id_channel=${
channel.site_id
}&app_type=10`
return `https://www.dens.tv/api/dens3/tv/TvChannels/listEpgByDate?date=${date.format(
'YYYY-MM-DD'
)}&id_channel=${channel.site_id}&app_type=10`
},
parser({ content }) {
// parsing
@ -25,8 +25,9 @@ module.exports = {
if (Array.isArray(response?.data)) {
response.data.forEach(item => {
const title = item.title
const [, , , season, , , episode] = title.match(/( (Season |Season|S)(\d+))?( (Episode|Ep) (\d+))/) ||
[null, null, null, null, null, null, null]
const [, , , season, , , episode] = title.match(
/( (Season |Season|S)(\d+))?( (Episode|Ep) (\d+))/
) || [null, null, null, null, null, null, null]
programs.push({
title,
description: item.description,
@ -52,7 +53,7 @@ module.exports = {
const channels = []
for (const id_category of Object.values(categories)) {
const data = await axios
.get(`https://www.dens.tv/api/dens3/tv/TvChannels/listByCategory`, {
.get('https://www.dens.tv/api/dens3/tv/TvChannels/listByCategory', {
params: { id_category }
})
.then(r => r.data)

View file

@ -10,7 +10,9 @@ const date = dayjs.utc('2024-11-24').startOf('d')
const channel = { site_id: '38', xmltv_id: 'AniplusAsia.sg', lang: 'id' }
it('can generate valid url', () => {
expect(url({ channel, date })).toBe('https://www.dens.tv/api/dens3/tv/TvChannels/listEpgByDate?date=2024-11-24&id_channel=38&app_type=10')
expect(url({ channel, date })).toBe(
'https://www.dens.tv/api/dens3/tv/TvChannels/listEpgByDate?date=2024-11-24&id_channel=38&app_type=10'
)
})
it('can parse response', () => {

View file

@ -65,7 +65,7 @@ module.exports = {
const cheerio = require('cheerio')
const data = await axios
.get(`https://www.digiturk.com.tr/`, {
.get('https://www.digiturk.com.tr/', {
headers: {
'User-Agent':
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36'

View file

@ -60,7 +60,7 @@ module.exports = {
$('.pgrid').each((i, el) => {
const onclick = $(el).find('.chnl-logo').attr('onclick')
const number = $(el).find('.cnl-fav > a > span').text().trim()
const [, name, site_id] = onclick.match(/ShowChannelGuid\('([^']+)','([^']+)'/) || [
const [, , site_id] = onclick.match(/ShowChannelGuid\('([^']+)','([^']+)'/) || [
null,
'',
''

View file

@ -49,7 +49,7 @@ module.exports = {
const channels = []
for (let provider of providers) {
const data = await axios
.post(`https://www.guida.tv/guide/schedule`, null, {
.post('https://www.guida.tv/guide/schedule', null, {
params: {
provider,
region: 'Italy',
@ -81,7 +81,7 @@ module.exports = {
}
}
function parseStart($item, date, channel) {
function parseStart($item, date) {
const timeString = $item('td:eq(0)').text().trim()
const dateString = `${date.format('YYYY-MM-DD')} ${timeString}`

View file

@ -35,7 +35,7 @@ module.exports = {
const cheerio = require('cheerio')
const data = await axios
.get(`https://guidatv.sky.it/canali`)
.get('https://guidatv.sky.it/canali')
.then(r => r.data)
.catch(console.log)

View file

@ -16,7 +16,7 @@ module.exports = {
url: function ({ channel, date }) {
return `https://epg-file.hoy.tv/hoy/OTT${channel.site_id}${date.format('YYYYMMDD')}.xml`
},
parser({ content, channel, date }) {
parser({ content, date }) {
const data = convert.xml2js(content, {
compact: true,
ignoreDeclaration: true,
@ -28,7 +28,7 @@ module.exports = {
for (let item of data.ProgramGuide.Channel.EpgItem) {
const start = dayjs.tz(item.EpgStartDateTime._text, 'YYYY-MM-DD HH:mm:ss', 'Asia/Hong_Kong')
if (! date.isSame(start, 'day')) {
if (!date.isSame(start, 'day')) {
continue
}
@ -40,13 +40,13 @@ module.exports = {
sub_title: subtitle,
description: item.EpisodeInfo.EpisodeLongDescription._text,
start,
stop: dayjs.tz(item.EpgEndDateTime._text, 'YYYY-MM-DD HH:mm:ss', 'Asia/Hong_Kong'),
stop: dayjs.tz(item.EpgEndDateTime._text, 'YYYY-MM-DD HH:mm:ss', 'Asia/Hong_Kong')
})
}
return programs
},
async channels({ lang }) {
async channels() {
const data = await axios
.get('https://api2.hoy.tv/api/v2/a/channel')
.then(r => r.data)
@ -56,7 +56,7 @@ module.exports = {
return {
site_id: c.videos.id,
name: c.name.zh_hk,
lang: 'zh',
lang: 'zh'
}
})
}

View file

@ -87,9 +87,7 @@ const content = `<?xml version="1.0" encoding="UTF-8" ?>
</ProgramGuide>`
it('can generate valid url', () => {
expect(url({ channel, date })).toBe(
'https://epg-file.hoy.tv/hoy/OTT7620240913.xml'
)
expect(url({ channel, date })).toBe('https://epg-file.hoy.tv/hoy/OTT7620240913.xml')
})
it('can parse response', () => {
@ -104,13 +102,14 @@ it('can parse response', () => {
start: '2024-09-13T03:30:00.000Z',
stop: '2024-09-13T04:30:00.000Z',
title: '點講都係一家人[PG]',
sub_title: '第46集',
sub_title: '第46集'
},
{
start: '2024-09-13T04:30:00.000Z',
stop: '2024-09-13T05:30:00.000Z',
title: '麝香之路',
description: 'Ep. 2 .The Secret of disappeared kingdom.shows the mysterious disappearance of the ancient Tibetan kingdom which gained world',
description:
'Ep. 2 .The Secret of disappeared kingdom.shows the mysterious disappearance of the ancient Tibetan kingdom which gained world'
}
])
})

View file

@ -172,14 +172,17 @@ function parseItems(content, channel, date) {
if (!data || !Array.isArray(data.programs)) return []
return data.programs
.filter(p => p.channel === site_id && dayjs(p.start, 'YYYYMMDDHHmmss ZZ').isBetween(curr_day, next_day))
.filter(
p =>
p.channel === site_id && dayjs(p.start, 'YYYYMMDDHHmmss ZZ').isBetween(curr_day, next_day)
)
.map(p => {
if (Array.isArray(p.date) && p.date.length) {
p.date = p.date[0]
}
return p
})
} catch (error) {
} catch {
return []
}
}

View file

@ -12,27 +12,30 @@ module.exports = {
site: 'ipko.tv',
timezone: 'Europe/Belgrade',
days: 5,
url({ date, channel }) { return 'https://stargate.ipko.tv/api/titan.tv.WebEpg/GetWebEpgData' },
url() {
return 'https://stargate.ipko.tv/api/titan.tv.WebEpg/GetWebEpgData'
},
request: {
method: 'POST',
headers: {
'Host': 'stargate.ipko.tv',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0',
'Accept': 'application/json, text/plain, */*',
Host: 'stargate.ipko.tv',
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0',
Accept: 'application/json, text/plain, */*',
'Accept-Language': 'nl,en-US;q=0.7,en;q=0.3',
'Content-Type': 'application/json',
'X-AppLayout': '1',
'x-language': 'sq',
'Origin': 'https://ipko.tv',
Origin: 'https://ipko.tv',
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'cross-site',
'Sec-GPC': '1',
'Connection': 'keep-alive'
Connection: 'keep-alive'
},
data({ channel, date }) {
const todayEpoch = date.startOf('day').unix();
const nextDayEpoch = date.add(1, 'day').startOf('day').unix();
const todayEpoch = date.startOf('day').unix()
const nextDayEpoch = date.add(1, 'day').startOf('day').unix()
return JSON.stringify({
ch_ext_id: channel.site_id,
from: todayEpoch,
@ -41,11 +44,11 @@ module.exports = {
}
},
parser: function ({ content }) {
const programs = [];
const data = JSON.parse(content);
const programs = []
const data = JSON.parse(content)
data.shows.forEach(show => {
const start = dayjs.unix(show.show_start).utc();
const stop = dayjs.unix(show.show_end).utc();
const start = dayjs.unix(show.show_start).utc()
const stop = dayjs.unix(show.show_end).utc()
const programData = {
title: show.title,
description: show.summary || 'No description available',
@ -58,15 +61,19 @@ module.exports = {
return programs
},
async channels() {
const response = await axios.post('https://stargate.ipko.tv/api/titan.tv.WebEpg/ZapList', JSON.stringify({ includeRadioStations: true }), {
headers: this.request.headers
});
const response = await axios.post(
'https://stargate.ipko.tv/api/titan.tv.WebEpg/ZapList',
JSON.stringify({ includeRadioStations: true }),
{
headers: this.request.headers
}
)
const data = response.data.data;
const data = response.data.data
return data.map(item => ({
lang: 'sq',
name: String(item.channel.title),
site_id: String(item.channel.id),
site_id: String(item.channel.id)
//logo: String(item.channel.logo)
}))
}

View file

@ -76,33 +76,29 @@ it('can parse response', () => {
]
}`
const result = parser({ content, channel }).map(p => {
p.start = p.start
p.stop = p.stop
return p
})
const result = parser({ content, channel })
expect(result).toMatchObject([
{
title: "IPKO Promo",
description: "No description available",
start: "2024-12-24T04:00:00.000Z",
stop: "2024-12-24T06:00:00.000Z",
thumbnail: "https://vimg.ipko.tv/mtcms/18/2/1/1821cc68-a9bf-4733-b1af-9a5d80163b78.jpg"
title: 'IPKO Promo',
description: 'No description available',
start: '2024-12-24T04:00:00.000Z',
stop: '2024-12-24T06:00:00.000Z',
thumbnail: 'https://vimg.ipko.tv/mtcms/18/2/1/1821cc68-a9bf-4733-b1af-9a5d80163b78.jpg'
},
{
title: "IPKO Promo",
description: "No description available",
start: "2024-12-24T06:00:00.000Z",
stop: "2024-12-24T08:00:00.000Z",
thumbnail: "https://vimg.ipko.tv/mtcms/18/2/1/1821cc68-a9bf-4733-b1af-9a5d80163b78.jpg"
title: 'IPKO Promo',
description: 'No description available',
start: '2024-12-24T06:00:00.000Z',
stop: '2024-12-24T08:00:00.000Z',
thumbnail: 'https://vimg.ipko.tv/mtcms/18/2/1/1821cc68-a9bf-4733-b1af-9a5d80163b78.jpg'
},
{
title: "IPKO Promo",
description: "No description available",
start: "2024-12-24T08:00:00.000Z",
stop: "2024-12-24T10:00:00.000Z",
thumbnail: "https://vimg.ipko.tv/mtcms/18/2/1/1821cc68-a9bf-4733-b1af-9a5d80163b78.jpg"
title: 'IPKO Promo',
description: 'No description available',
start: '2024-12-24T08:00:00.000Z',
stop: '2024-12-24T10:00:00.000Z',
thumbnail: 'https://vimg.ipko.tv/mtcms/18/2/1/1821cc68-a9bf-4733-b1af-9a5d80163b78.jpg'
}
])
})

View file

@ -38,7 +38,7 @@ module.exports = {
async channels() {
const axios = require('axios')
const data = await axios
.get(`https://m.tv.sms.cz/?zmen_stanice=true`)
.get('https://m.tv.sms.cz/?zmen_stanice=true')
.then(r => r.data)
.catch(console.log)

View file

@ -77,8 +77,8 @@ function parseItems(content) {
let data
try {
data = JSON.parse(content)
} catch (error) {
console.log(error.message)
} catch {
return []
}
if (!data || !Array.isArray(data)) return []

View file

@ -32,7 +32,7 @@ module.exports = {
async channels() {
const axios = require('axios')
const data = await axios
.get(`https://player.maxtvtogo.tportal.hr:8082/OTT4Proxy/proxy/epg/channels`)
.get('https://player.maxtvtogo.tportal.hr:8082/OTT4Proxy/proxy/epg/channels')
.then(r => r.data)
.catch(console.log)

View file

@ -56,7 +56,7 @@ function parseStop($item) {
try {
return dayjs(timeString, 'YYYY-MM-DD HH:mm:ssZZ')
} catch (err) {
} catch {
return null
}
}

View file

@ -10,31 +10,35 @@ dayjs.extend(timezone)
module.exports = {
site: 'mediasetinfinity.mediaset.it',
days: 2,
url: function ({date, channel}) {
url: function ({ date, channel }) {
// Get the epoch timestamp
const todayEpoch = date.startOf('day').utc().valueOf()
// Get the epoch timestamp for the next day
const nextDayEpoch = date.add(1, 'day').startOf('day').utc().valueOf()
return `https://api-ott-prod-fe.mediaset.net/PROD/play/feed/allListingFeedEpg/v2.0?byListingTime=${todayEpoch}~${nextDayEpoch}&byCallSign=${channel.site_id}`
},
parser: function ({content}) {
parser: function ({ content }) {
const programs = []
const data = JSON.parse(content)
if (!data.response || !data.response.entries || !data.response.entries[0] || !data.response.entries[0].listings) {
if (
!data.response ||
!data.response.entries ||
!data.response.entries[0] ||
!data.response.entries[0].listings
) {
// If the structure is not as expected, return an empty array
return programs
}
const listings = data.response.entries[0].listings
listings.forEach((listing) => {
listings.forEach(listing => {
const title = listing.mediasetlisting$epgTitle
const subTitle = listing.program.title
const season = parseSeason(listing)
const episode = parseEpisode(listing)
if (listing.program.title && listing.startTime && listing.endTime) {
programs.push({
title: title || subTitle,
@ -54,7 +58,6 @@ module.exports = {
}
}
function parseTime(timestamp) {
return dayjs(timestamp).utc().format('YYYY-MM-DD HH:mm')
}
@ -77,17 +80,18 @@ function getMaxResolutionThumbnails(item) {
for (const key in thumbnails) {
const type = key.split('-')[0] // Estrarre il tipo di thumbnail
const {width, height, url, title} = thumbnails[key]
const { width, height, url, title } = thumbnails[key]
if (!maxResolutionThumbnails[type] ||
(width * height > maxResolutionThumbnails[type].width * maxResolutionThumbnails[type].height)) {
maxResolutionThumbnails[type] = {width, height, url, title}
if (
!maxResolutionThumbnails[type] ||
width * height > maxResolutionThumbnails[type].width * maxResolutionThumbnails[type].height
) {
maxResolutionThumbnails[type] = { width, height, url, title }
}
}
if (maxResolutionThumbnails.image_keyframe_poster)
return maxResolutionThumbnails.image_keyframe_poster.url
else if (maxResolutionThumbnails.image_header_poster)
return maxResolutionThumbnails.image_header_poster.url
else
return null
else return null
}

View file

@ -1,4 +1,4 @@
const {parser, url} = require('./mediasetinfinity.mediaset.it.config.js')
const { parser, url } = require('./mediasetinfinity.mediaset.it.config.js')
const fs = require('fs')
const path = require('path')
const dayjs = require('dayjs')
@ -9,19 +9,24 @@ dayjs.extend(utc)
const date = dayjs.utc('2024-01-20', 'YYYY-MM-DD').startOf('d')
const channel = {
site_id: 'LB', xmltv_id: '20.it'
site_id: 'LB',
xmltv_id: '20.it'
}
it('can generate valid url', () => {
expect(url({
channel,
date
})).toBe('https://api-ott-prod-fe.mediaset.net/PROD/play/feed/allListingFeedEpg/v2.0?byListingTime=1705708800000~1705795200000&byCallSign=LB')
expect(
url({
channel,
date
})
).toBe(
'https://api-ott-prod-fe.mediaset.net/PROD/play/feed/allListingFeedEpg/v2.0?byListingTime=1705708800000~1705795200000&byCallSign=LB'
)
})
it('can parse response', () => {
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'), 'utf8')
const results = parser({content, date}).map(p => {
const results = parser({ content, date }).map(p => {
return p
})
@ -30,11 +35,13 @@ it('can parse response', () => {
stop: '2024-01-20 02:54',
title: 'Chicago Fire',
sub_title: 'Ep. 22 - Io non ti lascio',
description: 'Severide e Kidd continuano a indagare su un vecchio caso doloso di Benny. Notizie inaspettate portano Brett a meditare su una grande decisione.',
description:
'Severide e Kidd continuano a indagare su un vecchio caso doloso di Benny. Notizie inaspettate portano Brett a meditare su una grande decisione.',
category: 'Intrattenimento',
season: '7',
episode: '22',
image: 'https://static2.mediasetplay.mediaset.it/Mediaset_Italia_Production_-_Main/F309370301002204/media/0/0/1ef76b73-3173-43bd-9c16-73986a0ec131/46896726-11e7-4438-b947-d2ae53f58c0b.jpg'
image:
'https://static2.mediasetplay.mediaset.it/Mediaset_Italia_Production_-_Main/F309370301002204/media/0/0/1ef76b73-3173-43bd-9c16-73986a0ec131/46896726-11e7-4438-b947-d2ae53f58c0b.jpg'
})
})

View file

@ -40,7 +40,7 @@ module.exports = {
async channels() {
const axios = require('axios')
const data = await axios
.post(`https://authservice.apps.meo.pt/Services/GridTv/GridTvMng.svc/getGridAnon`, null, {
.post('https://authservice.apps.meo.pt/Services/GridTv/GridTvMng.svc/getGridAnon', null, {
headers: {
Origin: 'https://www.meo.pt'
}

View file

@ -42,7 +42,7 @@ module.exports = {
.catch(console.error)
if (content) {
const [ $, items ] = getItems(content)
const [$, items] = getItems(content)
if (seq === 0) {
queues.push(...items.map(category => baseUrl + $(category).attr('href')))
} else {
@ -86,7 +86,11 @@ function parseItems(content, date) {
data.season = parseInt(ep[1])
data.episode = parseInt(ep[2])
}
data.start = dayjs.tz(`${lastDate} ${$item.find('.time').text()}`, 'DD/MM/YYYY HH:mm', 'America/Sao_Paulo')
data.start = dayjs.tz(
`${lastDate} ${$item.find('.time').text()}`,
'DD/MM/YYYY HH:mm',
'America/Sao_Paulo'
)
result.push(data)
}
}

View file

@ -42,7 +42,7 @@ module.exports = {
const axios = require('axios')
const cheerio = require('cheerio')
const data = await axios
.get(`https://www.mewatch.sg/channel-guide`)
.get('https://www.mewatch.sg/channel-guide')
.then(r => r.data)
.catch(console.log)

View file

@ -11,9 +11,7 @@ dayjs.extend(utc)
dayjs.extend(timezone)
dayjs.extend(customParseFormat)
doFetch
.setCheckResult(false)
.setDebugger(debug)
doFetch.setCheckResult(false).setDebugger(debug)
const languages = { en: 'english', id: 'indonesia' }
const cookies = {}
@ -125,7 +123,7 @@ async function parseItems(content, date, cookies) {
const url = $item.find('a').attr('href')
const headers = {
'X-Requested-With': 'XMLHttpRequest',
Cookie: cookies,
Cookie: cookies
}
queues.push({ i: $item, url, params: { headers, timeout } })
}

View file

@ -48,8 +48,10 @@ function parseItems(context) {
schDayPrograms.forEach((program, i) => {
const itemDay = {
progStart: parseStart($(schDayMonth), $(program)),
progStop: parseStop($(schDayMonth), schDayPrograms[i + 1] ?
$(schDayPrograms[i + 1]) : null),
progStop: parseStop(
$(schDayMonth),
schDayPrograms[i + 1] ? $(schDayPrograms[i + 1]) : null
),
progTitle: parseTitle($(program)),
progDesc: parseDescription($(program))
}
@ -91,7 +93,9 @@ function parseStop(schDayMonth, itemNext) {
)
} else {
return dayjs.tz(
`${currentYear}-${monthDate[0]}-${(parseInt(monthDate[1]) + 1).toString().padStart(2, '0')} 00:00`,
`${currentYear}-${monthDate[0]}-${(parseInt(monthDate[1]) + 1)
.toString()
.padStart(2, '0')} 00:00`,
'YYYY-MMM-DD HH:mm',
tz
)

File diff suppressed because one or more lines are too long

View file

@ -41,7 +41,7 @@ module.exports = {
const pages = Array.from(Array(totalPages).keys())
for (let page of pages) {
const data = await axios
.get(`https://mtel.ba/oec/epg/program`, {
.get('https://mtel.ba/oec/epg/program', {
params: { page, date: dayjs().format('YYYY-MM-DD') },
headers: {
'X-Requested-With': 'XMLHttpRequest'
@ -65,7 +65,7 @@ module.exports = {
async function getTotalPageCount() {
const data = await axios
.get(`https://mtel.ba/oec/epg/program`, {
.get('https://mtel.ba/oec/epg/program', {
params: { page: 0, date: dayjs().format('YYYY-MM-DD') },
headers: {
'X-Requested-With': 'XMLHttpRequest'

View file

@ -43,7 +43,7 @@ module.exports = {
const pages = Array.from(Array(totalPages).keys())
for (let page of pages) {
const data = await axios
.get(`https://mts.rs/oec/epg/program`, {
.get('https://mts.rs/oec/epg/program', {
params: { page, date: dayjs().format('YYYY-MM-DD') },
headers: {
'X-Requested-With': 'XMLHttpRequest'
@ -67,7 +67,7 @@ module.exports = {
async function getTotalPageCount() {
const data = await axios
.get(`https://mts.rs/oec/epg/program`, {
.get('https://mts.rs/oec/epg/program', {
params: { page: 0, date: dayjs().format('YYYY-MM-DD') },
headers: {
'X-Requested-With': 'XMLHttpRequest'
@ -84,8 +84,8 @@ function parseContent(content, channel) {
let data
try {
data = JSON.parse(content)
} catch (error) {
console.log(error)
} catch {
return []
}
if (!data || !data.channels || !data.channels.length) return null

View file

@ -47,7 +47,7 @@ module.exports = {
const data = await axios
.post(
`https://services.mujtvprogram.cz/tvprogram2services/services/tvchannellist_mobile.php`,
'https://services.mujtvprogram.cz/tvprogram2services/services/tvchannellist_mobile.php',
params,
{
headers: {
@ -86,7 +86,7 @@ function parseItems(content) {
if (!data) return []
const programmes = data['tv-program-programmes'].programme
return programmes && Array.isArray(programmes) ? programmes : []
} catch (err) {
} catch {
return []
}
}

View file

@ -9,7 +9,7 @@ dayjs.extend(customParseFormat)
const headers = {
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36 OPR/115.0.0.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36 OPR/115.0.0.0'
}
module.exports = {

View file

@ -26,12 +26,11 @@ it('can generate valid url for today', () => {
it('can parse response', () => {
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'))
const results = parser({ content, date })
.map(p => {
p.start = p.start.toJSON()
p.stop = p.stop.toJSON()
return p
})
const results = parser({ content, date }).map(p => {
p.start = p.start.toJSON()
p.stop = p.stop.toJSON()
return p
})
expect(results[0]).toMatchObject({
start: '2022-11-19T23:00:00.000Z',

View file

@ -23,14 +23,15 @@ module.exports = {
channel.site_id
}.html?dt=${date.format('YYYY-MM-DD')}`
},
async parser({ content, date, channel }) {
async parser({ content, date }) {
const programs = []
if (content) {
const queues = []
const $ = cheerio.load(content)
$('table.table > tbody > tr').toArray()
$('table.table > tbody > tr')
.toArray()
.forEach(el => {
const td = $(el).find('td:eq(1)')
const title = td.find('h5 a')
@ -66,12 +67,16 @@ module.exports = {
const subTitle = parseText($('.tab-pane > h5 > strong'))
const description = parseText($('.tab-pane > .tvbody > p'))
const image = $('.program-media-image img').attr('src')
const category = $('.schedule-attributes-genres span').toArray()
const category = $('.schedule-attributes-genres span')
.toArray()
.map(el => $(el).text())
const casts = $('.single-cast-head:not([id])').toArray()
const casts = $('.single-cast-head:not([id])')
.toArray()
.map(el => {
const cast = { name: parseText($(el).find('a')) }
const [, role] = $(el).text().match(/\((.*)\)/) || [null, null]
const [, role] = $(el)
.text()
.match(/\((.*)\)/) || [null, null]
if (role) {
cast.role = role
}
@ -102,7 +107,7 @@ module.exports = {
start,
stop
})
})
})
}
}
@ -115,11 +120,17 @@ module.exports = {
// process form -> provider
if (queue.t === 'p') {
const $ = cheerio.load(res)
$('#guide_provider option').toArray()
$('#guide_provider option')
.toArray()
.forEach(el => {
const opt = $(el)
const provider = opt.attr('value')
queues.push({ t: 'r', method: 'post', url: 'https://www.mytelly.co.uk/getregions', params: { provider } })
queues.push({
t: 'r',
method: 'post',
url: 'https://www.mytelly.co.uk/getregions',
params: { provider }
})
})
}
// process provider -> region
@ -135,26 +146,30 @@ module.exports = {
u_time: now.format('HHmm'),
is_mobile: 1
}
queues.push({ t: 's', method: 'post', url: 'https://www.mytelly.co.uk/tv-guide/schedule', params })
queues.push({
t: 's',
method: 'post',
url: 'https://www.mytelly.co.uk/tv-guide/schedule',
params
})
}
}
// process schedule -> channels
if (queue.t === 's') {
const $ = cheerio.load(res)
$('.channelname')
.each((i, el) => {
const name = $(el).find('center > a:eq(1)').text()
const url = $(el).find('center > a:eq(1)').attr('href')
const [, number, slug] = url.match(/\/(\d+)\/(.*)\.html$/)
const site_id = `${number}/${slug}`
if (channels[site_id] === undefined) {
channels[site_id] = {
lang: 'en',
site_id,
name
}
$('.channelname').each((i, el) => {
const name = $(el).find('center > a:eq(1)').text()
const url = $(el).find('center > a:eq(1)').attr('href')
const [, number, slug] = url.match(/\/(\d+)\/(.*)\.html$/)
const site_id = `${number}/${slug}`
if (channels[site_id] === undefined) {
channels[site_id] = {
lang: 'en',
site_id,
name
}
})
}
})
}
})
@ -178,13 +193,10 @@ function parseTime(date, time) {
}
function parseText($item) {
let text = $item.text()
.replace(/\t/g, '')
.replace(/\n/g, ' ')
.trim()
let text = $item.text().replace(/\t/g, '').replace(/\n/g, ' ').trim()
while (true) {
if (text.match(/ /)) {
text = text.replace(/ /g, ' ')
if (text.match(/\s\s/)) {
text = text.replace(/\s\s/g, ' ')
continue
}
break

View file

@ -17,16 +17,18 @@ const channel = {
xmltv_id: 'BBCOneLondon.uk'
}
axios.get.mockImplementation((url, opts) => {
axios.get.mockImplementation(url => {
if (
url === 'https://www.mytelly.co.uk/tv-guide/listings/programme?cid=713&pid=1906433&tm=2024-12-07+00%3A00%3A00'
url ===
'https://www.mytelly.co.uk/tv-guide/listings/programme?cid=713&pid=1906433&tm=2024-12-07+00%3A00%3A00'
) {
return Promise.resolve({
data: fs.readFileSync(path.join(__dirname, '__data__', 'programme.html'))
})
}
if (
url === 'https://www.mytelly.co.uk/tv-guide/listings/programme?cid=713&pid=5656624&tm=2024-12-07+23%3A35%3A00'
url ===
'https://www.mytelly.co.uk/tv-guide/listings/programme?cid=713&pid=5656624&tm=2024-12-07+23%3A35%3A00'
) {
return Promise.resolve({
data: fs.readFileSync(path.join(__dirname, '__data__', 'programme2.html'))
@ -57,7 +59,8 @@ it('can parse response', async () => {
title: 'Captain Phillips',
description:
'An American cargo ship sets a dangerous course around the coast of Somalia, while inland, four men are pressed into service as pirates by the local warlords. The captain is taken hostage when the raiding party hijacks the vessel, resulting in a tense five-day crisis. Fact-based thriller, starring Tom Hanks and Barkhad Abdi',
image: 'https://d16ia5iwuvax6y.cloudfront.net/uk-prog-images/c44ce7b0d3ae602c0c93ece5af140815.jpg?k=VeeNdUjml3bSHdlZ0OXbGLy%2BmsLdYPwTV6iAxGkzq4dsylOCGGE7OWlqwSWt0cd0Qtrin4DkEMC0Zzdp8ZeNk2vNIQzjMF0DG0h3IeTR5NM%3D',
image:
'https://d16ia5iwuvax6y.cloudfront.net/uk-prog-images/c44ce7b0d3ae602c0c93ece5af140815.jpg?k=VeeNdUjml3bSHdlZ0OXbGLy%2BmsLdYPwTV6iAxGkzq4dsylOCGGE7OWlqwSWt0cd0Qtrin4DkEMC0Zzdp8ZeNk2vNIQzjMF0DG0h3IeTR5NM%3D',
category: ['Factual', 'Movie/Drama', 'Thriller']
})
expect(results[1]).toMatchObject({
@ -67,7 +70,8 @@ it('can parse response', async () => {
subTitle: 'Past and Pressure Season 6, Episode 5',
description:
'The artists are tasked with writing a song about their heritage. For some, the pressure of the competition proves too much for them to match. In their final challenge, they are put face to face with industry experts who grill them about their plans after the competition. Some impress, while others leave the mentors confused',
image: 'https://d16ia5iwuvax6y.cloudfront.net/uk-prog-images/2039278182b27cc279570b9ab9b89379.jpg?k=VeeNdUjml3bSHdlZ0OXbGLy%2BmsLdYPwTV6iAxGkzq4cDhR7jXTNFW3tgwQCdOPUobhXwlT81mIsqOe93HPusDG6tw1aoeYOgafojtynNWxc%3D',
image:
'https://d16ia5iwuvax6y.cloudfront.net/uk-prog-images/2039278182b27cc279570b9ab9b89379.jpg?k=VeeNdUjml3bSHdlZ0OXbGLy%2BmsLdYPwTV6iAxGkzq4cDhR7jXTNFW3tgwQCdOPUobhXwlT81mIsqOe93HPusDG6tw1aoeYOgafojtynNWxc%3D',
category: ['Challenge/Reality Show', 'Show/Game Show'],
season: 6,
episode: 5

View file

@ -12,27 +12,30 @@ module.exports = {
site: 'neo.io',
timezone: 'Europe/Ljubljana',
days: 5,
url({ date, channel }) { return 'https://stargate.telekom.si/api/titan.tv.WebEpg/GetWebEpgData' },
url() {
return 'https://stargate.telekom.si/api/titan.tv.WebEpg/GetWebEpgData'
},
request: {
method: 'POST',
headers: {
'Host': 'stargate.telekom.si',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0',
'Accept': 'application/json, text/plain, */*',
Host: 'stargate.telekom.si',
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0',
Accept: 'application/json, text/plain, */*',
'Accept-Language': 'nl,en-US;q=0.7,en;q=0.3',
'Content-Type': 'application/json',
'X-AppLayout': '1',
'x-language': 'sl',
'Origin': 'https://neo.io',
Origin: 'https://neo.io',
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'cross-site',
'Sec-GPC': '1',
'Connection': 'keep-alive'
Connection: 'keep-alive'
},
data({ channel, date }) {
const todayEpoch = date.startOf('day').unix();
const nextDayEpoch = date.add(1, 'day').startOf('day').unix();
const todayEpoch = date.startOf('day').unix()
const nextDayEpoch = date.add(1, 'day').startOf('day').unix()
return JSON.stringify({
ch_ext_id: channel.site_id,
from: todayEpoch,
@ -41,11 +44,11 @@ module.exports = {
}
},
parser: function ({ content }) {
const programs = [];
const data = JSON.parse(content);
const programs = []
const data = JSON.parse(content)
data.shows.forEach(show => {
const start = dayjs.unix(show.show_start).utc();
const stop = dayjs.unix(show.show_end).utc();
const start = dayjs.unix(show.show_start).utc()
const stop = dayjs.unix(show.show_end).utc()
const programData = {
title: show.title,
description: show.summary || 'No description available',
@ -58,15 +61,19 @@ module.exports = {
return programs
},
async channels() {
const response = await axios.post('https://stargate.telekom.si/api/titan.tv.WebEpg/ZapList', JSON.stringify({ includeRadioStations: true }), {
headers: this.request.headers
});
const response = await axios.post(
'https://stargate.telekom.si/api/titan.tv.WebEpg/ZapList',
JSON.stringify({ includeRadioStations: true }),
{
headers: this.request.headers
}
)
const data = response.data.data;
const data = response.data.data
return data.map(item => ({
lang: 'sq',
name: String(item.channel.title),
site_id: String(item.channel.id),
site_id: String(item.channel.id)
//logo: String(item.channel.logo)
}))
}

View file

@ -12,7 +12,9 @@ const channel = {
}
it('can generate valid url', () => {
expect(url({ date, channel })).toBe('https://stargate.telekom.si/api/titan.tv.WebEpg/GetWebEpgData')
expect(url({ date, channel })).toBe(
'https://stargate.telekom.si/api/titan.tv.WebEpg/GetWebEpgData'
)
})
it('can parse response', () => {
@ -82,33 +84,34 @@ it('can parse response', () => {
]
}`
const result = parser({ content, channel }).map(p => {
p.start = p.start
p.stop = p.stop
return p
})
const result = parser({ content, channel })
expect(result).toMatchObject([
{
title: "Napovedujemo",
description: "Vabilo k ogledu naših oddaj.",
start: "2024-12-26T04:05:00.000Z",
stop: "2024-12-26T05:50:00.000Z",
thumbnail: "https://ngimg.siol.tv/sioltv/mtcmsprod/52/0/0/5200d01a-fe5f-487e-835a-274e77227a6b.jpg"
title: 'Napovedujemo',
description: 'Vabilo k ogledu naših oddaj.',
start: '2024-12-26T04:05:00.000Z',
stop: '2024-12-26T05:50:00.000Z',
thumbnail:
'https://ngimg.siol.tv/sioltv/mtcmsprod/52/0/0/5200d01a-fe5f-487e-835a-274e77227a6b.jpg'
},
{
title: "S0E0 - Hrabri zajčki: Prvi sneg",
description: "Hrabri zajčki so prispeli v borov gozd in izkusili prvi sneg. Bob in Bu še nikoli nista videla snega. Mami kuha korenčkov kakav, Bu in Bob pa kmalu spoznata novega prijatelja, losa Danija.",
start: "2024-12-26T05:50:00.000Z",
stop: "2024-12-26T06:00:00.000Z",
thumbnail: "https://ngimg.siol.tv/sioltv/mtcmsprod/d6/4/5/d6456f4a-4f0a-4825-90c1-1749abd59688.jpg"
title: 'S0E0 - Hrabri zajčki: Prvi sneg',
description:
'Hrabri zajčki so prispeli v borov gozd in izkusili prvi sneg. Bob in Bu še nikoli nista videla snega. Mami kuha korenčkov kakav, Bu in Bob pa kmalu spoznata novega prijatelja, losa Danija.',
start: '2024-12-26T05:50:00.000Z',
stop: '2024-12-26T06:00:00.000Z',
thumbnail:
'https://ngimg.siol.tv/sioltv/mtcmsprod/d6/4/5/d6456f4a-4f0a-4825-90c1-1749abd59688.jpg'
},
{
title: "Dobro jutro",
description: "Oddaja Dobro jutro poleg informativnih in zabavnih vsebin podaja koristne nasvete o najrazličnejših tematikah iz vsakdanjega življenja.",
start: "2024-12-26T06:00:00.000Z",
stop: "2024-12-26T09:05:00.000Z",
thumbnail: "https://ngimg.siol.tv/sioltv/mtcmsprod/e1/2/d/e12d8eb4-693a-43d3-89d4-fd96dade9f0f.jpg"
title: 'Dobro jutro',
description:
'Oddaja Dobro jutro poleg informativnih in zabavnih vsebin podaja koristne nasvete o najrazličnejših tematikah iz vsakdanjega življenja.',
start: '2024-12-26T06:00:00.000Z',
stop: '2024-12-26T09:05:00.000Z',
thumbnail:
'https://ngimg.siol.tv/sioltv/mtcmsprod/e1/2/d/e12d8eb4-693a-43d3-89d4-fd96dade9f0f.jpg'
}
])
})

View file

@ -50,7 +50,7 @@ function parseItems(content, date) {
if (!data || !data.item || !Array.isArray(data.item.episodes)) return []
return data.item.episodes.filter(ep => ep.schedule.startsWith(date.format('YYYY-MM-DD')))
} catch (err) {
} catch {
return []
}
}

View file

@ -5,7 +5,8 @@ module.exports = {
// I'm not sure what `endDate` represents but they only return 1 day of
// results, with `endTime`s ocassionally in the following day.
days: 1,
url: ({ date }) => `https://api-web.nhle.com/v1/network/tv-schedule/${date.toJSON().split("T")[0]}`,
url: ({ date }) =>
`https://api-web.nhle.com/v1/network/tv-schedule/${date.toJSON().split('T')[0]}`,
parser({ content }) {
const programs = []
const items = parseItems(content)
@ -13,7 +14,7 @@ module.exports = {
programs.push({
title: item.title,
description: item.description === item.title ? undefined : item.description,
category: "Sports",
category: 'Sports',
// image: parseImage(item),
start: parseStart(item),
stop: parseStop(item)

View file

@ -10,9 +10,7 @@ dayjs.extend(utc)
const date = dayjs.utc('2024-11-21', 'YYYY-MM-DD').startOf('d')
it('can generate valid url', () => {
expect(url({ date })).toBe(
'https://api-web.nhle.com/v1/network/tv-schedule/2024-11-21'
)
expect(url({ date })).toBe('https://api-web.nhle.com/v1/network/tv-schedule/2024-11-21')
})
it('can parse response', () => {
@ -28,17 +26,19 @@ it('can parse response', () => {
start: '2024-11-21T12:00:00.000Z',
stop: '2024-11-21T13:00:00.000Z',
title: 'On The Fly',
category: 'Sports',
category: 'Sports'
})
})
it('can handle empty guide', () => {
const results = parser({ content: JSON.stringify({
// extra props not necessary but they form a valid response
date: "2024-11-21",
startDate: "2024-11-07",
endDate: "2024-12-05",
broadcasts: [],
}) })
const results = parser({
content: JSON.stringify({
// extra props not necessary but they form a valid response
date: '2024-11-21',
startDate: '2024-11-07',
endDate: '2024-12-05',
broadcasts: []
})
})
expect(results).toMatchObject([])
})

View file

@ -55,7 +55,7 @@ module.exports = {
.map(function () {
return {
lang: 'es',
site_id: $(this).attr('alt').replace(/\&/gi, '&amp;'),
site_id: $(this).attr('alt').replace(/&/gi, '&amp;'),
name: $(this).attr('alt')
}
})

View file

@ -27,7 +27,7 @@ module.exports = {
if (item.episodeNum) {
item.episodeNum.forEach(ep => {
if (ep.system === 'xmltv_ns') {
const [season, episode, _] = ep.value.split('.')
const [season, episode] = ep.value.split('.')
program.season = parseInt(season) + 1
program.episode = parseInt(episode) + 1
return true

View file

@ -132,7 +132,7 @@ module.exports = {
const $ = cheerio.load(data)
$('.channelname').each((i, el) => {
let name = $(el).find('center > a:eq(1)').text()
name = name.replace(/\-\-/gi, '-')
name = name.replace(/--/gi, '-')
const url = $(el).find('center > a:eq(1)').attr('href')
if (!url) return
const [, number, slug] = url.match(/\/(\d+)\/(.*)\.html$/)

View file

@ -5,7 +5,8 @@ const axios = require('axios')
dayjs.extend(utc)
const API_PROGRAM_ENDPOINT = 'https://epg.orangetv.orange.es/epg/Smartphone_Android/1_PRO'
const API_CHANNEL_ENDPOINT = 'https://pc.orangetv.orange.es/pc/api/rtv/v1/GetChannelList?bouquet_id=1&model_external_id=PC&filter_unsupported_channels=false&client=json'
const API_CHANNEL_ENDPOINT =
'https://pc.orangetv.orange.es/pc/api/rtv/v1/GetChannelList?bouquet_id=1&model_external_id=PC&filter_unsupported_channels=false&client=json'
const API_IMAGE_ENDPOINT = 'https://pc.orangetv.orange.es/pc/api/rtv/v1/images'
module.exports = {
@ -25,15 +26,9 @@ module.exports = {
if (!items.length) return programs
const promises = [
axios.get(
`${API_PROGRAM_ENDPOINT}/${date.format('YYYYMMDD')}_8h_1.json`,
),
axios.get(
`${API_PROGRAM_ENDPOINT}/${date.format('YYYYMMDD')}_8h_2.json`,
),
axios.get(
`${API_PROGRAM_ENDPOINT}/${date.format('YYYYMMDD')}_8h_3.json`,
),
axios.get(`${API_PROGRAM_ENDPOINT}/${date.format('YYYYMMDD')}_8h_1.json`),
axios.get(`${API_PROGRAM_ENDPOINT}/${date.format('YYYYMMDD')}_8h_2.json`),
axios.get(`${API_PROGRAM_ENDPOINT}/${date.format('YYYYMMDD')}_8h_3.json`)
]
await Promise.allSettled(promises)
@ -42,7 +37,9 @@ module.exports = {
if (r.status === 'fulfilled') {
const parsed = parseItems(r.value.data, channel)
items = items.filter((item, index) => items.findIndex(oi => oi.id === item.id) === index).concat(parsed)
items = items
.filter((item, index) => items.findIndex(oi => oi.id === item.id) === index)
.concat(parsed)
}
})
})
@ -57,7 +54,7 @@ module.exports = {
episode: item.episodeId || null,
icon: parseIcon(item),
start: dayjs.utc(item.startDate) || null,
stop: dayjs.utc(item.endDate) || null,
stop: dayjs.utc(item.endDate) || null
})
})
@ -72,42 +69,40 @@ module.exports = {
return data.response.map(item => {
return {
lang: 'es',
name: item.name,
name: item.name,
site_id: item.externalChannelId
}
})
}
}
function parseIcon(item){
function parseIcon(item) {
if (item.attachments.length > 0) {
const cover = item.attachments.find(i => i.name === 'COVER' || i.name === 'cover')
if(item.attachments.length > 0){
const cover = item.attachments.find(i => i.name === "COVER" || i.name === "cover")
if(cover)
{
return `${API_IMAGE_ENDPOINT}${cover.value}`;
if (cover) {
return `${API_IMAGE_ENDPOINT}${cover.value}`
}
}
return ''
}
function parseGenres(item){
return item.genres.map(i => i.name);
function parseGenres(item) {
return item.genres.map(i => i.name)
}
function parseItems(content, channel) {
const json = typeof content === 'string' ? JSON.parse(content) : Array.isArray(content) ? content : []
const json =
typeof content === 'string' ? JSON.parse(content) : Array.isArray(content) ? content : []
if (!Array.isArray(json)) {
return [];
return []
}
const channelData = json.find(i => i.channelExternalId == channel.site_id);
const channelData = json.find(i => i.channelExternalId == channel.site_id)
if(!channelData)
return [];
if (!channelData) return []
return channelData.programs;
return channelData.programs
}

View file

@ -14,11 +14,15 @@ const channel = {
}
it('can generate valid url', () => {
expect(url({ date })).toBe(`https://epg.orangetv.orange.es/epg/Smartphone_Android/1_PRO/${date.format('YYYYMMDD')}_8h_1.json`)
expect(url({ date })).toBe(
`https://epg.orangetv.orange.es/epg/Smartphone_Android/1_PRO/${date.format(
'YYYYMMDD'
)}_8h_1.json`
)
})
it('can parse response', async () => {
const content = fs.readFileSync(path.resolve(__dirname, '__data__/data.json')).toString()
const content = fs.readFileSync(path.resolve(__dirname, '__data__/data.json')).toString()
let results = await parser({ content, channel, date })
results = results.map(p => {
p.start = p.start.toJSON()
@ -28,13 +32,14 @@ it('can parse response', async () => {
expect(results.length).toBe(4)
var sampleResult = results[0];
var sampleResult = results[0]
expect(sampleResult).toMatchObject({
start: '2024-11-30T22:36:51.000Z',
stop: '2024-11-30T23:57:25.000Z',
category: ['Cine', 'Romance', 'Comedia', 'Comedia Romántica'],
description: 'Charlie trabaja como director en una escuela de primaria y goza de una placentera existencia junto a sus amigos. A pesar de ello, no es feliz porque cada vez que se enamora pierde la cordura.',
description:
'Charlie trabaja como director en una escuela de primaria y goza de una placentera existencia junto a sus amigos. A pesar de ello, no es feliz porque cada vez que se enamora pierde la cordura.',
title: 'Loco de amor'
})
})

View file

@ -5,7 +5,12 @@ const timezone = require('dayjs/plugin/timezone')
dayjs.extend(utc)
dayjs.extend(timezone)
const packages = { 'OSNTV CONNECT': 3720, 'OSNTV PRIME': 3733, 'ALFA': 1281, 'OSN PINOY PLUS EXTRA': 3519 }
const packages = {
'OSNTV CONNECT': 3720,
'OSNTV PRIME': 3733,
ALFA: 1281,
'OSN PINOY PLUS EXTRA': 3519
}
const country = 'AE'
const tz = 'Asia/Dubai'
@ -13,11 +18,9 @@ module.exports = {
site: 'osn.com',
days: 2,
url({ channel, date }) {
return `https://www.osn.com/api/TVScheduleWebService.asmx/time?dt=${
encodeURIComponent(date.format('MM/DD/YYYY'))
}&co=${country}&ch=${
channel.site_id
}&mo=false&hr=0`
return `https://www.osn.com/api/TVScheduleWebService.asmx/time?dt=${encodeURIComponent(
date.format('MM/DD/YYYY')
)}&co=${country}&ch=${channel.site_id}&mo=false&hr=0`
},
request: {
headers({ channel }) {
@ -46,7 +49,9 @@ module.exports = {
const axios = require('axios')
for (const pkg of Object.values(packages)) {
const channels = await axios
.get(`https://www.osn.com/api/tvchannels.ashx?culture=en-US&packageId=${pkg}&country=${country}`)
.get(
`https://www.osn.com/api/tvchannels.ashx?culture=en-US&packageId=${pkg}&country=${country}`
)
.then(response => response.data)
.catch(console.error)

View file

@ -28,32 +28,30 @@ it('can generate valid url', () => {
})
it('can parse response (ar)', () => {
const result = parser({ date, channel: channelAR, content })
.map(a => {
a.start = a.start.toJSON()
a.stop = a.stop.toJSON()
return a
})
const result = parser({ date, channel: channelAR, content }).map(a => {
a.start = a.start.toJSON()
a.stop = a.stop.toJSON()
return a
})
expect(result.length).toBe(29)
expect(result[1]).toMatchObject({
start: '2024-11-26T20:50:00.000Z',
stop: '2024-11-26T21:45:00.000Z',
title: 'بيت الحلويات: الحلقة 3',
title: 'بيت الحلويات: الحلقة 3'
})
})
it('can parse response (en)', () => {
const result = parser({ date, channel: channelEN, content })
.map(a => {
a.start = a.start.toJSON()
a.stop = a.stop.toJSON()
return a
})
const result = parser({ date, channel: channelEN, content }).map(a => {
a.start = a.start.toJSON()
a.stop = a.stop.toJSON()
return a
})
expect(result.length).toBe(29)
expect(result[1]).toMatchObject({
start: '2024-11-26T20:50:00.000Z',
stop: '2024-11-26T21:45:00.000Z',
title: 'House Of Desserts: Episode 3',
title: 'House Of Desserts: Episode 3'
})
})

View file

@ -27,7 +27,7 @@ function parseItems(content, date) {
let data
try {
data = JSON.parse(json)
} catch (error) {
} catch {
return []
}

View file

@ -129,36 +129,34 @@ module.exports = {
)
}
}
function fetchApiVersion() {
return new Promise(async (resolve, reject) => {
async function fetchApiVersion() {
// you'll never find what happened here :)
// load the pickx page and get the hash from the MWC configuration.
// it's not the best way to get the version but it's the only way to get it.
const hashUrl = 'https://www.pickx.be/nl/televisie/tv-gids'
const hashData = await axios
.get(hashUrl)
.then(r => {
const re = /"hashes":\["(.*)"\]/
const match = r.data.match(re)
if (match && match[1]) {
return match[1]
} else {
throw new Error('React app version hash not found')
}
})
.catch(console.error)
const versionUrl = `https://www.pickx.be/api/s-${hashData}`
const response = await axios.get(versionUrl, {
headers: {
Origin: 'https://www.pickx.be',
Referer: 'https://www.pickx.be/'
}
})
return new Promise((resolve, reject) => {
try {
// you'll never find what happened here :)
// load the pickx page and get the hash from the MWC configuration.
// it's not the best way to get the version but it's the only way to get it.
const hashUrl = 'https://www.pickx.be/nl/televisie/tv-gids';
const hashData = await axios.get(hashUrl)
.then(r => {
const re = /"hashes":\["(.*)"\]/
const match = r.data.match(re)
if (match && match[1]) {
return match[1]
} else {
throw new Error('React app version hash not found')
}
})
.catch(console.error);
const versionUrl = `https://www.pickx.be/api/s-${hashData}`
const response = await axios.get(versionUrl, {
headers: {
Origin: 'https://www.pickx.be',
Referer: 'https://www.pickx.be/'
}
})
if (response.status === 200) {
apiVersion = response.data.version
resolve()

View file

@ -6,14 +6,7 @@ jest.mock('./pickx.be.config.js', () => {
}
})
const {
parser,
url,
request,
fetchApiVersion,
setApiVersion,
getApiVersion
} = require('./pickx.be.config.js')
const { parser, url, request, setApiVersion } = require('./pickx.be.config.js')
const fs = require('fs')
const path = require('path')
@ -36,7 +29,7 @@ beforeEach(() => {
it('can generate valid url', async () => {
const generatedUrl = await url({ channel, date })
expect(generatedUrl).toBe(
`https://px-epg.azureedge.net/airings/mockedApiVersion/2023-12-13/channel/UID0118?timezone=Europe%2FBrussels`
'https://px-epg.azureedge.net/airings/mockedApiVersion/2023-12-13/channel/UID0118?timezone=Europe%2FBrussels'
)
})

View file

@ -8,9 +8,9 @@ module.exports = {
site: 'player.ee.co.uk',
days: 2,
url({ date, channel, hour = 0 }) {
return `https://api.youview.tv/metadata/linear/v2/schedule/by-servicelocator?serviceLocator=${
encodeURIComponent(channel.site_id)
}&interval=${date.format('YYYY-MM-DD')}T${hour.toString().padStart(2,'0')}Z/PT12H`
return `https://api.youview.tv/metadata/linear/v2/schedule/by-servicelocator?serviceLocator=${encodeURIComponent(
channel.site_id
)}&interval=${date.format('YYYY-MM-DD')}T${hour.toString().padStart(2, '0')}Z/PT12H`
},
request: {
headers: {
@ -39,7 +39,7 @@ module.exports = {
const stop = start.add(item.publishedDuration, 's')
const description = item.synopsis
if (description) {
const matches = description.trim().match(/\(?S(\d+)[\/\s]Ep(\d+)\)?/)
const matches = description.trim().match(/\(?S(\d+)[/\s]Ep(\d+)\)?/)
if (matches) {
if (matches[1]) {
season = parseInt(matches[1])
@ -79,7 +79,7 @@ module.exports = {
'BTSubscriptionCodesExtension'
]
const result = await axios
.get(`https://api.youview.tv/metadata/linear/v2/linear-services`, {
.get('https://api.youview.tv/metadata/linear/v2/linear-services', {
params: {
contentTargetingToken: token,
extensions: extensions.join(',')
@ -89,14 +89,16 @@ module.exports = {
.then(response => response.data)
.catch(console.error)
return result?.items
.filter(channel => channel.contentTypes.indexOf('tv') >= 0)
.map(channel => {
return {
lang: 'en',
site_id: channel.serviceLocator,
name: channel.fullName
}
}) || []
return (
result?.items
.filter(channel => channel.contentTypes.indexOf('tv') >= 0)
.map(channel => {
return {
lang: 'en',
site_id: channel.serviceLocator,
name: channel.fullName
}
}) || []
)
}
}

View file

@ -15,8 +15,11 @@ const channel = {
xmltv_id: 'HGTV.uk'
}
axios.get.mockImplementation((url, opts) => {
if (url === 'https://api.youview.tv/metadata/linear/v2/schedule/by-servicelocator?serviceLocator=dvb%3A%2F%2F233a..6d60&interval=2023-12-13T12Z/PT12H') {
axios.get.mockImplementation(url => {
if (
url ===
'https://api.youview.tv/metadata/linear/v2/schedule/by-servicelocator?serviceLocator=dvb%3A%2F%2F233a..6d60&interval=2023-12-13T12Z/PT12H'
) {
return Promise.resolve({
data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/data1.json')))
})
@ -33,12 +36,11 @@ it('can generate valid url', () => {
it('can parse response', async () => {
const content = fs.readFileSync(path.resolve(__dirname, '__data__/data.json'))
const result = (await parser({ content, channel, date }))
.map(p => {
p.start = p.start.toJSON()
p.stop = p.stop.toJSON()
return p
})
const result = (await parser({ content, channel, date })).map(p => {
p.start = p.start.toJSON()
p.stop = p.stop.toJSON()
return p
})
expect(result).toMatchObject([
{

View file

@ -43,7 +43,7 @@ module.exports = {
const axios = require('axios')
const data = await axios
.post(
`https://playtv.unifi.com.my:7053/VSP/V3/QueryAllChannel`,
'https://playtv.unifi.com.my:7053/VSP/V3/QueryAllChannel',
{ isReturnAllMedia: '0' },
{
params: {
@ -74,7 +74,7 @@ function parseItems(content, channel) {
const channelData = data.find(i => i.id == channel.site_id)
return channelData.items && Array.isArray(channelData.items) ? channelData.items : []
} catch (err) {
} catch {
return []
}
}

View file

@ -46,10 +46,10 @@ module.exports = {
return programs
},
async channels({ country, lang }) {
async channels() {
const axios = require('axios')
const data = await axios
.get(`https://www.programetv.ro/api/station/index/`)
.get('https://www.programetv.ro/api/station/index/')
.then(r => r.data)
.catch(console.log)

View file

@ -62,7 +62,7 @@ module.exports = {
$('.channelList-listItemsLink').each((i, el) => {
const name = $(el).attr('title')
const url = $(el).attr('href')
const [, site_id] = url.match(/\/programme\-(.*)\.html$/i)
const [, site_id] = url.match(/\/programme-(.*)\.html$/i)
channels.push({
lang: 'fr',

View file

@ -51,7 +51,7 @@ module.exports = {
return data.programmes.map(item => {
const site_id = item.url.replace('/', '')
const name = site_id.replace(/\-/gi, ' ')
const name = site_id.replace(/-/gi, ' ')
return {
lang: 'fr',

View file

@ -13,19 +13,17 @@ module.exports = {
site: 'programme.tvb.com',
days: 2,
url({ channel, date, time = null }) {
return `https://programme.tvb.com/api/schedule?input_date=${
date.format('YYYYMMDD')
}&network_code=${channel.site_id}&_t=${time ? time : parseInt(Date.now() / 1000)}`
return `https://programme.tvb.com/api/schedule?input_date=${date.format(
'YYYYMMDD'
)}&network_code=${channel.site_id}&_t=${time ? time : parseInt(Date.now() / 1000)}`
},
parser({ content, channel, date }) {
const programs = []
const data = content ? JSON.parse(content) : {}
if (Array.isArray(data.data?.list)) {
const dt = date.format('YYYY-MM-DD')
for (const d of data.data.list) {
if (Array.isArray(d.schedules)) {
const schedules = d.schedules
.filter(s => s.network_code === channel.site_id)
const schedules = d.schedules.filter(s => s.network_code === channel.site_id)
schedules.forEach((s, i) => {
const start = dayjs.tz(s.event_datetime, 'YYYY-MM-DD HH:mm:ss', tz)
let stop
@ -64,7 +62,7 @@ module.exports = {
if (assets) {
queues.push(...assets.map(a => base + '/' + a))
} else {
const metadata = content.match(/e\=(\[(.*?)\])/)
const metadata = content.match(/e=(\[(.*?)\])/)
if (metadata) {
const infos = eval(metadata[1])
if (Array.isArray(infos)) {

View file

@ -34,7 +34,7 @@ it('can parse response (en)', () => {
expect(results[1]).toMatchObject({
start: '2024-12-06T15:55:00.000Z',
stop: '2024-12-06T16:55:00.000Z',
title: 'Line Walker: Bull Fight#16[Can][PG]',
title: 'Line Walker: Bull Fight#16[Can][PG]'
})
})
@ -51,7 +51,7 @@ it('can parse response (zh)', () => {
stop: '2024-12-06T16:55:00.000Z',
title: '使徒行者3#16[粵][PG]',
description:
'文鼎從淑梅手上救走大聖爺兒子,大聖爺還恩於歡喜,答允支持九指強。崇聯社定下選舉日子,恰巧是韋傑出獄之日,頭目們顧念舊日恩義,紛紛轉投浩洋。浩洋帶亞希逛傢俬店,憧憬二人未來。亞希向家強承認愛上浩洋,要求退出臥底任務。作榮與歡喜暗中會面,將國際犯罪組織「永恆幫」情報交給他。阿火遭家強出賣,到沐足店搶錢。家強逮住阿火,惟被合星誤會而受拘捕。家強把正植遺下的頸鏈和學生證交還,合星意識到家強已知悉正植身世。',
'文鼎從淑梅手上救走大聖爺兒子,大聖爺還恩於歡喜,答允支持九指強。崇聯社定下選舉日子,恰巧是韋傑出獄之日,頭目們顧念舊日恩義,紛紛轉投浩洋。浩洋帶亞希逛傢俬店,憧憬二人未來。亞希向家強承認愛上浩洋,要求退出臥底任務。作榮與歡喜暗中會面,將國際犯罪組織「永恆幫」情報交給他。阿火遭家強出賣,到沐足店搶錢。家強逮住阿火,惟被合星誤會而受拘捕。家強把正植遺下的頸鏈和學生證交還,合星意識到家強已知悉正植身世。'
})
})

View file

@ -50,7 +50,7 @@ module.exports = {
$('ul.channelList a').each((i, el) => {
const name = $(el).text()
const url = $(el).attr('href')
const [, site_id] = url.match(/^\/program\-tv\/(.*)$/i)
const [, site_id] = url.match(/^\/program-tv\/(.*)$/i)
channels.push({
lang: 'pl',

View file

@ -58,7 +58,7 @@ function parseItems(content, channel) {
let data
try {
data = JSON.parse(content)
} catch (error) {
} catch {
return []
}

View file

@ -11,9 +11,7 @@ dayjs.extend(timezone)
dayjs.extend(utc)
dayjs.extend(customParseFormat)
doFetch
.setCheckResult(false)
.setDebugger(debug)
doFetch.setCheckResult(false).setDebugger(debug)
const tz = 'Asia/Riyadh'
const defaultHeaders = {
@ -47,7 +45,7 @@ module.exports = {
headers: {
...defaultHeaders,
'X-Requested-With': 'XMLHttpRequest',
cookie: cookies[channel.lang],
cookie: cookies[channel.lang]
}
}
queues.push({ i: item, url, params })
@ -61,7 +59,7 @@ module.exports = {
},
async channels({ lang = 'en' }) {
const result = await axios
.get(`https://rotana.net/api/channels`)
.get('https://rotana.net/api/channels')
.then(response => response.data)
.catch(console.error)
@ -88,34 +86,37 @@ function parseProgram(item, result) {
item.description = desc
}
}
break;
break
case 'Element':
if (el.name === 'span') {
const [k, v] = $(el).text().split(':').map(a => a.trim())
const [k, v] = $(el)
.text()
.split(':')
.map(a => a.trim())
switch (k) {
case 'Category':
case 'التصنيف':
item.category = v;
break;
item.category = v
break
case 'Country':
case 'البلد':
item.country = v;
break;
item.country = v
break
case 'Director':
case 'المخرج':
item.director = v;
break;
item.director = v
break
case 'Language':
case 'اللغة':
item.language = v;
break;
item.language = v
break
case 'Release Year':
case 'سنة الإصدار':
item.date = v;
break;
item.date = v
break
}
}
break;
break
}
}
}
@ -142,7 +143,9 @@ function parseItems(content, date) {
const heading = top.find('.iq-accordion-title .big-title')
if (heading.length) {
const progId = top.attr('id')
const title = heading.find('span:eq(1)').text()
const title = heading
.find('span:eq(1)')
.text()
.split('\n')
.map(a => a.trim())
.join(' ')
@ -151,7 +154,7 @@ function parseItems(content, date) {
items.push({
program: progId.substr(progId.indexOf('-') + 1),
title: title ? title.trim() : title,
start: `${y}-${m}-${d} ${time.trim()}`,
start: `${y}-${m}-${d} ${time.trim()}`
})
}
}

View file

@ -19,7 +19,7 @@ const channel = {
}
const channelAr = Object.assign({}, channel, { lang: 'ar' })
axios.get.mockImplementation((url, opts) => {
axios.get.mockImplementation(url => {
if (url === 'https://rotana.net/en/streams?channel=439&itemId=736970') {
return Promise.resolve({
data: fs.readFileSync(path.resolve(__dirname, '__data__/program_en.html'))
@ -52,11 +52,13 @@ it('can generate valid arabic url', () => {
})
it('can parse english response', async () => {
const result = (await parser({
channel,
date,
content: fs.readFileSync(path.join(__dirname, '/__data__/content_en.html'))
})).map(a => {
const result = (
await parser({
channel,
date,
content: fs.readFileSync(path.join(__dirname, '/__data__/content_en.html'))
})
).map(a => {
a.start = a.start.toJSON()
a.stop = a.stop.toJSON()
return a
@ -69,17 +71,20 @@ it('can parse english response', async () => {
title: 'Khiyana Mashroua',
description:
'Hisham knows that his father has given all his wealth to his elder brother. This leads him to plan to kill his brother to make it look like a defense of honor, which he does by killing his wife along...',
image: 'https://s3.eu-central-1.amazonaws.com/rotana.website/spider_storage/1398X1000/1687084565',
image:
'https://s3.eu-central-1.amazonaws.com/rotana.website/spider_storage/1398X1000/1687084565',
category: 'Movie'
})
})
it('can parse arabic response', async () => {
const result = (await parser({
channel: channelAr,
date,
content: fs.readFileSync(path.join(__dirname, '/__data__/content_ar.html'))
})).map(a => {
const result = (
await parser({
channel: channelAr,
date,
content: fs.readFileSync(path.join(__dirname, '/__data__/content_ar.html'))
})
).map(a => {
a.start = a.start.toJSON()
a.stop = a.stop.toJSON()
return a
@ -92,7 +97,8 @@ it('can parse arabic response', async () => {
title: 'خيانة مشروعة',
description:
'يعلم هشام البحيري أن والده قد حرمه من الميراث، ووهب كل ثروته لشقيقه اﻷكبر، وهو ما يدفعه لتدبير جريمة قتل شقيقه لتبدو وكأنها دفاع عن الشرف، وذلك حين يقتل هشام زوجته مع شقيقه.',
image: 'https://s3.eu-central-1.amazonaws.com/rotana.website/spider_storage/1398X1000/1687084565',
image:
'https://s3.eu-central-1.amazonaws.com/rotana.website/spider_storage/1398X1000/1687084565',
category: 'فيلم'
})
})

View file

@ -53,7 +53,7 @@ async function parseItems(buffer) {
let data
try {
data = await pdf(buffer)
} catch (err) {
} catch {
return []
}

View file

@ -1,5 +1,4 @@
const _ = require('lodash')
const axios = require('axios')
const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
const timezone = require('dayjs/plugin/timezone')

View file

@ -1,5 +1,5 @@
const dayjs = require('dayjs')
const duration = require("dayjs/plugin/duration")
const duration = require('dayjs/plugin/duration')
const utc = require('dayjs/plugin/utc')
const timezone = require('dayjs/plugin/timezone')
const customParseFormat = require('dayjs/plugin/customParseFormat')
@ -10,72 +10,74 @@ dayjs.extend(customParseFormat)
dayjs.extend(duration)
module.exports = {
site: 's.mxtv.jp',
days: 1,
lang: 'ja',
url: function ({ date, channel }) {
const id = `SV${channel.site_id}EPG${date.format('YYYYMMDD')}`
return `https://s.mxtv.jp/bangumi_file/json01/${id}.json`
},
parser: function ({ content }) {
let programs = []
const items = parseItems(content)
items.forEach(item => {
programs.push({
title: item.Event_name,
description: item.Event_text,
category: parseCategory(item),
image: parseImage(item),
start: parseStart(item),
stop: parseStop(item)
})
})
return programs
},
channels() {
return [
{
lang: 'ja',
site_id: '1',
name: 'Tokyo MX1',
xmltv_id: 'TokyoMX1.jp'
},
{
lang: 'ja',
site_id: '2',
name: 'Tokyo MX2',
xmltv_id: 'TokyoMX2.jp'
}
]
}
site: 's.mxtv.jp',
days: 1,
lang: 'ja',
url: function ({ date, channel }) {
const id = `SV${channel.site_id}EPG${date.format('YYYYMMDD')}`
return `https://s.mxtv.jp/bangumi_file/json01/${id}.json`
},
parser: function ({ content }) {
let programs = []
const items = parseItems(content)
items.forEach(item => {
programs.push({
title: item.Event_name,
description: item.Event_text,
category: parseCategory(item),
image: parseImage(item),
start: parseStart(item),
stop: parseStop(item)
})
})
return programs
},
channels() {
return [
{
lang: 'ja',
site_id: '1',
name: 'Tokyo MX1',
xmltv_id: 'TokyoMX1.jp'
},
{
lang: 'ja',
site_id: '2',
name: 'Tokyo MX2',
xmltv_id: 'TokyoMX2.jp'
}
]
}
}
function parseImage(item) {
// Should return a string if we can output an image URL
// Might be done with `https://s.mxtv.jp/bangumi/link/weblinkU.csv?1722421896752` ?
return null
function parseImage() {
// Should return a string if we can output an image URL
// Might be done with `https://s.mxtv.jp/bangumi/link/weblinkU.csv?1722421896752` ?
return null
}
function parseCategory(item) {
// Should return a string if we can determine the category
// Might be done with `https://s.mxtv.jp/index_set/csv/ranking_bangumi_allU.csv` ?
return null
function parseCategory() {
// Should return a string if we can determine the category
// Might be done with `https://s.mxtv.jp/index_set/csv/ranking_bangumi_allU.csv` ?
return null
}
function parseStart(item) {
return dayjs.tz(item.Start_time.toString(), 'YYYY年MM月DD日HH時mm分ss秒', 'Asia/Tokyo')
return dayjs.tz(item.Start_time.toString(), 'YYYY年MM月DD日HH時mm分ss秒', 'Asia/Tokyo')
}
function parseStop(item) {
// Add the duration to the start time
const durationDate = dayjs(item.Duration, 'HH:mm:ss');
return parseStart(item).add(dayjs.duration({
hours: durationDate.hour(),
minutes: durationDate.minute(),
seconds: durationDate.second()
}))
// Add the duration to the start time
const durationDate = dayjs(item.Duration, 'HH:mm:ss')
return parseStart(item).add(
dayjs.duration({
hours: durationDate.hour(),
minutes: durationDate.minute(),
seconds: durationDate.second()
})
)
}
function parseItems(content) {
return JSON.parse(content) || []
return JSON.parse(content) || []
}

View file

@ -11,7 +11,8 @@ const channel = {
name: 'Tokyo MX2',
xmltv_id: 'TokyoMX2.jp'
}
const content = `[{ "Event_id": "0x6a57", "Start_time": "2024年07月27日05時00分00秒", "Duration": "01:00:00", "Event_name": "ヒーリングタイム&ヘッドラインニュース", "Event_text": "ねこの足跡", "Component": "480i 16:9 パンベクトルなし", "Sound": "ステレオ", "Event_detail": ""}]`
const content =
'[{ "Event_id": "0x6a57", "Start_time": "2024年07月27日05時00分00秒", "Duration": "01:00:00", "Event_name": "ヒーリングタイム&ヘッドラインニュース", "Event_text": "ねこの足跡", "Component": "480i 16:9 パンベクトルなし", "Sound": "ステレオ", "Event_detail": ""}]'
it('can generate valid url', () => {
const result = url({ date, channel })

View file

@ -114,7 +114,7 @@ module.exports = {
const $ = cheerio.load(data)
$('.main-container-channels-events > .container-channel-events').each((i, el) => {
const name = $(el).find('.channel-title').text().trim()
const channelId = name.replace(/\s\&\s/gi, ' &amp; ')
const channelId = name.replace(/\s&\s/gi, ' &amp; ')
if (!name) return

View file

@ -10,31 +10,36 @@ dayjs.extend(customParseFormat)
module.exports = {
site: 'shahid.mbc.net',
days: 2,
url({ channel, date}) {
return `https://api2.shahid.net/proxy/v2.1/shahid-epg-api/?csvChannelIds=${channel.site_id}&from=${date.format('YYYY-MM-DD')}T00:00:00.000Z&to=${date.format('YYYY-MM-DD')}T23:59:59.999Z&country=SA&language=${channel.lang}&Accept-Language=${channel.lang}`
url({ channel, date }) {
return `https://api2.shahid.net/proxy/v2.1/shahid-epg-api/?csvChannelIds=${
channel.site_id
}&from=${date.format('YYYY-MM-DD')}T00:00:00.000Z&to=${date.format(
'YYYY-MM-DD'
)}T23:59:59.999Z&country=SA&language=${channel.lang}&Accept-Language=${channel.lang}`
},
parser({ content, channel }) {
const programs = parseItems(content, channel)
.map(item => {
return {
title: item.title,
description: item.description,
session: item.seasonNumber,
episode: item.episodeNumber,
start: dayjs.tz(item.actualFrom, 'Asia/Riyadh').toISOString(),
stop: dayjs.tz(item.actualTo, 'Asia/Riyadh').toISOString()
}
})
const programs = parseItems(content, channel).map(item => {
return {
title: item.title,
description: item.description,
session: item.seasonNumber,
episode: item.episodeNumber,
start: dayjs.tz(item.actualFrom, 'Asia/Riyadh').toISOString(),
stop: dayjs.tz(item.actualTo, 'Asia/Riyadh').toISOString()
}
})
return programs
},
async channels({lang = 'en'}) {
async channels({ lang = 'en' }) {
const axios = require('axios')
const items = []
let page = 0
while (true) {
const result = await axios
.get(`https://api2.shahid.net/proxy/v2.1/product/filter?filter=%7B"pageNumber":${page},"pageSize":100,"productType":"LIVESTREAM","productSubType":"LIVE_CHANNEL"%7D&country=SA&language=${lang}&Accept-Language=${lang}`)
.get(
`https://api2.shahid.net/proxy/v2.1/product/filter?filter=%7B"pageNumber":${page},"pageSize":100,"productType":"LIVESTREAM","productSubType":"LIVE_CHANNEL"%7D&country=SA&language=${lang}&Accept-Language=${lang}`
)
.then(response => response.data)
.catch(console.error)
if (result.productList) {
@ -44,7 +49,7 @@ module.exports = {
continue
}
}
break;
break
}
const channels = items.map(channel => {
return {

View file

@ -9,7 +9,11 @@ const channel = { site_id: '996520', xmltv_id: 'AlAanTV.ae', lang: 'en' }
it('can generate valid url', () => {
expect(url({ channel, date })).toBe(
`https://api2.shahid.net/proxy/v2.1/shahid-epg-api/?csvChannelIds=${channel.site_id}&from=${date.format('YYYY-MM-DD')}T00:00:00.000Z&to=${date.format('YYYY-MM-DD')}T23:59:59.999Z&country=SA&language=${channel.lang}&Accept-Language=${channel.lang}`
`https://api2.shahid.net/proxy/v2.1/shahid-epg-api/?csvChannelIds=${
channel.site_id
}&from=${date.format('YYYY-MM-DD')}T00:00:00.000Z&to=${date.format(
'YYYY-MM-DD'
)}T23:59:59.999Z&country=SA&language=${channel.lang}&Accept-Language=${channel.lang}`
)
})
@ -22,9 +26,9 @@ it('can parse response', () => {
{
start: '2023-11-10T21:00:00.000Z',
stop: '2023-11-10T21:30:00.000Z',
title: 'Menassaatona Fi Osboo\'',
title: "Menassaatona Fi Osboo'",
description:
'The presenter reviews the most prominent episodes of news programs produced by the channel\'s team on a weekly basis, which include the most important global updates and developments at all levels.'
"The presenter reviews the most prominent episodes of news programs produced by the channel's team on a weekly basis, which include the most important global updates and developments at all levels."
}
])
})

View file

@ -40,7 +40,7 @@ module.exports = {
const cheerio = require('cheerio')
const data = await axios
.get(`https://www.singtel.com/personal/products-services/tv/tv-programme-guide`)
.get('https://www.singtel.com/personal/products-services/tv/tv-programme-guide')
.then(r => r.data)
.catch(console.log)
@ -62,7 +62,7 @@ function parseItems(content, channel) {
try {
const data = JSON.parse(content)
return data && data[channel.site_id] ? data[channel.site_id] : []
} catch (err) {
} catch {
return []
}
}

View file

@ -44,7 +44,7 @@ module.exports = {
const cheerio = require('cheerio')
const data = await axios
.get(`https://sjonvarp.is/`)
.get('https://sjonvarp.is/')
.then(r => r.data)
.catch(console.log)

View file

@ -12,9 +12,7 @@ module.exports = {
site: 'sky.com',
days: 2,
url({ date, channel }) {
return `https://awk.epgsky.com/hawk/linear/schedule/${
date.format('YYYYMMDD')
}/${
return `https://awk.epgsky.com/hawk/linear/schedule/${date.format('YYYYMMDD')}/${
channel.site_id
}`
},
@ -27,19 +25,18 @@ module.exports = {
.filter(schedule => schedule.sid === channel.site_id)
.forEach(schedule => {
if (Array.isArray(schedule.events)) {
schedule.events
.forEach(event => {
const start = dayjs.utc(event.st * 1000)
const stop = start.add(event.d, 's')
programs.push({
title: event.t,
description: event.sy,
season: event.seasonnumber,
episode: event.episodenumber,
start,
stop
})
schedule.events.forEach(event => {
const start = dayjs.utc(event.st * 1000)
const stop = start.add(event.d, 's')
programs.push({
title: event.t,
description: event.sy,
season: event.seasonnumber,
episode: event.episodenumber,
start,
stop
})
})
}
})
}
@ -55,10 +52,12 @@ module.exports = {
if (queue.t === 'r') {
const $ = cheerio.load(res)
const initialData = JSON.parse(decodeURIComponent($('#initialData').text()))
initialData.state.epgData.regions
.forEach(region => {
queues.push({ t: 'c', url: `https://awk.epgsky.com/hawk/linear/services/${region.bouquet}/${region.subBouquet}` })
initialData.state.epgData.regions.forEach(region => {
queues.push({
t: 'c',
url: `https://awk.epgsky.com/hawk/linear/services/${region.bouquet}/${region.subBouquet}`
})
})
}
// process channels
if (queue.t === 'c') {

View file

@ -15,9 +15,7 @@ const channel = {
}
it('can generate valid url', () => {
expect(url({ channel, date })).toBe(
'https://awk.epgsky.com/hawk/linear/schedule/20241214/4086'
)
expect(url({ channel, date })).toBe('https://awk.epgsky.com/hawk/linear/schedule/20241214/4086')
})
it('can parse response', () => {
@ -34,7 +32,7 @@ it('can parse response', () => {
stop: '2024-12-13T23:00:00.000Z',
title: 'The UnXplained With...',
description:
'The Hunt for Jack the Ripper: Jack the Ripper\'s identity has eluded police, historians and armchair detectives for over a century. What do we know about the notorious killer? (S3, ep 21)',
"The Hunt for Jack the Ripper: Jack the Ripper's identity has eluded police, historians and armchair detectives for over a century. What do we know about the notorious killer? (S3, ep 21)",
season: 4,
episode: 14
})

View file

@ -1,4 +1,4 @@
const { parser, url, request } = require('./skylife.co.kr.config.js')
const { parser, url } = require('./skylife.co.kr.config.js')
const fs = require('fs')
const path = require('path')
const dayjs = require('dayjs')

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -12,103 +12,110 @@ dayjs.extend(customParseFormat)
dayjs.extend(duration)
const exported = {
site: 'skyperfectv.co.jp',
days: 1,
lang: 'ja',
url: function ({ date, channel }) {
let [type, ...code] = channel.site_id.split('_')
code = code.join('_')
return `https://www.skyperfectv.co.jp/program/schedule/${type}/channel:${code}/date:${date.format('YYMMDD')}`
},
logo: function ({ channel }) {
return `https://www.skyperfectv.co.jp/library/common/img/channel/icon/basic/m_${channel.site_id.toLowerCase()}.gif`
},
// Specific function that permits to gather NSFW channels (needs confirmation)
async fetchSchedule({ date, channel }) {
const url = exported.url({ date, channel })
const response = await axios.get(url, {
headers: {
'Cookie': 'adult_auth=true'
}
site: 'skyperfectv.co.jp',
days: 1,
lang: 'ja',
url: function ({ date, channel }) {
let [type, ...code] = channel.site_id.split('_')
code = code.join('_')
return `https://www.skyperfectv.co.jp/program/schedule/${type}/channel:${code}/date:${date.format(
'YYMMDD'
)}`
},
logo: function ({ channel }) {
return `https://www.skyperfectv.co.jp/library/common/img/channel/icon/basic/m_${channel.site_id.toLowerCase()}.gif`
},
// Specific function that permits to gather NSFW channels (needs confirmation)
async fetchSchedule({ date, channel }) {
const url = exported.url({ date, channel })
const response = await axios.get(url, {
headers: {
Cookie: 'adult_auth=true'
}
})
return response.data
},
parser({ content, date }) {
const $ = cheerio.load(content)
const programs = []
const sections = [
{ id: 'js-am', addition: 0 },
{ id: 'js-pm', addition: 0 },
{ id: 'js-md', addition: 1 }
]
sections.forEach(({ id, addition }) => {
$(`#${id} > td`).each((index, element) => {
// `td` is a column for a day
// the next `td` will be the next day
const today = date.add(index + addition, 'd').tz('Asia/Tokyo')
const parseTime = timeString => {
// timeString is in the format "HH:mm"
// replace `today` with the time from timeString
const [hour, minute] = timeString.split(':').map(Number)
return today.hour(hour).minute(minute)
}
const $element = $(element) // Wrap element with Cheerio
$element.find('.p-program__item').each((itemIndex, itemElement) => {
const $itemElement = $(itemElement) // Wrap itemElement with Cheerio
const [start, stop] = $itemElement
.find('.p-program__range')
.first()
.text()
.split('〜')
.map(parseTime)
const title = $itemElement.find('.p-program__name').first().text()
const image = $itemElement.find('.js-program_thumbnail').first().attr('data-lazysrc')
programs.push({
title,
start,
stop,
image
})
})
return response.data
},
parser({ content, date }) {
const $ = cheerio.load(content)
const programs = []
})
})
const sections = [
{ id: 'js-am', addition: 0 },
{ id: 'js-pm', addition: 0 },
{ id: 'js-md', addition: 1 }
]
return programs
},
async channels() {
const pageParser = (content, type) => {
// type: "basic" | "premium"
// Returns an array of channel objects
sections.forEach(({ id, addition }) => {
$(`#${id} > td`).each((index, element) => {
// `td` is a column for a day
// the next `td` will be the next day
const today = date.add(index + addition, 'd').tz('Asia/Tokyo')
const $ = cheerio.load(content)
const channels = []
const parseTime = (timeString) => {
// timeString is in the format "HH:mm"
// replace `today` with the time from timeString
const [hour, minute] = timeString.split(':').map(Number)
return today.hour(hour).minute(minute)
}
$('.p-channel').each((index, element) => {
const site_id = `${type}_${$(element).find('.p-channel__id').text()}`
const name = $(element).find('.p-channel__name').text()
channels.push({ site_id, name, lang: 'ja' })
})
const $element = $(element) // Wrap element with Cheerio
$element.find('.p-program__item').each((itemIndex, itemElement) => {
const $itemElement = $(itemElement) // Wrap itemElement with Cheerio
const [start, stop] = $itemElement.find('.p-program__range').first().text().split('〜').map(parseTime)
const title = $itemElement.find('.p-program__name').first().text()
const image = $itemElement.find('.js-program_thumbnail').first().attr('data-lazysrc')
programs.push({
title,
start,
stop,
image
})
})
})
})
return programs
},
async channels() {
const pageParser = (content, type) => {
// type: "basic" | "premium"
// Returns an array of channel objects
const $ = cheerio.load(content)
const channels = []
$('.p-channel').each((index, element) => {
const site_id = `${type}_${$(element).find('.p-channel__id').text()}`
const name = $(element).find('.p-channel__name').text()
channels.push({ site_id, name, lang: 'ja' })
})
return channels
}
const getChannels = async (type) => {
const response = await axios.get(`https://www.skyperfectv.co.jp/program/schedule/${type}/`, {
headers: {
'Cookie': 'adult_auth=true;'
}
})
return pageParser(response.data, type)
}
const fetchAllChannels = async () => {
const basicChannels = await getChannels('basic')
const premiumChannels = await getChannels('premium')
const results = [...basicChannels, ...premiumChannels]
return results
}
return await fetchAllChannels()
return channels
}
const getChannels = async type => {
const response = await axios.get(`https://www.skyperfectv.co.jp/program/schedule/${type}/`, {
headers: {
Cookie: 'adult_auth=true;'
}
})
return pageParser(response.data, type)
}
const fetchAllChannels = async () => {
const basicChannels = await getChannels('basic')
const premiumChannels = await getChannels('premium')
const results = [...basicChannels, ...premiumChannels]
return results
}
return await fetchAllChannels()
}
}
module.exports = exported

File diff suppressed because one or more lines are too long

View file

@ -1,5 +1,4 @@
const cheerio = require('cheerio')
const { DateTime } = require('luxon')
const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
const timezone = require('dayjs/plugin/timezone')
@ -9,15 +8,6 @@ dayjs.extend(utc)
dayjs.extend(timezone)
dayjs.extend(customParseFormat)
const channel = [{ site_id: '1208', xmltv_id: 'AlAoula.ma', lang: 'ar' },
{ site_id: '4069', xmltv_id: 'Laayoune.ma', lang: 'ar' },
{ site_id: '4070', xmltv_id: 'Arryadia.ma', lang: 'ar' },
{ site_id: '4071', xmltv_id: 'Athaqafia.ma', lang: 'ar' },
{ site_id: '4072', xmltv_id: 'AlMaghribia.ma', lang: 'ar' },
{ site_id: '4073', xmltv_id: 'Assadissa.ma', lang: 'ar' },
{ site_id: '4075', xmltv_id: 'Tamazight.ma', lang: 'ar' }]
module.exports = {
site: 'snrt.ma',
channels: 'snrt.ma.channels.xml',
@ -68,7 +58,7 @@ module.exports = {
}
function parseStart($item, date) {
const timeString = $item('.grille-time').text().trim()
const timeString = $item('.grille-time').text().trim()
const [hours, minutes] = timeString.split('H').map(Number)
const formattedTime = `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:00`
@ -77,7 +67,6 @@ function parseStart($item, date) {
return dayjs.tz(dateString, 'YYYY-MM-DD HH:mm:ss', 'Africa/Casablanca')
}
function parseTitle($item) {
return $item('.program-title-sm').text().trim()
}

View file

@ -1,6 +1,4 @@
const { parser, url } = require('./snrt.ma.config.js')
const cheerio = require('cheerio')
const { DateTime } = require('luxon')
const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
const timezone = require('dayjs/plugin/timezone')
@ -25,16 +23,14 @@ it('can parse response', () => {
})
expect(results[0]).toMatchObject({
"category": "القرآن الكريم",
"description": "",
"start": "2024-12-19T06:00:00.000Z",
"stop": "2024-12-19T06:10:00.000Z",
"stop": "2024-12-19T06:30:00.000Z",
"title": "ﺍﻟﺴﻼﻡ ﺍﻟﻮﻃﻨﻲ + ﺍﻟﻘﺮﺁﻥ ﺍﻟﻜﺮﻳﻢ"
category: 'القرآن الكريم',
description: '',
start: '2024-12-19T06:00:00.000Z',
stop: '2024-12-19T06:30:00.000Z',
title: 'ﺍﻟﺴﻼﻡ ﺍﻟﻮﻃﻨﻲ + ﺍﻟﻘﺮﺁﻥ ﺍﻟﻜﺮﻳﻢ'
})
})
it('can handle empty guide', () => {
const result = parser({
date,

View file

@ -9,15 +9,9 @@ module.exports = {
url({ date, channel }) {
return `https://waf-starhub-metadata-api-p001.ifs.vubiquity.com/v3.1/epg/schedules?locale=${
languages[channel.lang]
}&locale_default=${
languages[channel.lang]
}&device=1&in_channel_id=${
}&locale_default=${languages[channel.lang]}&device=1&in_channel_id=${
channel.site_id
}&gt_end=${
date.unix()
}&lt_start=${
date.add(1, 'd').unix()
}&limit=100&page=1`
}&gt_end=${date.unix()}&lt_start=${date.add(1, 'd').unix()}&limit=100&page=1`
},
async parser({ content, date, channel }) {
const programs = []
@ -29,7 +23,11 @@ module.exports = {
}
if (res.page && res.page.current < res.page.total) {
res = await axios
.get(module.exports.url({ date, channel }).replace(/page=(\d+)/, `page=${res.page.current + 1}`))
.get(
module.exports
.url({ date, channel })
.replace(/page=(\d+)/, `page=${res.page.current + 1}`)
)
.then(r => r.data)
.catch(console.error)
} else {
@ -39,7 +37,7 @@ module.exports = {
}
const season = s => {
if (s) {
const [ , , n ] = s.match(/(S|Season )(\d+)/) || [null, null, null]
const [, , n] = s.match(/(S|Season )(\d+)/) || [null, null, null]
if (n) {
return parseInt(n)
}
@ -66,11 +64,9 @@ module.exports = {
let page = 1
while (true) {
const items = await axios
.get(`https://waf-starhub-metadata-api-p001.ifs.vubiquity.com/v3.1/epg/channels?locale=${
languages[lang]
}&locale_default=${
languages[lang]
}&device=1&limit=50&page=${page}`)
.get(
`https://waf-starhub-metadata-api-p001.ifs.vubiquity.com/v3.1/epg/channels?locale=${languages[lang]}&locale_default=${languages[lang]}&device=1&limit=50&page=${page}`
)
.then(r => r.data)
.catch(console.error)
if (items.resources) {

View file

@ -1,4 +1,4 @@
const { parser, url, request } = require('./starhubtvplus.com.config.js')
const { parser, url } = require('./starhubtvplus.com.config.js')
const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
const customParseFormat = require('dayjs/plugin/customParseFormat')
@ -36,9 +36,12 @@ it('can parse response', async () => {
title: 'Northern Rexposure',
subTitle: 'Hudson & Rex (Season 5)',
description:
'When Jesse\'s sister contacts him for help, he, Sarah and Rex head to Northern Ontario and find themselves in the middle of a deadly situation.',
"When Jesse's sister contacts him for help, he, Sarah and Rex head to Northern Ontario and find themselves in the middle of a deadly situation.",
category: ['Drama'],
image: ['https://poster.starhubgo.com/poster/ch511_hudson_rex5.jpg?w=960&h=540', 'https://poster.starhubgo.com/poster/ch511_hudson_rex5.jpg?w=341&h=192'],
image: [
'https://poster.starhubgo.com/poster/ch511_hudson_rex5.jpg?w=960&h=540',
'https://poster.starhubgo.com/poster/ch511_hudson_rex5.jpg?w=341&h=192'
],
season: 5,
episode: 15,
rating: 'PG13'

View file

@ -8,9 +8,7 @@ const debug = require('debug')('site:startimestv.com')
dayjs.extend(utc)
dayjs.extend(customParseFormat)
doFetch
.setDebugger(debug)
.setMaxWorker(5)
doFetch.setDebugger(debug).setMaxWorker(5)
module.exports = {
site: 'startimestv.com',
@ -24,7 +22,8 @@ module.exports = {
const programs = []
if (content) {
const $ = cheerio.load(content)
$('.box .mask').toArray()
$('.box .mask')
.toArray()
.forEach(el => {
let title = parseText($(el).find('h4'))
const [s, e] = title.substr(0, title.indexOf(' ')).split('-') || [null, null]
@ -53,7 +52,8 @@ module.exports = {
// process area-id
if (queue.t === 'a') {
const $ = cheerio.load(res)
$('dd.update-areaID').toArray()
$('dd.update-areaID')
.toArray()
.forEach(el => {
const dd = $(el)
const areaId = dd.attr('area-id')
@ -72,7 +72,8 @@ module.exports = {
if (queue.t === 's') {
if (res) {
const $ = cheerio.load(res)
$(`.channl .c`).toArray()
$('.channl .c')
.toArray()
.forEach(el => {
// only process channel with schedule only
const clazz = $(el).attr('class')
@ -98,13 +99,10 @@ module.exports = {
}
function parseText($item) {
let text = $item.text()
.replace(/\t/g, '')
.replace(/\n/g, ' ')
.trim()
let text = $item.text().replace(/\t/g, '').replace(/\n/g, ' ').trim()
while (true) {
if (text.match(/ /)) {
text = text.replace(/ /g, ' ')
if (text.match(/\s\s/)) {
text = text.replace(/\s\s/g, ' ')
continue
}
break

View file

@ -33,10 +33,10 @@ module.exports = {
return programs
},
async channels({ country, lang }) {
async channels() {
const axios = require('axios')
const data = await axios
.get(`https://streamingtvguides.com/Preferences`)
.get('https://streamingtvguides.com/Preferences')
.then(r => r.data)
.catch(console.log)

View file

@ -3,7 +3,7 @@ const dayjs = require('dayjs')
const API_STATIC_ENDPOINT = 'https://static.spark.telenet.tv/eng/web/epg-service-lite/be'
const API_PROD_ENDPOINT = 'https://spark-prod-be.gnp.cloud.telenet.tv/eng/web/linear-service/v2'
const API_IMAGE_ENDPOINT = 'https://staticqbr-prod-be.gnp.cloud.telenet.tv/image-service';
const API_IMAGE_ENDPOINT = 'https://staticqbr-prod-be.gnp.cloud.telenet.tv/image-service'
module.exports = {
site: 'telenet.tv',
@ -94,7 +94,7 @@ module.exports = {
async function loadProgramDetails(item, channel) {
if (!item.id) return {}
const url = `${API_PROD_ENDPOINT}/replayEvent/${item.id}?returnLinearContent=true&language=${channel.lang}`
const url = `${API_PROD_ENDPOINT}/replayEvent/${item.id}?returnLinearContent=true&language=${channel.lang}`
const data = await axios
.get(url)
.then(r => r.data)
@ -134,5 +134,5 @@ function parseEpisode(detail) {
}
function parseIcon(item) {
return `${API_IMAGE_ENDPOINT}/intent/${item.id}/posterTile`;
return `${API_IMAGE_ENDPOINT}/intent/${item.id}/posterTile`
}

View file

@ -27,10 +27,10 @@ module.exports = {
return programs
},
async channels({ lang }) {
async channels() {
const axios = require('axios')
const data = await axios
.get(`https://telkussa.fi/API/Channels`)
.get('https://telkussa.fi/API/Channels')
.then(r => r.data)
.catch(console.log)

View file

@ -11,8 +11,7 @@ dayjs.extend(utc)
dayjs.extend(timezone)
dayjs.extend(customParseFormat)
doFetch
.setDebugger(debug)
doFetch.setDebugger(debug)
const tz = 'Asia/Jakarta'
@ -20,17 +19,14 @@ module.exports = {
site: 'tivie.id',
days: 2,
url({ channel, date }) {
return `https://tivie.id/channel/${
channel.site_id
}/${
date.format('YYYYMMDD')
}`
return `https://tivie.id/channel/${channel.site_id}/${date.format('YYYYMMDD')}`
},
async parser({ content, date }) {
const programs = []
if (content) {
const $ = cheerio.load(content)
const items = $('ul[x-data] > li[id*="event-"] > div.w-full').toArray()
const items = $('ul[x-data] > li[id*="event-"] > div.w-full')
.toArray()
.map(item => {
const $item = $(item)
const time = $item.find('div:nth-child(1) span:nth-child(1)')
@ -47,7 +43,12 @@ module.exports = {
p.title = parseText(info)
}
if (p.title) {
const [, , season, episode] = p.title.match(/( S(\d+))?, Ep\. (\d+)/) || [null, null, null, null]
const [, , season, episode] = p.title.match(/( S(\d+))?, Ep\. (\d+)/) || [
null,
null,
null,
null
]
if (season) {
p.season = parseInt(season)
}
@ -63,7 +64,7 @@ module.exports = {
.map(i => {
const url = i.url
delete i.url
return {i, url}
return { i, url }
})
if (queues.length) {
await doFetch(queues, (queue, res) => {
@ -84,7 +85,11 @@ module.exports = {
if (i < items.length - 1) {
items[i].stop = items[i + 1].start
} else {
items[i].stop = dayjs.tz(`${date.add(1, 'd').format('YYYY-MM-DD')} 00:00`, 'YYYY-MM-DD HH:mm', tz)
items[i].stop = dayjs.tz(
`${date.add(1, 'd').format('YYYY-MM-DD')} 00:00`,
'YYYY-MM-DD HH:mm',
tz
)
}
}
// add programs
@ -116,13 +121,10 @@ module.exports = {
}
function parseText($item) {
let text = $item.text()
.replace(/\t/g, '')
.replace(/\n/g, ' ')
.trim()
let text = $item.text().replace(/\t/g, '').replace(/\n/g, ' ').trim()
while (true) {
if (text.match(/ /)) {
text = text.replace(/ /g, ' ')
if (text.match(/\s\s/)) {
text = text.replace(/\s\s/g, ' ')
continue
}
break

View file

@ -20,10 +20,8 @@ const channel = {
axios.get.mockImplementation(url => {
const urls = {
'https://tivie.id/film/white-house-down-nwzDnwz9nAv6':
'program01.html',
'https://tivie.id/program/hudson-rex-s6-e14-nwzDnwvBmQr9':
'program02.html',
'https://tivie.id/film/white-house-down-nwzDnwz9nAv6': 'program01.html',
'https://tivie.id/program/hudson-rex-s6-e14-nwzDnwvBmQr9': 'program02.html'
}
let data = ''
if (urls[url] !== undefined) {
@ -38,9 +36,7 @@ it('can generate valid url', () => {
it('can parse response', async () => {
const content = fs.readFileSync(path.join(__dirname, '__data__', 'content.html'))
const results = (
await parser({ date, content, channel })
).map(p => {
const results = (await parser({ date, content, channel })).map(p => {
p.start = p.start.toJSON()
p.stop = p.stop.toJSON()
return p
@ -53,7 +49,8 @@ it('can parse response', async () => {
title: 'White House Down',
description:
'Saat melakukan tur di Gedung Putih bersama putrinya yang masih kecil, seorang perwira polisi beraksi untuk melindungi anaknya dan presiden dari sekelompok penjajah paramiliter bersenjata lengkap.',
image: 'https://i0.wp.com/is3.cloudhost.id/tivie/poster/2023/09/65116c78791c2-1695640694.jpg?resize=480,270',
image:
'https://i0.wp.com/is3.cloudhost.id/tivie/poster/2023/09/65116c78791c2-1695640694.jpg?resize=480,270'
})
expect(results[2]).toMatchObject({
start: '2024-12-30T18:00:00.000Z',
@ -61,9 +58,10 @@ it('can parse response', async () => {
title: 'Hudson & Rex S6, Ep. 14',
description:
'Saat guru musik Jesse terbunuh di studio rekamannya, Charlie dan Rex menghubungkan kejahatan tersebut dengan pembunuhan yang tampaknya tak ada hubungannya.',
image: 'https://i0.wp.com/is3.cloudhost.id/tivie/poster/2024/07/668b7ced47b25-1720417517.jpg?resize=480,270',
image:
'https://i0.wp.com/is3.cloudhost.id/tivie/poster/2024/07/668b7ced47b25-1720417517.jpg?resize=480,270',
season: 6,
episode: 14,
episode: 14
})
})
@ -71,7 +69,7 @@ it('can handle empty guide', async () => {
const results = await parser({
date,
channel,
content: '',
content: ''
})
expect(results).toMatchObject([])
})

View file

@ -73,7 +73,7 @@ function parseItems(content, channel) {
let parsed
try {
parsed = JSON.parse(content)
} catch (error) {
} catch {
return []
}
if (!parsed || !parsed.k) return []

View file

@ -70,7 +70,7 @@ module.exports = {
async function getTotalPageCount(region) {
const data = await axios
.get(`https://tv.mail.ru/ajax/channel/list/`, {
.get('https://tv.mail.ru/ajax/channel/list/', {
params: { page: 0 },
headers: {
cookie: `s=fver=0|geo=${region};`

View file

@ -40,7 +40,7 @@ module.exports = {
let offset = 0
while (offset !== undefined) {
const data = await axios
.get(`https://web-api.tv.nu/tableauLinearChannels`, {
.get('https://web-api.tv.nu/tableauLinearChannels', {
params: {
modules,
date: dayjs().format('YYYY-MM-DD'),

View file

@ -24,7 +24,7 @@ module.exports = {
return programs
},
async channels({ token, lang = en }) {
async channels({ token, lang = 'en' }) {
const axios = require('axios')
const ACCESS_TOKEN = token
? token

View file

@ -2,9 +2,7 @@ const dayjs = require('dayjs')
const doFetch = require('@ntlab/sfetch')
const debug = require('debug')('site:tv.yandex.ru')
doFetch
.setDebugger(debug)
.setMaxWorker(10)
doFetch.setDebugger(debug).setMaxWorker(10)
// enable to fetch guide description but its take a longer time
const detailedGuide = true
@ -12,14 +10,16 @@ const detailedGuide = true
// update this data by heading to https://tv.yandex.ru and change the values accordingly
const cookies = {
i: 'eIUfSP+/mzQWXcH+Cuz8o1vY+D2K8fhBd6Sj0xvbPZeO4l3cY+BvMp8fFIuM17l6UE1Z5+R2a18lP00ex9iYVJ+VT+c=',
spravka: 'dD0xNzM0MjA0NjM4O2k9MTI1LjE2NC4xNDkuMjAwO0Q9QTVCQ0IyOTI5RDQxNkU5NkEyOTcwMTNDMzZGMDAzNjRDNTFFNDM4QkE2Q0IyOTJDRjhCOTZDRDIzODdBQzk2MzRFRDc5QTk2Qjc2OEI1MUY5MTM5M0QzNkY3OEQ2OUY3OTUwNkQ3RjBCOEJGOEJDMjAwMTQ0RDUwRkFCMDNEQzJFMDI2OEI5OTk5OUJBNEFERUYwOEQ1MjUwQTE0QTI3RDU1MEQwM0U0O3U9MTczNDIwNDYzODUyNDYyNzg1NDtoPTIxNTc0ZTc2MDQ1ZjcwMDBkYmY0NTVkM2Q2ZWMyM2Y1',
spravka:
'dD0xNzM0MjA0NjM4O2k9MTI1LjE2NC4xNDkuMjAwO0Q9QTVCQ0IyOTI5RDQxNkU5NkEyOTcwMTNDMzZGMDAzNjRDNTFFNDM4QkE2Q0IyOTJDRjhCOTZDRDIzODdBQzk2MzRFRDc5QTk2Qjc2OEI1MUY5MTM5M0QzNkY3OEQ2OUY3OTUwNkQ3RjBCOEJGOEJDMjAwMTQ0RDUwRkFCMDNEQzJFMDI2OEI5OTk5OUJBNEFERUYwOEQ1MjUwQTE0QTI3RDU1MEQwM0U0O3U9MTczNDIwNDYzODUyNDYyNzg1NDtoPTIxNTc0ZTc2MDQ1ZjcwMDBkYmY0NTVkM2Q2ZWMyM2Y1',
yandexuid: '1197179041732383499',
yashr: '4682342911732383504',
yuidss: '1197179041732383499',
user_display: 824,
user_display: 824
}
const headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 OPR/114.0.0.0',
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 OPR/114.0.0.0'
}
const caches = {}
@ -50,7 +50,9 @@ module.exports = {
}
caches[cacheid].forEach(schedule => {
schedule.events
.filter(event => event.channelFamilyId == channel.site_id && date.isSame(event.start, 'day'))
.filter(
event => event.channelFamilyId == channel.site_id && date.isSame(event.start, 'day')
)
.forEach(event => {
if (events.indexOf(event.id) < 0) {
events.push(event.id)
@ -171,7 +173,10 @@ function parseContent(content, date, checkOnly = false) {
content = content.toString()
}
// got captcha, its look like our cookies has expired
if (content?.type === 'captcha' || (typeof content === 'string' && content.match(/SmartCaptcha/))) {
if (
content?.type === 'captcha' ||
(typeof content === 'string' && content.match(/SmartCaptcha/))
) {
throw new Error('Got captcha, please goto https://tv.yandex.ru and update cookies!')
}
if (typeof content === 'object') {
@ -210,35 +215,41 @@ function parseContent(content, date, checkOnly = false) {
headers['X-User-Session-Id'] = sessionId
}
if (checkOnly && region && tvSk.key && sessionId) {
valid = true;
valid = true
}
}
}
return checkOnly ? valid : [queues, schedules]
return checkOnly ? valid : [queues, schedules]
}
function parseCookies(headers) {
if (Array.isArray(headers['set-cookie'])) {
headers['set-cookie']
.forEach(cookie => {
const [key, value] = cookie.split('; ')[0].split('=')
if (cookies[key] !== value) {
cookies[key] = value
debug(`Update cookie ${key}=${value}`)
}
})
headers['set-cookie'].forEach(cookie => {
const [key, value] = cookie.split('; ')[0].split('=')
if (cookies[key] !== value) {
cookies[key] = value
debug(`Update cookie ${key}=${value}`)
}
})
}
}
function getSchedules(schedules) {
return schedules.filter(schedule => schedule.events.length);
return schedules.filter(schedule => schedule.events.length)
}
function getHeaders(data = {}) {
return Object.assign({}, headers, {
Cookie: Object.keys(cookies).map(cookie => `${cookie}=${cookies[cookie]}`).join('; ')
}, data)
return Object.assign(
{},
headers,
{
Cookie: Object.keys(cookies)
.map(cookie => `${cookie}=${cookies[cookie]}`)
.join('; ')
},
data
)
}
function getUrl(date, region = null, page = null, event = null) {
@ -253,7 +264,9 @@ function getUrl(date, region = null, page = null, event = null) {
url += `${url.endsWith('/') ? '' : '/'}event?eventId=${event.id}&programCoId=`
}
if (date) {
url += `${url.indexOf('?') < 0 ? '?' : '&'}date=${date.format('YYYY-MM-DD')}${!page ? '&grid=all' : ''}&period=all-day`
url += `${url.indexOf('?') < 0 ? '?' : '&'}date=${date.format('YYYY-MM-DD')}${
!page ? '&grid=all' : ''
}&period=all-day`
}
if (page && page.id !== undefined && page.offset !== undefined) {
url += `${url.indexOf('?') < 0 ? '?' : '&'}offset=${page.offset}`
@ -266,7 +279,7 @@ function getUrl(date, region = null, page = null, event = null) {
function getQueue(url, referer) {
const data = {
'Origin': 'https://tv.yandex.ru',
Origin: 'https://tv.yandex.ru'
}
if (referer) {
data['Referer'] = referer

View file

@ -16,7 +16,7 @@ const channel = {
site_id: '16',
xmltv_id: 'ChannelOne.ru'
}
axios.get.mockImplementation((url, opts) => {
axios.get.mockImplementation(url => {
if (url === 'https://tv.yandex.ru/?date=2023-11-26&grid=all&period=all-day') {
return Promise.resolve({
headers: {},
@ -29,7 +29,10 @@ axios.get.mockImplementation((url, opts) => {
data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/schedule.json')))
})
}
if (url === 'https://tv.yandex.ru/api/120809/main/chunk?page=0&date=2023-11-26&period=all-day&offset=0&limit=11') {
if (
url ===
'https://tv.yandex.ru/api/120809/main/chunk?page=0&date=2023-11-26&period=all-day&offset=0&limit=11'
) {
return Promise.resolve({
headers: {},
data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/schedule0.json')))
@ -44,9 +47,7 @@ axios.get.mockImplementation((url, opts) => {
})
it('can generate valid url', () => {
expect(url({ date })).toBe(
'https://tv.yandex.ru/?date=2023-11-26&grid=all&period=all-day'
)
expect(url({ date })).toBe('https://tv.yandex.ru/?date=2023-11-26&grid=all&period=all-day')
})
it('can generate valid request headers', () => {
@ -63,9 +64,7 @@ it('can generate valid request headers', () => {
it('can parse response', async () => {
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'))
const result = (
await parser({ content, date, channel })
).map(p => {
const result = (await parser({ content, date, channel })).map(p => {
p.start = p.start.toJSON()
p.stop = p.stop.toJSON()
return p
@ -77,7 +76,8 @@ it('can parse response', async () => {
stop: '2023-11-26T02:10:00.000Z',
title: 'ПОДКАСТ.ЛАБ. Мелодии моей жизни',
category: 'досуг',
description: 'Впереди вся ночь и есть о чем поговорить. Фильмы, музыка, любовь, звезды, еда, мода, анекдоты, спорт, деньги, настоящее, будущее - все это в творческом эксперименте.\nЛариса Гузеева читает любовные письма. Леонид Якубович рассказывает, кого не берут в пилоты. Арина Холина - какой секс способен довести до мужа или до развода. Валерий Сюткин на ходу сочиняет песню для Карины Кросс и Вали Карнавал. Дмитрий Дибров дарит новую жизнь любимой \"Антропологии\". Денис Казанский - все о футболе, хоккее и не только.\n\"ПОДКАСТЫ. ЛАБ\" - серия подкастов разной тематики, которые невозможно проспать. Интеллектуальные дискуссии после полуночи с самыми компетентными экспертами и актуальными спикерами.'
description:
'Впереди вся ночь и есть о чем поговорить. Фильмы, музыка, любовь, звезды, еда, мода, анекдоты, спорт, деньги, настоящее, будущее - все это в творческом эксперименте.\nЛариса Гузеева читает любовные письма. Леонид Якубович рассказывает, кого не берут в пилоты. Арина Холина - какой секс способен довести до мужа или до развода. Валерий Сюткин на ходу сочиняет песню для Карины Кросс и Вали Карнавал. Дмитрий Дибров дарит новую жизнь любимой "Антропологии". Денис Казанский - все о футболе, хоккее и не только.\n"ПОДКАСТЫ. ЛАБ" - серия подкастов разной тематики, которые невозможно проспать. Интеллектуальные дискуссии после полуночи с самыми компетентными экспертами и актуальными спикерами.'
}
])
})

Some files were not shown because too many files have changed in this diff Show more