const axios = require('axios') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') dayjs.extend(utc) const API_URL = 'https://rail-router.discovery.indazn.com/eu/v10/Rail' const IMAGE_API_URL = 'https://image.discovery.indazn.com/eu/v3/linear-channel/none' const DEFAULT_COUNTRY = 'de' const SUPPORTED_COUNTRIES = 'ad ae af ag ai al am ao aq ar at au aw ax az ba bb bd be bf bg bh bi bj bl bm bn bo bq br bs bt bv bw bz ca cc cd cf cg ch ci ck cl cm co cr cv cw cx cy cz de dj dk dm do dz ec ee eg eh er es et fi fj fk fm fo fr ga gb gd ge gf gh gl gm gn gp gq gr gs gt gu gw gy hk hm hn hr ht hu id ie il in io iq is it jm jo jp ke kg kh ki km kn kr kw ky kz la lb lc li lk lr ls lt lu lv ly ma mc md me mf mg mh mk ml mm mn mo mp mq mr ms mt mu mv mw mx my mz na nc ne nf ng ni nl no np nr nu nz om pa pe pf pg ph pk pl pm pn ps pt pw py qa re ro rs rw sa sb sc sd se sg sh si sj sk sl sn so sr ss st sv sx sz tc td tf tg th tj tk tl tm tn to tr tt tv tw tz ua ug um us uy uz vc ve vg vi vn vu wf ws xk ye yt za zm zw'.split( ' ' ) const COUNTRY_LANGUAGES = { ar: 'es', at: 'de', be: 'fr', bo: 'es', br: 'pt', ch: 'de', cl: 'es', co: 'es', cr: 'es', de: 'de', do: 'es', ec: 'es', es: 'es', fr: 'fr', gt: 'es', hn: 'es', it: 'it', jp: 'ja', li: 'de', lu: 'fr', mc: 'fr', mx: 'es', ni: 'es', nl: 'nl', pa: 'es', pe: 'es', pt: 'pt', py: 'es', sv: 'es', tw: 'zh', uy: 'es', ve: 'es' } const PREFERRED_COUNTRIES = { de: 'de', en: 'gb', es: 'es', fr: 'fr', it: 'it', ja: 'jp', nl: 'nl', pt: 'pt', zh: 'tw' } const HEADERS = { Accept: 'application/json, text/plain, */*', Referer: 'https://www.dazn.com/' } module.exports = { site: 'dazn.com', days: 3, url({ channel }) { const { country } = parseSiteId(channel.site_id) return createApiUrl(country, channel.lang || getLanguage(country)) }, request: { headers: HEADERS, cache: { ttl: 5 * 60 * 1000 } }, parser({ content, channel, date }) { const data = parseData(content) const tiles = Array.isArray(data?.Tiles) ? data.Tiles : [] const { assetId } = parseSiteId(channel.site_id) const tile = tiles.find(item => item.AssetId === assetId) if (!tile?.LinearSchedule) return [] const schedule = tile.LinearSchedule const programs = [ schedule.Now, schedule.Next, ...(Array.isArray(schedule.Later) ? schedule.Later : []) ].filter(Boolean) return programs .filter(item => item.Start && item.End && dayjs.utc(item.Start).isSame(date, 'day')) .map(parseProgram) }, async channels({ country, lang } = {}) { if (country) return loadChannels(country, lang) const channelsByAssetAndLanguage = new Map() for (const supportedCountry of SUPPORTED_COUNTRIES) { for (const channel of await loadChannels(supportedCountry)) { const { assetId, country } = parseSiteId(channel.site_id) const key = `${assetId}#${channel.lang}` const current = channelsByAssetAndLanguage.get(key) if (!current || isPreferredCountry(country, channel.lang)) { channelsByAssetAndLanguage.set(key, channel) } } } return Array.from(channelsByAssetAndLanguage.values()) } } async function loadChannels(country, lang) { country = country.toLowerCase() if (!SUPPORTED_COUNTRIES.includes(country)) { throw new Error(`Unsupported DAZN country: ${country}`) } const language = lang || getLanguage(country) const { data } = await axios.get(createApiUrl(country, language), { headers: HEADERS }) const tiles = Array.isArray(data?.Tiles) ? data.Tiles : [] return tiles .filter(tile => tile.AssetId && tile.Title) .map(tile => ({ lang: language, site_id: createSiteId(country, tile.AssetId), name: tile.Title, logo: createImageUrl(tile.LogoImage, { resizeAction: 'contain', width: 68, height: 56, format: 'png' }) })) } function createApiUrl(country, language) { const params = new URLSearchParams({ platform: 'web', id: 'Livetvschedule', country, brand: 'dazn', languageCode: language }) return `${API_URL}?${params}` } function createSiteId(country, assetId) { return country === DEFAULT_COUNTRY ? assetId : `${country}#${assetId}` } function parseSiteId(siteId) { const separator = siteId.indexOf('#') if (separator === -1) return { country: DEFAULT_COUNTRY, assetId: siteId } return { country: siteId.slice(0, separator), assetId: siteId.slice(separator + 1) } } function getLanguage(country) { return COUNTRY_LANGUAGES[country] || 'en' } function isPreferredCountry(country, language) { return country === PREFERRED_COUNTRIES[language] } function parseData(content) { try { return typeof content === 'string' ? JSON.parse(content) : content } catch { return null } } function parseProgram(item) { const program = { title: item.Title, start: item.Start, stop: item.End } if (item.EpisodeTitle) program.subTitle = item.EpisodeTitle if (item.Description) program.description = item.Description if (item.EventYear) program.date = item.EventYear if (item.TvRating) program.rating = item.TvRating const categories = (Array.isArray(item.Genre) ? item.Genre : []) .map(genre => genre?.name) .filter(Boolean) if (item.ProgramType) categories.push(item.ProgramType) if (categories.length) program.categories = [...new Set(categories)] const image = item.BackgroundImage || item.GradientBackgroundImage || item.Image const icon = createImageUrl(image) if (icon) program.icon = icon return program } function createImageUrl( image, { resizeAction = 'fill', width = 856, height = 481, format = 'webp' } = {} ) { if (!image?.Id) return null return `${IMAGE_API_URL}/${encodeURIComponent( image.Id )}/${resizeAction}/center/center/none/80/${width}/${height}/${format}/image?brand=dazn` }