updating dependencies & fixed tv.dir.bg

This commit is contained in:
theofficialomega committed 2025-07-27 16:14:59 +02:00
1 parent 2a658185d3
commit d6884090df
8 files changed
+2268 -644

No files matched your search

+2084 -537
View File
File diff suppressed because it is too large. Load diff
+11 -11
View File
@@ -38,7 +38,7 @@
"dependencies": {
"@alex_neo/jest-expect-message": "^1.0.5",
"@eslint/eslintrc": "^3.3.1",
"@eslint/js": "^9.31.0",
"@eslint/js": "^9.32.0",
"@freearhey/chronos": "^0.0.1",
"@freearhey/core": "^0.10.2",
"@freearhey/search-js": "^0.1.2",
@@ -46,32 +46,32 @@
"@octokit/core": "^7.0.3",
"@octokit/plugin-paginate-rest": "^13.1.1",
"@octokit/plugin-rest-endpoint-methods": "^16.0.0",
"@swc/core": "^1.13.0",
"@swc/core": "^1.13.2",
"@swc/jest": "^0.2.39",
"@types/cli-progress": "^3.11.6",
"@types/fs-extra": "^11.0.4",
"@types/inquirer": "^9.0.8",
"@types/jest": "^30.0.0",
"@types/langs": "^2.0.5",
"@types/node": "^24.0.15",
"@types/node": "^24.1.0",
"@types/node-cleanup": "^2.1.5",
"@types/numeral": "^2.0.5",
"@typescript-eslint/eslint-plugin": "^8.37.0",
"@typescript-eslint/parser": "^8.37.0",
"axios": "^1.10.0",
"@typescript-eslint/eslint-plugin": "^8.38.0",
"@typescript-eslint/parser": "^8.38.0",
"axios": "^1.11.0",
"axios-cookiejar-support": "^6.0.4",
"chalk": "^5.4.1",
"cheerio": "^1.1.0",
"cheerio": "^1.1.2",
"cli-progress": "^3.12.0",
"commander": "^14.0.0",
"consola": "^3.4.2",
"cross-env": "^7.0.3",
"cross-env": "^10.0.0",
"csv-parser": "^3.2.0",
"cwait": "^1.1.2",
"dayjs": "^1.11.13",
"epg-grabber": "^0.41.0",
"epg-parser": "^0.3.1",
"eslint": "^9.31.0",
"eslint": "^9.32.0",
"eslint-config-prettier": "^10.1.8",
"form-data": "^4.0.4",
"fs-extra": "^11.3.0",
@@ -79,8 +79,8 @@
"globals": "^16.3.0",
"husky": "^9.1.7",
"iconv-lite": "^0.6.3",
"inquirer": "^12.7.0",
"jest": "^30.0.4",
"inquirer": "^12.8.2",
"jest": "^30.0.5",
"jest-offline": "^1.0.1",
"langs": "^2.0.0",
"libxml2-wasm": "^0.5.0",
-1
View File
@@ -1 +0,0 @@
<div class=\"panels\">\n <div class=\"panel\">\n <div class=\"day-broadcast-list\">\n <p class=\"broadcast-item-name\">\n \u0412\u0447\u0435\u0440\u0430\n <\/p>\n <div class=\"broadcasts-lists\">\n <div class=\"broadcast-item \">\n <div class=\"broadcast-image\">\n <img loading=\"lazy\" src=\"data:image\/jpg;base64,\/9j\/4AAQSkZJRgABAQEBLAEsAAD\/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj\/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj\/wAARCAFoAoADASIAAhEBAxEB\/8QAHAAAAgIDAQEAAAAAAAAAAAAAAgMBBAAFBgcI\/8QARRAAAgEDAgQEAggEBAUFAAEFAQIDAAQREiEFMUFRBhMiYXGBBxQyQlKRofAjscHRFTNi4QgkcoLxFkNTkqLCJXODstL\/xAAZAQEBAQEBAQAAAAAAAAAAAAAAAQIDBAX\/xAAkEQEBAQACAwACAgMBAQAAAAAAARECEgMhMUFRE2EEFHEiMv\/aAAwDAQACEQMRAD8A+dhTU50sCmRiurznJvRaaGMUwUA6ajy80wUaigQYj2odGKuAZFCY6CqFqdFM0dqnT7UUvRWBKaFoguaIr+XWaKs6Kjy6CtpogKcY6ExkUaCKkCs0kUQFEwOis0e1NWi0g1WcV9FTop+is00VX01gUdafoqNBoFhe1SFogMUQAIoFFM1gSnhaIJQV9FZoqxprNNBV8us0Yq1ozQFDmiwkLWYqwIj2ovKHagq49qjTVvyhWz8McDk45x204dCcNM+7YJwo3J29hQaIL2otFe7+Ifox8O2lnChM\/D55kLwSNKX83YkL1B2BzgA14zxCzW0vri3SZJ1ikZBKnJwDjIq2YksqgEqNFWVTvU+XkVFVNFRpq35VT5NFVVSj0VZEVF5dGdVNFSI6teVUiPFC1U8upEdWtFT5dGVTRUeXVvRUaKCtoqNFWtFZooaq+XWeXVnRWaKCt5dZ5dWdFDp9qBGis0VY0ZrPLoK\/l1nl1Z0VmigreXWeXVjy6IR1FVfLqdFWhHWaMU0xV8us0Va0ZrPLppisI6zy6teXioKUFbRU6BT9PasEZNTVxVK0JjzVsx1nlVNWKXl1giJq75YqQmKKpeTQmPFXXXApLITRFZtqU2elXBbk86IQAUXFARFudMWECrRUCsAzyFBQEdEqVf8Aq9SIN+VDFWNKIpV6O3z0oZICDyoYpaaJRTjER0qRGRzppgBWU4JtQlDQKwKLRmi096JVxWgryxWBMcqeBRKtZMIG3OiAFO0A0JQiroXpqClMFEBmqYR5dZ5dWVTIqRH3oir5dRoI5VdEe1QY8iporVIAomQgmoAxV0xAXNSY9qIVI2ppiuY9zQhSKtlc0OimqUozTVU1KpT403pqYToqRHVrRWaKaitoqPLFW\/LNT5e3Kmioq1hSrHlkdKMR5pqqgQ9K9D8LeDrq04dBx1eIKk8qqYoYgcgOxX1NnY7csH864XyypOK3\/C\/GHGuHW9vbRyxzWkCsiQTxK6YP6n232q8eUl9pZvx2vBmaPgHiHhpsUuZRAYo2eV2MS88orHuMgjHUGvI3iKSOp5qSPyNek+HOMTyxszRxzC3KSLBICY5Ai6WUhegUq23auKvLN2WW8jjVYGnZMrnBbOSfYdq3znrWeLWaO4qVQGrKJjmKYsQzyrlraqIs1PldxVwR+1SU2qaikI\/ap8s9qtYrMe1NFby6zyqsge1SFzV0xWEVR5dW9FZopqYp+VWeXVvRWaKaYqeXUeXVvT7VGimir5dZ5dWdHtUeXU1cVSlCU7CrhjqBHimioqHqKLy6teXWaB2ppisI6zy6sFMcqgjFNMJ8uo0gUTnFCFZqLIFjjlQBSaspD3pyw1NXFRU25UQQ1b8qs8uiYqeXWeV3q55dZoouKgiohHVgJmjERoYqGOllRV1ojioWADmKmmKQjJpqQbVbEdMSPamrI17QDtQGICr8q1XdCaaYptillGY8quiH2oxDTWlAQ96kREdKO7vbW0z5kgLfhXc1pLvjU0uRboI1\/EdzRltbO9trjZXAb8LbGtnHCGG1cYsWqtjw+8u7dgI31D8D7g\/Orhrq4bUHpQT2uDyouE8VilYJcxtE3fmp+dbSRUkyY2Vh3BrLcksc+bY77VAts9K3f1bsKxbbuKadWnW125UqS3IroPIwpwKqSQ89qunVozCcnas8ojpW2Nv7ULW23KmpjUsmDWKKvvb+1L8nFXUxXUbVhFOMRxyoStFJMfY0OkinY71mM0ClYinI2RvQ6KkJ2omGACp0dqBNQNOUHFEwopnnQmHtVoH8QoggP2cU1MUfKx0yKIJVzQDsaExZ5U1VbSKjRtVnyiOxovKyKuiooFPjUY3oDHpNMjwaGGogo9AoQvamIhxvUTAlAanRR6agChhboKFRT1XNQYyD7UXAhAe1C8YVGbB2BO1PVM8qfaorTxrKwVGYBiTjA6\/PGas90sdx4U8OGbhh4r50sbIVNquj0EjBcZbYrjVnGee\/Ko4tccEgZ7VuGyDg8shYPFK4Bk2yy52zueY69qrN4i4Z5\/CYPqckttwyMxQTXEpLKWG5K8tsk471tfEnEZ0js341ZzIsykecojcuinMZ7a8E5PwFei304yOI8R8JThXGJ7SKTzYQFeJzzZGUMucdcGtaIwK23H+If4pxOS4SAQRaVSOIHOlVGACep6n41rvavNb79O84\/sAQVBjBpoG21QaLhXljtQlOwp+\/QVOk4odVdYyelEI6fjAoSN6adS9FDp3pmCakKRRMK01mmmkVGM0MJwKzTTStZihhWnFRpp2msC7UMJ0+1YVFOxUaaGE6ajSaseXWaKhivooTHmreipCYoYpiAdaMRDtVrAFDtRcKEeBRKlHkVBftQxGkUBwKIktWCMmhgNulQFJqzHBmm+UB0qL1VY46fHCDzpgGKdEtFkVzCO1LMftV8pnpWeRq5UXGvCAUyOIt0q2LbHMVJntrbaSRc\/hG5NQxRkgOTtVeREjUtIwVR1JqnxXjkpLLaQ6R+N\/7VzN1LPcsTPI8h7HkPlVkYtn4bu843awZWAGZx+Hl+daG94rd3WQX8tPwp\/ekMgXnSHYDNakjOg2G+MnuaAkmpJJ5UJXvvVMXLaUg771u7BYbghWOh+ma5+IsDtW54Sks0qqqj5ioOv4PwuVZlDrqU8jjYihueGlLh5ImeAg8wcCt7wMC3tUSSYMzHAUcqpcdlS6ysMgLpsUP9Ky31mNZ\/ic9ptIi3I\/Euxq1acWtLptPmaHP3ZPSa5yRpI5CGyoB3HKrMMCXS7YJ7MMUScq7COMMhI7VVkh3xiqfDLa5gtpDBJIgA+y2601L+WLe+gwPxxn+ho3oxB2FEYMLuKbbXtpc7W8yM34Ts35GrZjJQ5FFjSyQjfakGD2raSR7mgEXtRMatrfbYVXeAjpW+MIIqu8I6UOrSGL2ofLrbNBvypbW+\/KiY1misC1da3I5ClmMiqmK4UijU4pmis8sdaGDQBqnyd\/SaAIRypsZYc6GI0MKlR3FPU5FYcVDCwgPKoMe1PC5qdHvVMUJUyMDnVfSUNbVo+4zVeWEtnANDCoXJG5qymCKp6GQnNOjkPU0MWQMUJAFCsgqS6kUEZGdjWMcc80Gpd8Vb4VZScTujbW2WuChMaDcyN0Ue5qybcS+vY+E2rX90sSnSDzY8ht+\/gK2lnweSS4EBPluWC5kTONixY9sKCcdyB3rp+G+E7m2jtohC2qWXyAz7KwGWc5J5YU\/LHenGRVsbmeTINvw+ScjG6NM+Ez3OP0NejjxkcLytMseFwy2VtPfWEWuTRpLHSTrMhAJ9lUZJ7irF6tqWjt04Tw\/y2iha3D5Ozp6AcEfeBU9iynlW34tIknCorW3tY42jjt4HYJ0aMjr3BHzrkTckQm1ktmDhfqrSKTyQllPscgbjtW0afj9vbG0t7yO0jszKf8ALjLYA6Egk4zhhz5qR0rSFMMVYb10l3b+dJcNdISsqrJI2rKprbBcnn6XKnH\/AFd652KOYC6S4TRLbtpK9RglWHyYEVy58PzHTx8\/eVGjbpQrGAc5zUh98YowO1cHowOkVAA6VLCh0mhjMUOPamhakpmhhOw6Vm1ZInvSHbBwNzRPhpK1lIUEnOTTUjHc1ETsBvUqM8t6kQZ509IwowKqyEFKjRVhhigAoYWF2qdNMPxxSXkC8t6GJ00LEDrSnkZvhSzk1EOaVRS2mPSgCk0YizQLLMe9SMnvThFRrEe1DCVjJpiQ1YjhPWrUUIouKaxY5CmJFV4we1QIt6LhUUWTRvAe1W4I9+VNfSgJcgAdTU1WsEGDyqxBBvQTcQtRkRN5rjom\/wCtU1vL2aTSirAvLuaJsXrlordS0rqijqTWnm8QQxki1jaZu42Fa7iLine truncated
+4
View File
@@ -0,0 +1,4 @@
{
"status": true,
"html": "<div class=\"panels\">\n <div class=\"panel\">\n <div class=\"day-broadcast-list\">\n <p class=\"broadcast-item-name\">\n 29.06\n </p>\n <div class=\"broadcasts-lists\">\n <div class=\"broadcast-item \">\n <div class=\"broadcast-image\">\n <img loading=\"lazy\" src=\"data:image/jpg;base64,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 truncated
}
+4
View File
@@ -0,0 +1,4 @@
{
"status": true,
"html": "<div class=\"panels\">\n <div class=\"panel\">\n <div class=\"day-broadcast-list\">\n <p class=\"broadcast-item-name\">\n 29.07\n </p>\n <div class=\"broadcasts-lists\">\n </div>\n </div>\n </div>\n <div class=\"panel\">\n <div class=\"day-broadcast-list\">\n <p class=\"broadcast-item-name\">\n 30.07\n </p>\n <div class=\"broadcasts-lists\">\n </div>\n </div>\n </div>\n <div class=\"panel\">\n <div class=\"day-broadcast-list\">\n <p class=\"broadcast-item-name\">\n 31.07\n </p>\n <div class=\"broadcasts-lists\">\n </div>\n </div>\n </div>\n </div>"
}
-1
View File
@@ -1 +0,0 @@
<div class=\"panels\">\n <div class=\"panel\">\n <div class=\"day-broadcast-list\">\n <p class=\"broadcast-item-name\">\n 19.02\n </p>\n <div class=\"broadcasts-lists\">\n </div>\n </div>\n </div>\n <div class=\"panel\">\n <div class=\"day-broadcast-list\">\n <p class=\"broadcast-item-name\">\n 20.02\n </p>\n <div class=\"broadcasts-lists\">\n </div>\n </div>\n </div>\n <div class=\"panel\">\n <div class=\"day-broadcast-list\">\n <p class=\"broadcast-item-name\">\n 21.02\n </p>\n <div class=\"broadcasts-lists\">\n </div>\n </div>\n </div>\n </div>
+147 -72
View File
@@ -1,52 +1,134 @@
const axios = require('axios')
const cheerio = require('cheerio')
const url = require('url')
const { DateTime } = require('luxon')
const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
const timezone = require('dayjs/plugin/timezone')
const customParseFormat = require('dayjs/plugin/customParseFormat')
let cachedToken = null
let tokenExpiry = null
dayjs.extend(utc)
dayjs.extend(timezone)
dayjs.extend(customParseFormat)
let sessionCache = null
async function getSession(forceRefresh = false) {
if (sessionCache && !forceRefresh) {
return sessionCache
}
try {
const initResponse = await axios.get('https://tv.dir.bg/init')
if (!initResponse.data) {
throw new Error('No response data from init endpoint')
}
// Extract cookies from response headers
const setCookieHeader = initResponse.headers['set-cookie']
let xsrfToken = null
let dirSessionCookie = null
if (setCookieHeader) {
setCookieHeader.forEach(cookie => {
// Extract XSRF token from cookie
const xsrfMatch = cookie.match(/XSRF-TOKEN=([^;]+)/)
if (xsrfMatch) {
xsrfToken = decodeURIComponent(xsrfMatch[1])
}
// Extract dir_session cookie
const sessionMatch = cookie.match(/dir_session=([^;]+)/)
if (sessionMatch) {
dirSessionCookie = sessionMatch[1]
}
})
}
const csrfToken = initResponse.data.csrfToken
if (!csrfToken) {
throw new Error('No CSRF/XSRF token found in response')
}
// Build cookie string
let cookieString = ''
if (xsrfToken) {
cookieString += `XSRF-TOKEN=${encodeURIComponent(xsrfToken)}`
}
if (dirSessionCookie) {
if (cookieString) cookieString += '; '
cookieString += `dir_session=${dirSessionCookie}`
}
sessionCache = {
csrfToken,
cookieString,
timestamp: Date.now()
}
return sessionCache
} catch (error) {
console.error('Error getting session:', error.message)
throw error
}
}
module.exports = {
site: 'tv.dir.bg',
days: 2,
async url({ channel, date }) {
const token = await getToken()
if (!token) {
throw new Error('Unable to retrieve CSRF token')
}
const form = new url.URLSearchParams({
_token: token,
channel: channel.site_id,
day: date.format('YYYY-MM-DD')
})
return axios.post('https://tv.dir.bg/load/programs', form.toString(), {
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
'X-Requested-With': 'XMLHttpRequest'
url: 'https://tv.dir.bg/load/programs',
request: {
maxContentLength: 125000000, // 10 MB
method: 'POST',
async headers() {
try {
const session = await getSession()
return {
'Cookie': session.cookieString,
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
'X-Requested-With': 'XMLHttpRequest'
}
} catch (error) {
console.error('Error getting headers:', error.message)
throw error
}
})
},
async data({ channel, date }) {
try {
const session = await getSession()
const params = new URLSearchParams()
params.append('_token', session.csrfToken)
params.append('channel', channel.site_id)
params.append('day', date.format('YYYY-MM-DD'))
return params
} catch (error) {
console.error('Error preparing request data:', error.message)
throw error
}
},
},
parser({ content, date }) {
const programs = []
const items = parseItems(content)
items.forEach(item => {
const $item = cheerio.load(item)
const prev = programs[programs.length - 1]
const $item = cheerio.load(item)
let start = parseStart($item, date)
if (!start) return
if (prev) {
if (start < prev.start) {
start = start.plus({ days: 1 })
date = date.plus({ days: 1 })
if (start.isBefore(prev.start)) {
start = start.add(1, 'd')
}
prev.stop = start
}
const stop = start.plus({ minutes: 30 })
const stop = start.add(30, 'm')
programs.push({
title: parseTitle($item),
start,
@@ -56,6 +138,7 @@ module.exports = {
return programs
},
async channels() {
try {
const response = await axios.get('https://tv.dir.bg/channels')
@@ -63,7 +146,7 @@ module.exports = {
const channels = []
$('.channel_cont').each((index, element) => {
$('.channel_cont').each((_index, element) => {
const $element = $(element)
const $link = $element.find('a.channel_link')
@@ -79,63 +162,55 @@ module.exports = {
channels.push({
lang: 'bg',
site_id: site_id,
name: name,
logo: logo
name: name.trim(),
logo: logo ? (logo.startsWith('http') ? logo : `https://tv.dir.bg${logo}`) : null
})
}
})
return channels
return channels
} catch (error) {
console.error('Error fetching channels:', error)
return []
}
}
}
async function getToken() {
if (cachedToken && tokenExpiry && DateTime.now() < tokenExpiry) {
return cachedToken
}
try {
const response = await axios.get('https://tv.dir.bg/init', { headers: {'X-Requested-With': 'XMLHttpRequest'} })
// Check different possible locations for the token
let token = null
if (response.data && response.data.csrfToken) {
token = response.data.csrfToken
} catch (error) {
console.error('Error fetching channels:', error.message)
return []
}
if (token) {
cachedToken = token
tokenExpiry = DateTime.now().plus({ hours: 1 })
return token
} else {
console.error('CSRF token not found in response structure:', Object.keys(response.data || {}))
return null
}
} catch (error) {
console.error('Error fetching token:', error.message)
return null
},
clearSession() {
sessionCache = null
}
}
function parseStart($item, date) {
const timeText = $item('.broadcast-time').text().trim()
if (!timeText) return null
const time = $item('.broadcast-time').text().trim()
const dateString = `${date.format('YYYY-MM-DD')} ${time}`
const [hours, minutes] = timeText.split(':').map(Number)
const dateTime = date.isValid ? date : DateTime.fromISO(date)
return dateTime.set({ hour: hours, minute: minutes, second: 0, millisecond: 0 })
return dayjs.tz(dateString, 'YYYY-MM-DD HH:mm', 'Europe/Sofia')
}
function parseTitle($item) {
return $item('.broadcast-title').text().trim()
return $item('.broadcast-title').text()
.replace(/\s+/g, ' ')
.trim()
}
function parseItems(content) {
const $ = cheerio.load(content)
return $('.broadcast-item').toArray()
}
try {
const json = JSON.parse(content)
if (!json || json.status !== true) {
return []
}
const $ = cheerio.load(json.html)
const items = $('.broadcast-item').toArray()
return items
} catch (error) {
console.error('❌ Error parsing items:', error.message)
console.error('Error stack:', error.stack)
return []
}
}
+18 -22
View File
@@ -9,46 +9,42 @@ dayjs.extend(utc)
const date = dayjs.utc('2025-06-30', 'YYYY-MM-DD').startOf('d')
const channel = {
site_id: '12',
site_id: '61',
xmltv_id: 'BTV.bg'
}
it('can generate valid url', () => {
expect(url({ channel, date })).toBe('https://tv.dir.bg/programa/12')
expect(url).toBe('https://tv.dir.bg/load/programs')
})
it('can parse response', () => {
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'))
const result = parser({ content, date }).map(p => {
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'))
const results = parser({ content, date }).map(p => {
p.start = p.start.toJSON()
p.stop = p.stop.toJSON()
return p
})
expect(result).toMatchObject([
{
start: '2025-06-30T08:00:00.000Z',
stop: '2025-06-30T10:00:00.000Z',
title: 'Купа на Франция: Еспали - Пари Сен Жермен'
},
{
start: '2025-06-30T10:00:00.000Z',
stop: '2025-06-30T12:00:00.000Z',
title: 'Ла Лига: Леганес - Реал Сосиедад'
},
{
start: '2025-06-30T12:00:00.000Z',
stop: '2025-06-30T13:00:00.000Z',
title: 'Пред Стадиона&quot; - спортно шоу'
}
])
expect(results.length).toBe(63)
expect(results[0]).toMatchObject({
start: '2025-06-30T03:00:00.000Z',
stop: '2025-06-30T03:30:00.000Z',
title: 'Светът на здравето'
})
expect(results[62]).toMatchObject({
start: '2025-07-01T02:00:00.000Z',
stop: '2025-07-01T02:30:00.000Z',
title: 'Убийства в Рая , сезон 1 , епизод 7'
})
})
it('can handle empty guide', () => {
const result = parser({
date,
channel,
content: fs.readFileSync(path.resolve(__dirname, '__data__/no_data.html'))
content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json'))
})
expect(result).toMatchObject([])
})