From eec8f4ea47e2cba842edb39900690372bc7e56e5 Mon Sep 17 00:00:00 2001 From: nirvana-7777 <48356537+nirvana-7777@users.noreply.github.com> Date: Thu, 20 Feb 2025 12:42:19 +0100 Subject: [PATCH 1/7] Update mojmaxtv.hrvatskitelekom.hr.config.js - More details, e.g. images - Add rating - Fix categories --- .../mojmaxtv.hrvatskitelekom.hr.config.js | 151 ++++++++++++++---- 1 file changed, 117 insertions(+), 34 deletions(-) diff --git a/sites/mojmaxtv.hrvatskitelekom.hr/mojmaxtv.hrvatskitelekom.hr.config.js b/sites/mojmaxtv.hrvatskitelekom.hr/mojmaxtv.hrvatskitelekom.hr.config.js index 0d44fcd1..647f58f0 100644 --- a/sites/mojmaxtv.hrvatskitelekom.hr/mojmaxtv.hrvatskitelekom.hr.config.js +++ b/sites/mojmaxtv.hrvatskitelekom.hr/mojmaxtv.hrvatskitelekom.hr.config.js @@ -2,31 +2,48 @@ const doFetch = require('@ntlab/sfetch') const axios = require('axios') const dayjs = require('dayjs') const _ = require('lodash') +const crypto = require('crypto') + +// API Configuration Constants +const NATCO_CODE = 'hr' +const APP_KEY = 'GWaBW4RTloLwpUgYVzOiW5zUxFLmoMj5' +const APP_VERSION = '02.0.1080' +const NATCO_KEY = 'l2lyvGVbUm2EKJE96ImQgcc8PKMZWtbE' +const SITE_URL = 'mojmaxtv.hrvatskitelekom.hr' + +// Dynamic API Endpoint based on NATCO_CODE +const API_ENDPOINT = `https://tv-${NATCO_CODE}-prod.yo-digital.com/${NATCO_CODE}-bifrost` + +// Session/Device IDs +const DEVICE_ID = crypto.randomUUID() +const SESSION_ID = crypto.randomUUID() const cached = {} +const getHeaders = () => ({ + 'app_key': APP_KEY, + 'app_version': APP_VERSION, + 'device-id': DEVICE_ID, + 'tenant': 'tv', + 'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36', + 'origin': `https://${SITE_URL}`, + 'x-request-session-id': SESSION_ID, + 'x-request-tracking-id': crypto.randomUUID(), + 'x-tv-step': 'EPG_SCHEDULES', + 'x-tv-flow': 'EPG', + 'x-call-type': 'GUEST_USER', + 'x-user-agent': `web|web|Chrome-133|${APP_VERSION}|1` +}) + module.exports = { - site: 'mojmaxtv.hrvatskitelekom.hr', + site: SITE_URL, url({ date }) { - return `https://tv-hr-prod.yo-digital.com/hr-bifrost/epg/channel/schedules?date=${date.format( + return `${API_ENDPOINT}/epg/channel/schedules?date=${date.format( 'YYYY-MM-DD' - )}&hour_offset=0&hour_range=3&channelMap_id&filler=true&app_language=hr&natco_code=hr` + )}&hour_offset=0&hour_range=3&channelMap_id&filler=true&app_language=${NATCO_CODE}&natco_code=${NATCO_CODE}` }, request: { - headers: { - 'app_key': 'GWaBW4RTloLwpUgYVzOiW5zUxFLmoMj5', - 'app_version': '02.0.1080', - 'device-id': 'a78f079d-5527-46d8-af3f-9f0b6b6fb758', - 'tenant': 'tv', - 'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36', - 'origin': 'https://mojmaxtv.hrvatskitelekom.hr', - 'x-request-session-id': 'fc96c9de-7a3b-4b51-8b9d-5d9f9a3c3268', - 'x-request-tracking-id': '05a8f0bc-f977-4754-b8ad-1d4d1bd742fb', - 'x-tv-step': 'EPG_SCHEDULES', - 'x-tv-flow': 'EPG', - 'x-call-type': 'GUEST_USER', - 'x-user-agent': 'web|web|Chrome-133|02.0.1080|1' - }, + headers: getHeaders(), cache: { ttl: 24 * 60 * 60 * 1000 // 1 day } @@ -41,11 +58,10 @@ module.exports = { const queue = [3, 6, 9, 12, 15, 18, 21] .map(offset => { const url = module.exports.url({ date }).replace('hour_offset=0', `hour_offset=${offset}`) - const params = module.exports.request + const params = { ...module.exports.request, headers: getHeaders() } if (cached[url]) { items = items.concat(parseItems(cached[url], channel)) - return null } @@ -56,44 +72,66 @@ module.exports = { await doFetch(queue, (_req, _data) => { if (_data) { cached[_req.url] = _data - items = items.concat(parseItems(_data, channel)) } }) items = _.sortBy(items, i => dayjs(i.start_time).valueOf()) - return items.map(item => ({ - title: item.description, - categories: Array.isArray(item.genres) ? item.genres.map(g => g.name) : [], - season: item.season_number, - episode: item.episode_number ? parseInt(item.episode_number) : null, - date: item['release_year'] ? item['release_year'].toString() : null, - start: item.start_time, - stop: item.end_time - })) + // Fetch program details for each item + const programs = [] + for (let item of items) { + const detail = await loadProgramDetails(item) + programs.push({ + title: item.description, + description: parseDescription(detail), + categories: parseCategories(detail), + date: parseDate(item), + image: detail.poster_image_url, + actors: parseRoles(detail, 'GLUMI'), + directors: parseRoles(detail, 'REŽIJA'), + producers: parseRoles(detail, 'Producent'), + season: parseSeason(item), + episode: parseEpisode(item), + rating: parseRating(item), + start: item.start_time, + stop: item.end_time + }) + } + + return programs }, async channels() { const data = await axios .get( - 'https://tv-hr-prod.yo-digital.com/hr-bifrost/epg/channel?channelMap_id=&includeVirtualChannels=false&natco_key=l2lyvGVbUm2EKJE96ImQgcc8PKMZWtbE&app_language=hr&natco_code=hr', - module.exports.request + `${API_ENDPOINT}/epg/channel?channelMap_id=&includeVirtualChannels=false&natco_key=${NATCO_KEY}&app_language=${NATCO_CODE}&natco_code=${NATCO_CODE}`, + { ...module.exports.request, headers: getHeaders() } ) .then(r => r.data) .catch(console.error) return data.channels.map(channel => ({ - lang: 'hr', + lang: NATCO_CODE, name: channel.title, site_id: channel.station_id })) } } +async function loadProgramDetails(item) { + if (!item.program_id) return {} + const url = `${API_ENDPOINT}/details/series/${item.program_id}?natco_code=${NATCO_CODE}` + const data = await axios + .get(url, { headers: getHeaders() }) + .then(r => r.data) + .catch(console.log) + + return data || {} +} + function parseData(content) { try { const data = JSON.parse(content) - return data || null } catch { return null @@ -102,6 +140,51 @@ function parseData(content) { function parseItems(data, channel) { if (!data.channels || !Array.isArray(data.channels[channel.site_id])) return [] - return data.channels[channel.site_id] } + +function parseDate(item) { + return item && item.release_year ? item.release_year.toString() : null +} + +function parseRating(item) { + return item.ratings + ? { + system: 'MPA', + value: item.ratings + } + : null +} + +function parseSeason(item) { + if (item.season_display_number === 'Epizode') return null // 'Epizode' is 'Episodes' in Croatian + return item.season_number +} + +function parseEpisode(item) { + if (item.episode_number) return parseInt(item.episode_number) + if (item.season_display_number === 'Epizode') return item.season_number + return null +} + +function parseDescription(item) { + if (!item.details) return null + return item.details.description +} + +function parseCategories(item) { + if (!item.details?.metadata) return [] + + const genreMetadata = item.details.metadata.find(meta => + meta.type === "GENRES" + ) + + if (!genreMetadata?.value) return [] + + return genreMetadata.value.split(', ').filter(Boolean) +} + +function parseRoles(item, role_name) { + if (!item.roles) return null + return item.roles.filter(role => role.role_name === role_name).map(role => role.person_name) +} From cd7182ddb346c1ac1f7c2b9864eddb98a9cfd1f6 Mon Sep 17 00:00:00 2001 From: nirvana-7777 <48356537+nirvana-7777@users.noreply.github.com> Date: Thu, 20 Feb 2025 13:07:50 +0100 Subject: [PATCH 2/7] Update mojmaxtv.hrvatskitelekom.hr.config.js Fix double quotes --- .../mojmaxtv.hrvatskitelekom.hr.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sites/mojmaxtv.hrvatskitelekom.hr/mojmaxtv.hrvatskitelekom.hr.config.js b/sites/mojmaxtv.hrvatskitelekom.hr/mojmaxtv.hrvatskitelekom.hr.config.js index 647f58f0..a593402f 100644 --- a/sites/mojmaxtv.hrvatskitelekom.hr/mojmaxtv.hrvatskitelekom.hr.config.js +++ b/sites/mojmaxtv.hrvatskitelekom.hr/mojmaxtv.hrvatskitelekom.hr.config.js @@ -176,7 +176,7 @@ function parseCategories(item) { if (!item.details?.metadata) return [] const genreMetadata = item.details.metadata.find(meta => - meta.type === "GENRES" + meta.type === 'GENRES' ) if (!genreMetadata?.value) return [] From fab5f9ad0806277becc1272319127cf2d19d2021 Mon Sep 17 00:00:00 2001 From: nirvana-7777 <48356537+nirvana-7777@users.noreply.github.com> Date: Fri, 21 Feb 2025 13:32:57 +0100 Subject: [PATCH 3/7] Update mojmaxtv.hrvatskitelekom.hr.config.js --- .../mojmaxtv.hrvatskitelekom.hr.config.js | 63 +++++++++++++------ 1 file changed, 45 insertions(+), 18 deletions(-) diff --git a/sites/mojmaxtv.hrvatskitelekom.hr/mojmaxtv.hrvatskitelekom.hr.config.js b/sites/mojmaxtv.hrvatskitelekom.hr/mojmaxtv.hrvatskitelekom.hr.config.js index a593402f..45f40b81 100644 --- a/sites/mojmaxtv.hrvatskitelekom.hr/mojmaxtv.hrvatskitelekom.hr.config.js +++ b/sites/mojmaxtv.hrvatskitelekom.hr/mojmaxtv.hrvatskitelekom.hr.config.js @@ -6,11 +6,21 @@ const crypto = require('crypto') // API Configuration Constants const NATCO_CODE = 'hr' +const APP_LANGUAGE = 'hr' const APP_KEY = 'GWaBW4RTloLwpUgYVzOiW5zUxFLmoMj5' const APP_VERSION = '02.0.1080' const NATCO_KEY = 'l2lyvGVbUm2EKJE96ImQgcc8PKMZWtbE' const SITE_URL = 'mojmaxtv.hrvatskitelekom.hr' +// Role Types +const ROLE_TYPES = { + ACTOR: 'GLUMI', // Croatian for "ACTS" + DIRECTOR: 'REŽIJA', // Croatian for "DIRECTOR" + PRODUCER: 'PRODUKCIJA', // Croatian for "PRODUCER" + WRITER: 'AUTOR', + SCENARIO: 'SCENARIJ' +} + // Dynamic API Endpoint based on NATCO_CODE const API_ENDPOINT = `https://tv-${NATCO_CODE}-prod.yo-digital.com/${NATCO_CODE}-bifrost` @@ -18,6 +28,9 @@ const API_ENDPOINT = `https://tv-${NATCO_CODE}-prod.yo-digital.com/${NATCO_CODE} const DEVICE_ID = crypto.randomUUID() const SESSION_ID = crypto.randomUUID() +// Set of known role names for quick lookup +const KNOWN_ROLES = new Set(Object.values(ROLE_TYPES)) + const cached = {} const getHeaders = () => ({ @@ -35,12 +48,34 @@ const getHeaders = () => ({ 'x-user-agent': `web|web|Chrome-133|${APP_VERSION}|1` }) +const unknownRoles = new Map() + +function detectUnknownRoles(item) { + if (!item.roles) return + + item.roles.forEach(role => { + if (!KNOWN_ROLES.has(role.role_name)) { + const count = unknownRoles.get(role.role_name) || 0 + unknownRoles.set(role.role_name, count + 1) + + // Log the first occurrence of each unknown role with an example + if (count === 0) { + console.log('New role type detected:', { + role_name: role.role_name, + example_person: role.person_name, + program_id: item.program_id + }) + } + } + }) +} + module.exports = { site: SITE_URL, url({ date }) { return `${API_ENDPOINT}/epg/channel/schedules?date=${date.format( 'YYYY-MM-DD' - )}&hour_offset=0&hour_range=3&channelMap_id&filler=true&app_language=${NATCO_CODE}&natco_code=${NATCO_CODE}` + )}&hour_offset=0&hour_range=3&channelMap_id&filler=true&app_language=${APP_LANGUAGE}&natco_code=${NATCO_CODE}` }, request: { headers: getHeaders(), @@ -82,15 +117,19 @@ module.exports = { const programs = [] for (let item of items) { const detail = await loadProgramDetails(item) + +// detectUnknownRoles(detail) + programs.push({ title: item.description, + sub_title: item.episode_name, description: parseDescription(detail), - categories: parseCategories(detail), + categories: Array.isArray(item.genres) ? item.genres.map(g => g.name) : [], date: parseDate(item), image: detail.poster_image_url, - actors: parseRoles(detail, 'GLUMI'), - directors: parseRoles(detail, 'REŽIJA'), - producers: parseRoles(detail, 'Producent'), + actors: parseRoles(detail, ROLE_TYPES.ACTOR), + directors: parseRoles(detail, ROLE_TYPES.DIRECTOR), + producers: parseRoles(detail, ROLE_TYPES.PRODUCER), season: parseSeason(item), episode: parseEpisode(item), rating: parseRating(item), @@ -104,7 +143,7 @@ module.exports = { async channels() { const data = await axios .get( - `${API_ENDPOINT}/epg/channel?channelMap_id=&includeVirtualChannels=false&natco_key=${NATCO_KEY}&app_language=${NATCO_CODE}&natco_code=${NATCO_CODE}`, + `${API_ENDPOINT}/epg/channel?channelMap_id=&includeVirtualChannels=false&natco_key=${NATCO_KEY}&app_language=${APP_LANGUAGE}&natco_code=${NATCO_CODE}`, { ...module.exports.request, headers: getHeaders() } ) .then(r => r.data) @@ -172,18 +211,6 @@ function parseDescription(item) { return item.details.description } -function parseCategories(item) { - if (!item.details?.metadata) return [] - - const genreMetadata = item.details.metadata.find(meta => - meta.type === 'GENRES' - ) - - if (!genreMetadata?.value) return [] - - return genreMetadata.value.split(', ').filter(Boolean) -} - function parseRoles(item, role_name) { if (!item.roles) return null return item.roles.filter(role => role.role_name === role_name).map(role => role.person_name) From 667c3944c534e60987bb83f57cf10cad5ed6208f Mon Sep 17 00:00:00 2001 From: nirvana-7777 <48356537+nirvana-7777@users.noreply.github.com> Date: Fri, 21 Feb 2025 13:34:16 +0100 Subject: [PATCH 4/7] Update mojmaxtv.hrvatskitelekom.hr.test.js Make test happy --- .../mojmaxtv.hrvatskitelekom.hr.test.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/sites/mojmaxtv.hrvatskitelekom.hr/mojmaxtv.hrvatskitelekom.hr.test.js b/sites/mojmaxtv.hrvatskitelekom.hr/mojmaxtv.hrvatskitelekom.hr.test.js index 3680a2ca..c91a7eb9 100644 --- a/sites/mojmaxtv.hrvatskitelekom.hr/mojmaxtv.hrvatskitelekom.hr.test.js +++ b/sites/mojmaxtv.hrvatskitelekom.hr/mojmaxtv.hrvatskitelekom.hr.test.js @@ -45,10 +45,6 @@ it('can generate valid request headers', () => { expect(request.headers).toMatchObject({ app_key: 'GWaBW4RTloLwpUgYVzOiW5zUxFLmoMj5', app_version: '02.0.1080', - 'device-id': 'a78f079d-5527-46d8-af3f-9f0b6b6fb758', - 'x-request-session-id': 'fc96c9de-7a3b-4b51-8b9d-5d9f9a3c3268', - 'x-request-tracking-id': '05a8f0bc-f977-4754-b8ad-1d4d1bd742fb', - 'x-user-agent': 'web|web|Chrome-128|02.0.1080|1' }) }) From f2759f8b3915a4d9f898851006dcd0b6b445b94d Mon Sep 17 00:00:00 2001 From: nirvana-7777 <48356537+nirvana-7777@users.noreply.github.com> Date: Fri, 21 Feb 2025 13:36:20 +0100 Subject: [PATCH 5/7] Update mojmaxtv.hrvatskitelekom.hr.config.js --- .../mojmaxtv.hrvatskitelekom.hr.config.js | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/sites/mojmaxtv.hrvatskitelekom.hr/mojmaxtv.hrvatskitelekom.hr.config.js b/sites/mojmaxtv.hrvatskitelekom.hr/mojmaxtv.hrvatskitelekom.hr.config.js index 45f40b81..e06d916a 100644 --- a/sites/mojmaxtv.hrvatskitelekom.hr/mojmaxtv.hrvatskitelekom.hr.config.js +++ b/sites/mojmaxtv.hrvatskitelekom.hr/mojmaxtv.hrvatskitelekom.hr.config.js @@ -50,26 +50,6 @@ const getHeaders = () => ({ const unknownRoles = new Map() -function detectUnknownRoles(item) { - if (!item.roles) return - - item.roles.forEach(role => { - if (!KNOWN_ROLES.has(role.role_name)) { - const count = unknownRoles.get(role.role_name) || 0 - unknownRoles.set(role.role_name, count + 1) - - // Log the first occurrence of each unknown role with an example - if (count === 0) { - console.log('New role type detected:', { - role_name: role.role_name, - example_person: role.person_name, - program_id: item.program_id - }) - } - } - }) -} - module.exports = { site: SITE_URL, url({ date }) { From 893f741473b4b02d3f72f7ad46e0fa74d55b48e0 Mon Sep 17 00:00:00 2001 From: nirvana-7777 <48356537+nirvana-7777@users.noreply.github.com> Date: Fri, 21 Feb 2025 13:40:24 +0100 Subject: [PATCH 6/7] Update mojmaxtv.hrvatskitelekom.hr.config.js --- .../mojmaxtv.hrvatskitelekom.hr.config.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/sites/mojmaxtv.hrvatskitelekom.hr/mojmaxtv.hrvatskitelekom.hr.config.js b/sites/mojmaxtv.hrvatskitelekom.hr/mojmaxtv.hrvatskitelekom.hr.config.js index e06d916a..c8259d06 100644 --- a/sites/mojmaxtv.hrvatskitelekom.hr/mojmaxtv.hrvatskitelekom.hr.config.js +++ b/sites/mojmaxtv.hrvatskitelekom.hr/mojmaxtv.hrvatskitelekom.hr.config.js @@ -48,8 +48,6 @@ const getHeaders = () => ({ 'x-user-agent': `web|web|Chrome-133|${APP_VERSION}|1` }) -const unknownRoles = new Map() - module.exports = { site: SITE_URL, url({ date }) { From 09e96c5d106016ae3001e0e525415ac64424b872 Mon Sep 17 00:00:00 2001 From: nirvana-7777 <48356537+nirvana-7777@users.noreply.github.com> Date: Fri, 21 Feb 2025 13:44:17 +0100 Subject: [PATCH 7/7] Update mojmaxtv.hrvatskitelekom.hr.config.js --- .../mojmaxtv.hrvatskitelekom.hr.config.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/sites/mojmaxtv.hrvatskitelekom.hr/mojmaxtv.hrvatskitelekom.hr.config.js b/sites/mojmaxtv.hrvatskitelekom.hr/mojmaxtv.hrvatskitelekom.hr.config.js index c8259d06..db783843 100644 --- a/sites/mojmaxtv.hrvatskitelekom.hr/mojmaxtv.hrvatskitelekom.hr.config.js +++ b/sites/mojmaxtv.hrvatskitelekom.hr/mojmaxtv.hrvatskitelekom.hr.config.js @@ -28,9 +28,6 @@ const API_ENDPOINT = `https://tv-${NATCO_CODE}-prod.yo-digital.com/${NATCO_CODE} const DEVICE_ID = crypto.randomUUID() const SESSION_ID = crypto.randomUUID() -// Set of known role names for quick lookup -const KNOWN_ROLES = new Set(Object.values(ROLE_TYPES)) - const cached = {} const getHeaders = () => ({