Fix linter issues

This commit is contained in:
freearhey
2025-07-28 04:07:40 +03:00
parent 851aba2438
commit 7afd3fe3fe
5 changed files with 671 additions and 646 deletions

View File

@@ -10,7 +10,8 @@
* const users = [{name: 'john', age: 30}, {name: 'jane', age: 25}];
* sortBy(users, x => x.age); // [{name: 'jane', age: 25}, {name: 'john', age: 30}]
*/
export const sortBy = <T>(arr: T[], fn: (item: T) => number | string): T[] => [...arr].sort((a, b) => fn(a) > fn(b) ? 1 : -1)
export const sortBy = <T>(arr: T[], fn: (item: T) => number | string): T[] =>
[...arr].sort((a, b) => (fn(a) > fn(b) ? 1 : -1))
/**
* Sorts an array by multiple criteria with customizable sort orders.
@@ -26,10 +27,19 @@ export const sortBy = <T>(arr: T[], fn: (item: T) => number | string): T[] => [.
* orderBy(users, [x => x.age, x => x.name], ['desc', 'asc']);
* // [{name: 'bob', age: 30}, {name: 'john', age: 30}, {name: 'jane', age: 25}]
*/
export const orderBy = (arr: Array<unknown>, fns: Array<(item: unknown) => string | number>, orders: Array<string> = []): Array<unknown> => [...arr].sort((a, b) =>
fns.reduce((acc, fn, i) =>
acc || ((orders[i] === 'desc' ? fn(b) > fn(a) : fn(a) > fn(b)) ? 1 : fn(a) === fn(b) ? 0 : -1), 0)
)
export const orderBy = (
arr: Array<unknown>,
fns: Array<(item: unknown) => string | number>,
orders: Array<string> = []
): Array<unknown> =>
[...arr].sort((a, b) =>
fns.reduce(
(acc, fn, i) =>
acc ||
((orders[i] === 'desc' ? fn(b) > fn(a) : fn(a) > fn(b)) ? 1 : fn(a) === fn(b) ? 0 : -1),
0
)
)
/**
* Creates a duplicate-free version of an array using an iteratee function to generate
@@ -44,7 +54,8 @@ export const orderBy = (arr: Array<unknown>, fns: Array<(item: unknown) => strin
* const users = [{id: 1, name: 'john'}, {id: 2, name: 'jane'}, {id: 1, name: 'john'}];
* uniqBy(users, x => x.id); // [{id: 1, name: 'john'}, {id: 2, name: 'jane'}]
*/
export const uniqBy = <T>(arr: T[], fn: (item: T) => unknown): T[] => arr.filter((item, index) => arr.findIndex(x => fn(x) === fn(item)) === index)
export const uniqBy = <T>(arr: T[], fn: (item: T) => unknown): T[] =>
arr.filter((item, index) => arr.findIndex(x => fn(x) === fn(item)) === index)
/**
* Converts a string to start case (capitalizes the first letter of each word).
@@ -59,7 +70,8 @@ export const uniqBy = <T>(arr: T[], fn: (item: T) => unknown): T[] => arr.filter
* startCase('hello-world'); // "Hello World"
* startCase('hello world'); // "Hello World"
*/
export const startCase = (str: string): string => str
.replace(/([a-z])([A-Z])/g, '$1 $2') // Split camelCase
.replace(/[_-]/g, ' ') // Replace underscores and hyphens with spaces
.replace(/\b\w/g, c => c.toUpperCase()) // Capitalize first letter of each word
export const startCase = (str: string): string =>
str
.replace(/([a-z])([A-Z])/g, '$1 $2') // Split camelCase
.replace(/[_-]/g, ' ') // Replace underscores and hyphens with spaces
.replace(/\b\w/g, c => c.toUpperCase()) // Capitalize first letter of each word

View File

@@ -19,11 +19,11 @@ module.exports = {
const now = dayjs()
const demain = now.add(1, 'd')
if (date && date.isSame(demain, 'day')) {
return `https://www.guidetnt.com/tv-demain/programme-${channel.site_id}`
return `https://www.guidetnt.com/tv-demain/programme-${channel.site_id}`
} else if (!date || date.isSame(now, 'day')) {
return `https://www.guidetnt.com/tv/programme-${channel.site_id}`
return `https://www.guidetnt.com/tv/programme-${channel.site_id}`
} else {
return null
return null
}
},
async parser({ content, date }) {
@@ -57,8 +57,8 @@ module.exports = {
let category = parseCategory($item)
let description = parseDescription($item)
const itemDetailsURL = parseDescriptionURL($item)
if(itemDetailsURL) {
const url = 'https://www.guidetnt.com' + itemDetailsURL
if (itemDetailsURL) {
const url = 'https://www.guidetnt.com' + itemDetailsURL
try {
const response = await axios.get(url)
itemDetails = parseItemDetails(response.data)
@@ -66,21 +66,21 @@ module.exports = {
console.error(`Erreur lors du fetch des détails pour l'item: ${url}`, err)
}
const timeRange = parseTimeRange(itemDetails?.programHour, date.format('YYYY-MM-DD'))
start = timeRange?.start
stop = timeRange?.stop
const timeRange = parseTimeRange(itemDetails?.programHour, date.format('YYYY-MM-DD'))
start = timeRange?.start
stop = timeRange?.stop
subTitle = itemDetails?.subTitle
if (title == subTitle) subTitle = null
description = itemDetails?.description
subTitle = itemDetails?.subTitle
if (title == subTitle) subTitle = null
description = itemDetails?.description
const categoryDetails = parseCategoryText(itemDetails?.category)
//duration = categoryDetails?.duration
country = categoryDetails?.country
productionDate = categoryDetails?.productionDate
season = categoryDetails?.season
episode = categoryDetails?.episode
}
const categoryDetails = parseCategoryText(itemDetails?.category)
//duration = categoryDetails?.duration
country = categoryDetails?.country
productionDate = categoryDetails?.productionDate
season = categoryDetails?.season
episode = categoryDetails?.episode
}
// See https://www.npmjs.com/package/epg-parser for parameters
programs.push({
title,
@@ -110,115 +110,120 @@ module.exports = {
// Look inside each .tvlogo container
$('.tvlogo').each((i, el) => {
// Find all descendants that have an alt attribute
$(el).find('[alt]').each((j, subEl) => {
const alt = $(subEl).attr('alt')
const href = $(subEl).attr('href')
if (href && alt && alt.trim() !== '') {
const name = alt.trim()
const site_id = href.replace(/^\/tv\/programme-/, '')
channels.push({
lang: 'fr',
name,
site_id
})
}
})
$(el)
.find('[alt]')
.each((j, subEl) => {
const alt = $(subEl).attr('alt')
const href = $(subEl).attr('href')
if (href && alt && alt.trim() !== '') {
const name = alt.trim()
const site_id = href.replace(/^\/tv\/programme-/, '')
channels.push({
lang: 'fr',
name,
site_id
})
}
})
})
return channels
}
}
function parseTimeRange(timeRange, baseDate) {
// Split times
const [startStr, endStr] = timeRange.split(' - ').map(s => s.trim())
// Split times
const [startStr, endStr] = timeRange.split(' - ').map(s => s.trim())
// Parse with base date
const start = dayjs(`${baseDate} ${startStr}`, 'YYYY-MM-DD HH:mm')
let end = dayjs(`${baseDate} ${endStr}`, 'YYYY-MM-DD HH:mm')
// Parse with base date
const start = dayjs(`${baseDate} ${startStr}`, 'YYYY-MM-DD HH:mm')
let end = dayjs(`${baseDate} ${endStr}`, 'YYYY-MM-DD HH:mm')
// Handle possible day wrap (e.g., 23:30 - 00:15)
if (end.isBefore(start)) {
end = end.add(1, 'day')
}
// Handle possible day wrap (e.g., 23:30 - 00:15)
if (end.isBefore(start)) {
end = end.add(1, 'day')
}
// Calculate duration in minutes
const diffMinutes = end.diff(start, 'minute')
// Calculate duration in minutes
const diffMinutes = end.diff(start, 'minute')
return {
start: start.format(),
stop: end.format(),
duration: diffMinutes
}
return {
start: start.format(),
stop: end.format(),
duration: diffMinutes
}
}
function parseItemDetails(itemDetails) {
const $ = cheerio.load(itemDetails)
const $ = cheerio.load(itemDetails)
const program = $('.program-wrapper').first()
const program = $('.program-wrapper').first()
const programHour = program.find('.program-hour').text().trim()
const programTitle = program.find('.program-title').text().trim()
const programElementBold = program.find('.program-element-bold').text().trim()
const programArea1 = program.find('.program-element.program-area-1').text().trim()
const programHour = program.find('.program-hour').text().trim()
const programTitle = program.find('.program-title').text().trim()
const programElementBold = program.find('.program-element-bold').text().trim()
const programArea1 = program.find('.program-element.program-area-1').text().trim()
let description = ''
const programElements = $('.program-element').filter((i, el) => {
const classAttr = $(el).attr('class')
// Return true only if it is exactly "program-element" (no extra classes)
return classAttr.trim() === 'program-element'
})
let description = ''
const programElements = $('.program-element').filter((i, el) => {
const classAttr = $(el).attr('class')
// Return true only if it is exactly "program-element" (no extra classes)
return classAttr.trim() === 'program-element'
})
programElements.each((i, el) => {
description += $(el).text().trim()
})
programElements.each((i, el) => {
description += $(el).text().trim()
})
const area2Node = $('.program-area-2').first()
const area2 = $(area2Node)
const data = {}
let currentLabel = null
let texts = []
const area2Node = $('.program-area-2').first()
const area2 = $(area2Node)
const data = {}
let currentLabel = null
let texts = []
area2.contents().each((i, node) => {
if (node.type === 'tag' && node.name === 'strong') {
// If we had collected some text for the previous label, save it
if (currentLabel && texts.length) {
data[currentLabel] = texts.join('').trim().replace(/,\s*$/, '') // Remove trailing comma
}
// New label - get text without colon
currentLabel = $(node).text().replace(/:$/, '').trim()
texts = []
} else if (currentLabel) {
// Append the text content (text node or others)
if (node.type === 'text') {
texts.push(node.data)
} else if (node.type === 'tag' && node.name !== 'strong' && node.name !== 'br') {
texts.push($(node).text())
}
area2.contents().each((i, node) => {
if (node.type === 'tag' && node.name === 'strong') {
// If we had collected some text for the previous label, save it
if (currentLabel && texts.length) {
data[currentLabel] = texts.join('').trim().replace(/,\s*$/, '') // Remove trailing comma
}
// New label - get text without colon
currentLabel = $(node).text().replace(/:$/, '').trim()
texts = []
} else if (currentLabel) {
// Append the text content (text node or others)
if (node.type === 'text') {
texts.push(node.data)
} else if (node.type === 'tag' && node.name !== 'strong' && node.name !== 'br') {
texts.push($(node).text())
}
}
})
})
// Save last label text
if (currentLabel && texts.length) {
data[currentLabel] = texts.join('').trim().replace(/,\s*$/, '')
}
// Save last label text
if (currentLabel && texts.length) {
data[currentLabel] = texts.join('').trim().replace(/,\s*$/, '')
}
const imgSrc = program.find('div[style*="float:left"]')?.find('img')?.attr('src') || null
const imgSrc = program.find('div[style*="float:left"]')?.find('img')?.attr('src') || null
return {
programHour,
title: programTitle,
subTitle: programElementBold,
category: programArea1,
description: description,
directorActors: data,
image: imgSrc
}
return {
programHour,
title: programTitle,
subTitle: programElementBold,
category: programArea1,
description: description,
directorActors: data,
image: imgSrc
}
}
function parseCategoryText(text) {
if (!text) return null
const parts = text.split(',').map(s => s.trim()).filter(Boolean)
const parts = text
.split(',')
.map(s => s.trim())
.filter(Boolean)
const len = parts.length
const category = parts[0] || null
@@ -252,18 +257,18 @@ function parseCategoryText(text) {
//duration = [{ units: 'minutes', value: durationMinute }],
durationIndex = i
} else if (parts[i].toLowerCase().includes('épisode')) {
const match = text.match(/épisode\s+(\d+)(?:\/(\d+))?/i)
if (match) {
episode = parseInt(match[1], 10)
}
const match = text.match(/épisode\s+(\d+)(?:\/(\d+))?/i)
if (match) {
episode = parseInt(match[1], 10)
}
} else if (parts[i].toLowerCase().includes('saison')) {
season = parts[i].replace('saison', '').trim()
season = parts[i].replace('saison', '').trim()
}
}
// Country: second to last
const countryIndex = len - 2
let country = (durationIndex === countryIndex) ? null : parts[countryIndex]
let country = durationIndex === countryIndex ? null : parts[countryIndex]
return {
category,
@@ -325,9 +330,9 @@ function parseItems(content) {
const rows = $('.channel-row').toArray()
return {
rows,
logoSrc,
title,
formattedDate
rows,
logoSrc,
title,
formattedDate
}
}

View File

@@ -32,7 +32,8 @@ it('can parse response', async () => {
expect(results.length).toBe(29)
expect(results[0]).toMatchObject({
category: 'Série',
description: 'Grande effervescence pour toute l\'équipe du Camping Paradis, qui prépare les Olympiades. Côté arrivants, Hélène et sa fille Eva viennent passer quelques jours dans le but d\'optimiser les révisions d\'E...',
description:
"Grande effervescence pour toute l'équipe du Camping Paradis, qui prépare les Olympiades. Côté arrivants, Hélène et sa fille Eva viennent passer quelques jours dans le but d'optimiser les révisions d'E...",
start: '2025-06-30T22:55:00.000Z',
stop: '2025-06-30T23:45:00.000Z',
title: 'Camping Paradis'
@@ -45,11 +46,12 @@ it('can parse response', async () => {
title: 'Programmes de la nuit'
})
expect(results[15]).toMatchObject({
category: 'Téléfilm',
description: 'La vie quasi parfaite de Riley bascule brutalement lorsqu\'un accident de voiture lui coûte la vie, laissant derrière elle sa famille. Alors que l\'enquête débute, l\'affaire prend une tournure étrange l...',
category: 'Téléfilm',
description:
"La vie quasi parfaite de Riley bascule brutalement lorsqu'un accident de voiture lui coûte la vie, laissant derrière elle sa famille. Alors que l'enquête débute, l'affaire prend une tournure étrange l...",
start: '2025-07-01T12:25:00.000Z',
stop: '2025-07-01T14:00:00.000Z',
title: 'Trahie par l\'amour'
title: "Trahie par l'amour"
})
})
@@ -57,16 +59,16 @@ it('can parse response for current day', async () => {
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'))
let results = await parser({ content, date: dayjs.utc('2025-07-01', 'YYYY-MM-DD').startOf('d') })
results = results.map(p => {
p.start = p.start.toJSON()
p.stop = p.stop.toJSON()
return p
}
)
p.start = p.start.toJSON()
p.stop = p.stop.toJSON()
return p
})
expect(results.length).toBe(29)
expect(results[0]).toMatchObject({
category: 'Série',
description: 'Grande effervescence pour toute l\'équipe du Camping Paradis, qui prépare les Olympiades. Côté arrivants, Hélène et sa fille Eva viennent passer quelques jours dans le but d\'optimiser les révisions d\'E...',
description:
"Grande effervescence pour toute l'équipe du Camping Paradis, qui prépare les Olympiades. Côté arrivants, Hélène et sa fille Eva viennent passer quelques jours dans le but d'optimiser les révisions d'E...",
start: '2025-06-30T22:55:00.000Z',
stop: '2025-06-30T23:45:00.000Z',
title: 'Camping Paradis'

View File

@@ -5,19 +5,22 @@ module.exports = {
days: 1,
url({ date }) {
return `https://tm.tapi.videoready.tv/content-detail/pub/api/v2/channels/schedule?date=${date.format('DD-MM-YYYY')}`
return `https://tm.tapi.videoready.tv/content-detail/pub/api/v2/channels/schedule?date=${date.format(
'DD-MM-YYYY'
)}`
},
request: {
method: 'POST',
headers: {
'Accept': '*/*',
'Origin': 'https://watch.tataplay.com',
'Referer': 'https://watch.tataplay.com/',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
Accept: '*/*',
Origin: 'https://watch.tataplay.com',
Referer: 'https://watch.tataplay.com/',
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'content-type': 'application/json',
'locale': 'ENG',
'platform': 'web'
locale: 'ENG',
platform: 'web'
},
data({ channel }) {
return { id: channel.site_id }
@@ -46,13 +49,14 @@ module.exports = {
async channels() {
const headers = {
'Accept': '*/*',
'Origin': 'https://watch.tataplay.com',
'Referer': 'https://watch.tataplay.com/',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
Accept: '*/*',
Origin: 'https://watch.tataplay.com',
Referer: 'https://watch.tataplay.com/',
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'content-type': 'application/json',
'locale': 'ENG',
'platform': 'web'
locale: 'ENG',
platform: 'web'
}
const baseUrl = 'https://tm.tapi.videoready.tv/portal-search/pub/api/v1/channels/schedule'

View File

@@ -11,7 +11,9 @@ const date = dayjs.utc('2025-06-09', 'YYYY-MM-DD').startOf('d')
const channel = { site_id: '1001' }
it('can generate valid url', () => {
expect(url({ channel, date })).toBe('https://tm.tapi.videoready.tv/content-detail/pub/api/v2/channels/schedule?date=09-06-2025')
expect(url({ channel, date })).toBe(
'https://tm.tapi.videoready.tv/content-detail/pub/api/v2/channels/schedule?date=09-06-2025'
)
})
it('can parse response', () => {