add custom fixes
This commit is contained in:
		| @@ -4,12 +4,16 @@ ARG GIT_BRANCH=master | ||||
| ENV CRON_SCHEDULE="0 0,12 * * *" | ||||
| ENV DAYS=14 | ||||
| ENV MAX_CONNECTIONS=10 | ||||
| ENV ENABLE_FIXES=false | ||||
| ARG BIN_FOLDER=/bin | ||||
| ARG EPG_FOLDER=epg | ||||
| ARG FIXES_FOLDER_ARG=fixes | ||||
| ARG START_SCRIPT_ARG=$BIN_FOLDER/$EPG_FOLDER/start.sh | ||||
| ENV WORKDIR=${BIN_FOLDER}/${EPG_FOLDER} | ||||
| ENV FIXES_FOLDER=$FIXES_FOLDER_ARG | ||||
| ENV START_SCRIPT=$START_SCRIPT_ARG | ||||
| COPY channels.xml /config/channels.xml | ||||
| ADD $FIXES_FOLDER /fixes | ||||
| RUN apk update \ | ||||
|     && apk upgrade --available \ | ||||
| 	  && apk add curl \ | ||||
| @@ -40,5 +44,6 @@ COPY serve.json $WORKDIR | ||||
| RUN chmod +x "$START_SCRIPT" \ | ||||
|   && apk del git curl \ | ||||
|   && rm -rf /var/cache/apk/* | ||||
| ENTRYPOINT bash $START_SCRIPT chron-schedule="$CRON_SCHEDULE" work-dir="$WORKDIR" days="$DAYS" max_connections="$MAX_CONNECTIONS" | ||||
| SHELL ["/bin/bash", "-c"] | ||||
| ENTRYPOINT bash $START_SCRIPT chron-schedule="$CRON_SCHEDULE" work-dir="$WORKDIR" days="$DAYS" max_connections="$MAX_CONNECTIONS" enable_fixes="$ENABLE_FIXES" | ||||
| EXPOSE 3000 | ||||
							
								
								
									
										74
									
								
								fixes/movistarplus.es/movistarplus.es.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								fixes/movistarplus.es/movistarplus.es.config.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,74 @@ | ||||
| const { DateTime } = require('luxon') | ||||
|  | ||||
| const API_PROD_ENDPOINT = 'https://www.movistarplus.es/programacion-tv' | ||||
| const API_IMAGE_ENDPOINT = 'https://www.movistarplus.es/recorte/n/caratulaH/'; | ||||
|  | ||||
| module.exports = { | ||||
|   site: 'movistarplus.es', | ||||
|   days: 2, | ||||
|   url: function ({ date }) { | ||||
|     return `${API_PROD_ENDPOINT}/${date.format('YYYY-MM-DD')}?v=json` | ||||
|   }, | ||||
|   parser({ content, channel, date }) { | ||||
|     let programs = [] | ||||
|     let items = parseItems(content, channel) | ||||
|     if (!items.length) return programs | ||||
|     let guideDate = date | ||||
|  | ||||
|     items.forEach(item => { | ||||
|       let startTime = DateTime.fromFormat( | ||||
|         `${guideDate.format('YYYY-MM-DD')} ${item.HORA_INICIO}`, | ||||
|         'yyyy-MM-dd HH:mm', | ||||
|         { | ||||
|           zone: 'Europe/Madrid' | ||||
|         } | ||||
|       ).toUTC() | ||||
|       let stopTime = DateTime.fromFormat( | ||||
|         `${guideDate.format('YYYY-MM-DD')} ${item.HORA_FIN}`, | ||||
|         'yyyy-MM-dd HH:mm', | ||||
|         { | ||||
|           zone: 'Europe/Madrid' | ||||
|         } | ||||
|       ).toUTC() | ||||
|       if (stopTime < startTime) { | ||||
|         guideDate = guideDate.add(1, 'd') | ||||
|         stopTime = stopTime.plus({ days: 1 }) | ||||
|       } | ||||
|       programs.push({ | ||||
|         title: item.TITULO, | ||||
|         icon: parseIcon(item, channel), | ||||
|         category: item.GENERO, | ||||
|         start: startTime, | ||||
|         stop: stopTime | ||||
|       }) | ||||
|     }) | ||||
|     return programs | ||||
|   }, | ||||
|   async channels() { | ||||
|     const axios = require('axios') | ||||
|     const dayjs = require('dayjs') | ||||
|     const data = await axios | ||||
|       .get(`${API_PROD_ENDPOINT}/${dayjs().format('YYYY-MM-DD')}?v=json`) | ||||
|       .then(r => r.data) | ||||
|       .catch(console.log) | ||||
|  | ||||
|     return Object.values(data.data).map(item => { | ||||
|       return { | ||||
|         lang: 'es', | ||||
|         site_id: item.DATOS_CADENA.CODIGO, | ||||
|         name: item.DATOS_CADENA.NOMBRE | ||||
|       } | ||||
|     }) | ||||
|   } | ||||
| } | ||||
|  | ||||
| function parseIcon(item, channel) { | ||||
|   return `${API_IMAGE_ENDPOINT}/M${channel.site_id}P${item.ELEMENTO}`; | ||||
| } | ||||
|  | ||||
| function parseItems(content, channel) { | ||||
|   const json = typeof content === 'string' ? JSON.parse(content) : content | ||||
|   if (!(`${channel.site_id}-CODE` in json.data)) return [] | ||||
|   const data = json.data[`${channel.site_id}-CODE`] | ||||
|   return data ? data.PROGRAMAS : [] | ||||
| } | ||||
							
								
								
									
										178
									
								
								fixes/pickx.be/pickx.be.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										178
									
								
								fixes/pickx.be/pickx.be.config.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,178 @@ | ||||
| const axios = require('axios') | ||||
| const dayjs = require('dayjs') | ||||
| const utc = require('dayjs/plugin/utc') | ||||
|  | ||||
| let apiVersion | ||||
| let isApiVersionFetched = false | ||||
|  | ||||
| ;(async () => { | ||||
|   try { | ||||
|     await fetchApiVersion() | ||||
|     isApiVersionFetched = true | ||||
|   } catch (error) { | ||||
|     console.error('Error during script initialization:', error) | ||||
|   } | ||||
| })() | ||||
|  | ||||
| dayjs.extend(utc) | ||||
|  | ||||
| module.exports = { | ||||
|   site: 'pickx.be', | ||||
|   days: 2, | ||||
|   apiVersion: function () { | ||||
|     return apiVersion | ||||
|   }, | ||||
|   fetchApiVersion: fetchApiVersion, // Export fetchApiVersion | ||||
|   url: async function ({ channel, date }) { | ||||
|     while (!isApiVersionFetched) { | ||||
|       await new Promise(resolve => setTimeout(resolve, 100)) // Wait for 100 milliseconds | ||||
|     } | ||||
|     return `https://px-epg.azureedge.net/airings/${apiVersion}/${date.format( | ||||
|       'YYYY-MM-DD' | ||||
|     )}/channel/${channel.site_id}?timezone=Europe%2FBrussels` | ||||
|   }, | ||||
|   request: { | ||||
|     headers: { | ||||
|       Origin: 'https://www.pickx.be', | ||||
|       Referer: 'https://www.pickx.be/' | ||||
|     } | ||||
|   }, | ||||
|   parser({ channel, content }) { | ||||
|     const programs = [] | ||||
|     if (content) { | ||||
|       const items = JSON.parse(content) | ||||
|       items.forEach(item => { | ||||
|         programs.push({ | ||||
|           title: item.program.title, | ||||
|           sub_title: item.program.episodeTitle, | ||||
|           description: item.program.description, | ||||
|           category: item.program.translatedCategory?.[channel.lang] | ||||
|             ? item.program.translatedCategory[channel.lang] | ||||
|             : item.program.category.split('.')[1], | ||||
|           image: item.program.posterFileName | ||||
|             ? `https://experience-cache.proximustv.be/posterserver/poster/EPG/w-166_h-110/${item.program.posterFileName}` | ||||
|             : null, | ||||
|           season: item.program.seasonNumber, | ||||
|           episode: item.program.episodeNumber, | ||||
|           actors: item.program.actors, | ||||
|           director: item.program.director ? [item.program.director] : null, | ||||
|           start: dayjs.utc(item.programScheduleStart), | ||||
|           stop: dayjs.utc(item.programScheduleEnd) | ||||
|         }) | ||||
|       }) | ||||
|     } | ||||
|  | ||||
|     return programs | ||||
|   }, | ||||
|   async channels({ lang = '' }) { | ||||
|     const query = { | ||||
|       operationName: 'getChannels', | ||||
|       variables: { | ||||
|         language: lang, | ||||
|         queryParams: {}, | ||||
|         id: '0', | ||||
|         params: { | ||||
|           shouldReadFromCache: true | ||||
|         } | ||||
|       }, | ||||
|       query: `query getChannels($language: String!, $queryParams: ChannelQueryParams, $id: String, $params: ChannelParams) { | ||||
|           channels(language: $language, queryParams: $queryParams, id: $id, params: $params) { | ||||
|             id | ||||
|             channelReferenceNumber | ||||
|             name | ||||
|             callLetter | ||||
|             number | ||||
|             logo { | ||||
|               key | ||||
|               url | ||||
|               __typename | ||||
|             } | ||||
|             language | ||||
|             hd | ||||
|             radio | ||||
|             replayable | ||||
|             ottReplayable | ||||
|             playable | ||||
|             ottPlayable | ||||
|             recordable | ||||
|             subscribed | ||||
|             cloudRecordable | ||||
|             catchUpWindowInHours | ||||
|             isOttNPVREnabled | ||||
|             ottNPVRStart | ||||
|             subscription { | ||||
|               channelRef | ||||
|               subscribed | ||||
|               upselling { | ||||
|                 upsellable | ||||
|                 packages | ||||
|                 __typename | ||||
|               } | ||||
|               __typename | ||||
|             } | ||||
|             packages | ||||
|             __typename | ||||
|           } | ||||
|         }` | ||||
|     } | ||||
|     const result = await axios | ||||
|       .post('https://api.proximusmwc.be/tiams/v2/graphql', query) | ||||
|       .then(r => r.data) | ||||
|       .catch(console.error) | ||||
|  | ||||
|     return ( | ||||
|       result?.data?.channels | ||||
|         .filter( | ||||
|           channel => | ||||
|             !channel.radio && (!lang || channel.language === (lang === 'de' ? 'ger' : lang)) | ||||
|         ) | ||||
|         .map(channel => { | ||||
|           return { | ||||
|             lang: channel.language === 'ger' ? 'de' : channel.language, | ||||
|             site_id: channel.id, | ||||
|             name: channel.name | ||||
|           } | ||||
|         }) || [] | ||||
|     ) | ||||
|   } | ||||
| } | ||||
| function fetchApiVersion() { | ||||
|   return new Promise(async (resolve, reject) => { | ||||
|     try { | ||||
|       // https://px-epg.azureedge.net/version is deprecated | ||||
|       // probably the version url will be changed around over time | ||||
|  | ||||
|       //history of used version urls | ||||
|       //const versionUrl = 'https://www.pickx.be/api/s-3b36540f3cef64510112f3f95c2c0cdca321997ed2b1042ad778523235e155eb' | ||||
|       //const versionUrl = 'https://www.pickx.be/api/s-671f172425e1bc74cd0440fd67aaa6cbe68b582f3f401186c2f46ae97e80516b' | ||||
|       //const versionUrl = 'https://www.pickx.be/api/s-a6b4b4fefaa20e438523a6167e63b8504d96b9df8303473349763c4418cffe30' | ||||
|       //const versionUrl = 'https://www.pickx.be/api/s-8546c5fd136241d42aab714d2fe3ccc5671fd899035efae07cd0b8f4eb23994e' | ||||
|       //const versionUrl = 'https://www.pickx.be/api/s-64464ad9a3bc117af5dca620027216ecade6a51c230135a0f134c0ee042ff407'; | ||||
|       //const versionUrl = 'https://www.pickx.be/api/s-626d8fdabfb1d44e5a614cd69f4b45d6843fdb63566fc80ea4f97f40e4ea3152'; | ||||
|       //const versionUrl = 'https://www.pickx.be/api/s-cefaf96e249e53648c4895c279e7a621233c50b4357d62b0bdf6bff45f31b5c0'; | ||||
|       //const versionUrl = 'https://www.pickx.be/api/s-7fa35253080e9665f9c7d9d85e707d6fb1d1bf07ede11965e859fcb57c723949'; | ||||
|       //the new strategy to break the provider is to leave old version url's available and to return invalid results on those endpoints | ||||
|  | ||||
|       const versionUrl = 'https://www.pickx.be/api/s-0e58be3938175b6b900dfb5233bd5cfc0bcf915b633fe57b935f7ce8dbe5f6eb'; | ||||
|  | ||||
|  | ||||
|       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() | ||||
|       } else { | ||||
|         console.error(`Failed to fetch API version. Status: ${response.status}`) | ||||
|         reject(`Failed to fetch API version. Status: ${response.status}`) | ||||
|       } | ||||
|     } catch (error) { | ||||
|       console.error('Error fetching API version:', error.message) | ||||
|       reject(error) | ||||
|     } | ||||
|   }) | ||||
| } | ||||
							
								
								
									
										138
									
								
								fixes/telenet.tv/telenet.tv.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										138
									
								
								fixes/telenet.tv/telenet.tv.config.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,138 @@ | ||||
| const axios = require('axios') | ||||
| 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'; | ||||
|  | ||||
| module.exports = { | ||||
|   site: 'telenet.tv', | ||||
|   days: 2, | ||||
|   request: { | ||||
|     cache: { | ||||
|       ttl: 60 * 60 * 1000 // 1 hour | ||||
|     } | ||||
|   }, | ||||
|   url: function ({ date, channel }) { | ||||
|     return `${API_STATIC_ENDPOINT}/${channel.lang}/events/segments/${date.format('YYYYMMDDHHmmss')}` | ||||
|   }, | ||||
|   async parser({ content, channel, date }) { | ||||
|     let programs = [] | ||||
|     let items = parseItems(content, channel) | ||||
|     if (!items.length) return programs | ||||
|     const promises = [ | ||||
|       axios.get( | ||||
|         `${API_STATIC_ENDPOINT}/${channel.lang}/events/segments/${date | ||||
|           .add(6, 'h') | ||||
|           .format('YYYYMMDDHHmmss')}`, | ||||
|         { | ||||
|           responseType: 'arraybuffer' | ||||
|         } | ||||
|       ), | ||||
|       axios.get( | ||||
|         `${API_STATIC_ENDPOINT}/${channel.lang}/events/segments/${date | ||||
|           .add(12, 'h') | ||||
|           .format('YYYYMMDDHHmmss')}`, | ||||
|         { | ||||
|           responseType: 'arraybuffer' | ||||
|         } | ||||
|       ), | ||||
|       axios.get( | ||||
|         `${API_STATIC_ENDPOINT}/${channel.lang}/events/segments/${date | ||||
|           .add(18, 'h') | ||||
|           .format('YYYYMMDDHHmmss')}`, | ||||
|         { | ||||
|           responseType: 'arraybuffer' | ||||
|         } | ||||
|       ) | ||||
|     ] | ||||
|  | ||||
|     await Promise.allSettled(promises) | ||||
|       .then(results => { | ||||
|         results.forEach(r => { | ||||
|           if (r.status === 'fulfilled') { | ||||
|             const parsed = parseItems(r.value.data, channel) | ||||
|  | ||||
|             items = items.concat(parsed) | ||||
|           } | ||||
|         }) | ||||
|       }) | ||||
|       .catch(console.error) | ||||
|  | ||||
|     for (let item of items) { | ||||
|       const detail = await loadProgramDetails(item, channel) | ||||
|       programs.push({ | ||||
|         title: item.title, | ||||
|         icon: parseIcon(item), | ||||
|         description: detail.longDescription, | ||||
|         category: detail.genres, | ||||
|         actors: detail.actors, | ||||
|         season: parseSeason(detail), | ||||
|         episode: parseEpisode(detail), | ||||
|         start: parseStart(item), | ||||
|         stop: parseStop(item) | ||||
|       }) | ||||
|     } | ||||
|  | ||||
|     return programs | ||||
|   }, | ||||
|   async channels() { | ||||
|     const data = await axios | ||||
|       .get(`${API_PROD_ENDPOINT}/channels?cityId=28001&language=en&productClass=Orion-DASH`) | ||||
|       .then(r => r.data) | ||||
|       .catch(console.log) | ||||
|  | ||||
|     return data.map(item => { | ||||
|       return { | ||||
|         lang: 'nl', | ||||
|         site_id: item.id, | ||||
|         name: item.name | ||||
|       } | ||||
|     }) | ||||
|   } | ||||
| } | ||||
|  | ||||
| async function loadProgramDetails(item, channel) { | ||||
|   if (!item.id) return {} | ||||
|     const url = `${API_PROD_ENDPOINT}/replayEvent/${item.id}?returnLinearContent=true&language=${channel.lang}` | ||||
|   const data = await axios | ||||
|     .get(url) | ||||
|     .then(r => r.data) | ||||
|     .catch(console.log) | ||||
|  | ||||
|   return data || {} | ||||
| } | ||||
|  | ||||
| function parseStart(item) { | ||||
|   return dayjs.unix(item.startTime) | ||||
| } | ||||
|  | ||||
| function parseStop(item) { | ||||
|   return dayjs.unix(item.endTime) | ||||
| } | ||||
|  | ||||
| function parseItems(content, channel) { | ||||
|   if (!content) return [] | ||||
|   const data = JSON.parse(content) | ||||
|   if (!data || !Array.isArray(data.entries)) return [] | ||||
|   const channelData = data.entries.find(e => e.channelId === channel.site_id) | ||||
|   if (!channelData) return [] | ||||
|  | ||||
|   return Array.isArray(channelData.events) ? channelData.events : [] | ||||
| } | ||||
|  | ||||
| function parseSeason(detail) { | ||||
|   if (!detail.seasonNumber) return null | ||||
|   if (String(detail.seasonNumber).length > 2) return null | ||||
|   return detail.seasonNumber | ||||
| } | ||||
|  | ||||
| function parseEpisode(detail) { | ||||
|   if (!detail.episodeNumber) return null | ||||
|   if (String(detail.episodeNumber).length > 3) return null | ||||
|   return detail.episodeNumber | ||||
| } | ||||
|  | ||||
| function parseIcon(item) { | ||||
|   return `${API_IMAGE_ENDPOINT}/intent/${item.id}/posterTile`; | ||||
| } | ||||
							
								
								
									
										6
									
								
								start.sh
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								start.sh
									
									
									
									
									
								
							| @@ -7,6 +7,7 @@ for arg in "$@"; do | ||||
|       work-dir=*) work_dir="${arg#*=}" ;; | ||||
|       days=*) days="${arg#*=}" ;; | ||||
|       max_connections=*) max_connections="${arg#*=}" ;; | ||||
|       enable_fixes=*) enable_fixes="${arg#*=}" ;; | ||||
|    esac | ||||
| done | ||||
|  | ||||
| @@ -15,6 +16,11 @@ cd $work_dir | ||||
| echo "working dir : " $(pwd) | ||||
| echo "days : ${days}" | ||||
| echo "max_connections : ${max_connections}" | ||||
| echo "enable_fixes : ${enable_fixes}" | ||||
|  | ||||
| if [ "$enable_fixes" = true ] ; then | ||||
|  cp -R /fixes/* /bin/epg/sites/ | ||||
| fi | ||||
|  | ||||
| pm2 --name epg start npm -- run serve | ||||
| npm run grab -- --channels=channels.xml --maxConnections=$max_connections --days=$days --gzip | ||||
|   | ||||
		Reference in New Issue
	
	Block a user