Skip to content

Commit aefaf70

Browse files
authored
feat(myCANAL): enhance content display and fix various display issues (#10453)
* fix: update title selector and cache data * feat: add 'Watching' status and add settings to display the show title as activity name * fix: improve cache logic and selectors * fix: display image and name of current channel * feat: display current movie synospis * refactor: extract content handlers and update metadata * refactor: enhance cache logic for live videos
1 parent e02e1c5 commit aefaf70

File tree

2 files changed

+157
-84
lines changed

2 files changed

+157
-84
lines changed

websites/M/myCANAL/metadata.json

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313
{
1414
"name": "Arias800",
1515
"id": "341235976766488576"
16+
},
17+
{
18+
"name": "ByZorks",
19+
"id": "252147945011544065"
1620
}
1721
],
1822
"service": "myCANAL",
@@ -23,7 +27,7 @@
2327
},
2428
"url": "www.canalplus.com",
2529
"regExp": "^https?[:][/][/](www[.])?canalplus[.]com[/]",
26-
"version": "2.1.0",
30+
"version": "2.2.0",
2731
"logo": "https://cdn.rcd.gg/PreMiD/websites/M/myCANAL/assets/logo.png",
2832
"thumbnail": "https://cdn.rcd.gg/PreMiD/websites/M/myCANAL/assets/thumbnail.png",
2933
"color": "#000",
@@ -38,6 +42,12 @@
3842
"title": "Show Cover",
3943
"icon": "fad fa-images",
4044
"value": true
45+
},
46+
{
47+
"id": "useTitleAsName",
48+
"title": "Use Show Title as Activity Name",
49+
"icon": "fad fa-heading",
50+
"value": true
4151
}
4252
]
4353
}

websites/M/myCANAL/presence.ts

Lines changed: 146 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Assets } from 'premid'
1+
import { ActivityType, Assets, getTimestamps } from 'premid'
22

33
const presence = new Presence({
44
clientId: '503557087041683458',
@@ -135,110 +135,173 @@ export async function getThumbnail(
135135
})
136136
}
137137

138+
// Data we need is deleted when player controls disappear
139+
let cachedTitleTvShows: [string | null, string | null] = [null, null]
140+
let cachedEpisodeTitle: string | null = null
141+
let cachedSynopsis: string | null = null
142+
let cachedChannelName: string | null = null
143+
let cachedThumbnailUrl: string | null = null
144+
let lastImgSrc: string | null = null
145+
146+
const navigationRoutes: Record<string, string> = {
147+
'/mes-videos/': 'Mes Vidéos',
148+
'/chaines/': 'Chaînes',
149+
'/programme-tv/': 'Programme TV',
150+
'/cinema/': 'Films',
151+
'/series/': 'Séries',
152+
'/jeunesse/': 'Jeunesse',
153+
'/live/': 'Chaînes en direct',
154+
'/documentaires/': 'Documentaires',
155+
'/divertissement/': 'Divertissements',
156+
'/info/': 'Infos',
157+
'/musique/': 'Musique',
158+
'/sport/': 'Sport',
159+
}
160+
138161
presence.on('UpdateData', async () => {
139162
const presenceData: PresenceData = {
140163
largeImageKey: myCANALAssets.Logo,
164+
largeImageText: 'myCANAL',
141165
name: 'myCANAL',
166+
type: ActivityType.Watching,
142167
}
143168
const video = document.querySelector<HTMLVideoElement>('.iIZX3IGkM2eBzzWle1QQ')
144169
const showCover = await presence.getSetting<boolean>('cover')
145-
const mainTitle = document.querySelector('.bodyTitle___HwRP2')
146-
147-
switch (document.location.pathname) {
148-
case '/mes-videos/':
149-
presenceData.state = 'Mes Vidéos'
150-
break
151-
case '/chaines/':
152-
presenceData.state = 'Chaînes'
153-
break
154-
case '/programme-tv/':
155-
presenceData.state = 'Programme TV'
156-
break
157-
case '/cinema/':
158-
presenceData.state = 'Films'
159-
break
160-
case '/series/':
161-
presenceData.state = 'Séries'
162-
break
163-
case '/jeunesse/':
164-
presenceData.state = 'Jeunesse'
165-
break
166-
case '/live/':
167-
presenceData.state = 'Chaînes en direct'
168-
break
170+
const mainTitle = document.querySelector('.stickyTitle___HRELo')
171+
const currentPathName = document.location.pathname
172+
173+
if (navigationRoutes[currentPathName]) {
174+
presenceData.state = navigationRoutes[currentPathName]
169175
}
170176

171177
if (video && !Number.isNaN(video.duration)) {
172-
const titleTvShows = document.querySelectorAll('.MGrm26svmXpUhj6dfbGN')
173-
let channelID = new URLSearchParams(window.location.search).get('channel')
174-
switch (true) {
175-
case containsTerm('live'):
176-
channelID = `${channelID?.charAt(0)} ${channelID?.substring(1)}`
177-
presenceData.details = document.querySelector(
178-
'.A6AH2oNkXUuOKJN5IYrL',
179-
)?.textContent
180-
presenceData.state = `sur ${
181-
document.querySelector<HTMLImageElement>(
182-
`#\\3${channelID}_onclick > div > div.card__content_0dae1b.cardContent___DuNAN.ratio--169 > div[class*="cardLogoChannel"] > div > img`,
183-
)?.alt
184-
}`;
185-
[presenceData.startTimestamp, presenceData.endTimestamp] = presence.getTimestamps(video.currentTime, video.duration)
186-
presenceData.largeImageKey = showCover
187-
? document.querySelector<HTMLImageElement>(
188-
`#\\3${channelID}_onclick > div > div.card__content_0dae1b.cardContent___DuNAN.ratio--169 > div[class*="cardLogoChannel"] > div > img`,
189-
)?.src
190-
: myCANALAssets.Logo
191-
presenceData.smallImageKey = Assets.Live
192-
presenceData.smallImageText = 'En direct'
193-
delete presenceData.startTimestamp
194-
delete presenceData.endTimestamp
195-
presenceData.startTimestamp = browsingTimestamp
196-
break
197-
case containsTerm('cinema'):
198-
presenceData.details = document.querySelector(
199-
'.A6AH2oNkXUuOKJN5IYrL',
200-
)?.textContent;
201-
[presenceData.startTimestamp, presenceData.endTimestamp] = presence.getTimestamps(video.currentTime, video.duration)
202-
presenceData.largeImageKey = showCover
203-
? (presenceData.largeImageKey = await getThumbnail(
204-
document.querySelector<HTMLMetaElement>('[property=\'og:image\']')
205-
?.content,
206-
))
207-
: myCANALAssets.Logo
208-
presenceData.smallImageKey = video.paused ? Assets.Pause : Assets.Play
209-
presenceData.smallImageText = video.paused
210-
? (await strings).pause
211-
: (await strings).play
212-
break
213-
case containsTerm('series'):
214-
case containsTerm('jeunesse'):
215-
presenceData.details = titleTvShows[0]?.textContent?.trim()
216-
presenceData.state = titleTvShows[1]?.textContent?.trim();
217-
[presenceData.startTimestamp, presenceData.endTimestamp] = presence.getTimestamps(video.currentTime, video.duration)
218-
presenceData.largeImageKey = showCover
219-
? (presenceData.largeImageKey = await getThumbnail(
220-
document.querySelector<HTMLMetaElement>('[property=\'og:image\']')
221-
?.content,
222-
))
223-
: myCANALAssets.Logo
224-
presenceData.smallImageKey = video.paused ? Assets.Pause : Assets.Play
225-
presenceData.smallImageText = video.paused
226-
? (await strings).pause
227-
: (await strings).play
228-
break
178+
updateTitleForTvShowsCache()
179+
180+
const showTitleAsActivity = await presence.getSetting<boolean>('useTitleAsName')
181+
182+
if (containsTerm('live')) {
183+
await handleLiveContent(presenceData, showCover)
229184
}
185+
else if (containsTerm('cinema')) {
186+
await handleCinemaContent(presenceData, video, showCover)
187+
}
188+
else if (containsTerm('series') || containsTerm('jeunesse')) {
189+
await handleSeriesContent(presenceData, video, showCover, showTitleAsActivity, mainTitle)
190+
}
191+
230192
if (video.paused) {
231193
delete presenceData.startTimestamp
232194
delete presenceData.endTimestamp
233195
}
196+
197+
if (showTitleAsActivity && mainTitle) {
198+
presenceData.name = mainTitle.textContent?.trim()
199+
}
234200
}
235201
else if (mainTitle) {
236202
presenceData.details = 'Regarde...'
237203
presenceData.state = mainTitle.textContent
238204
}
239205
else {
240206
presenceData.details = 'Navigue...'
207+
resetCaches()
241208
}
242209

243-
presence.setActivity(presenceData)
210+
await presence.setActivity(presenceData)
244211
})
212+
213+
function resetCaches() {
214+
cachedTitleTvShows = [null, null]
215+
cachedEpisodeTitle = null
216+
cachedSynopsis = null
217+
cachedChannelName = null
218+
}
219+
220+
function updateTitleForTvShowsCache() {
221+
const titleTvShows = document.querySelectorAll('.RbcMSl3qdUyV2kb3rAEg')
222+
if (titleTvShows.length >= 2) {
223+
cachedTitleTvShows = [
224+
titleTvShows[0]?.textContent?.trim() || null,
225+
titleTvShows[1]?.textContent?.trim() || null,
226+
]
227+
}
228+
}
229+
230+
async function handleLiveContent(presenceData: MediaPresenceData | NonMediaPresenceData, showCover: boolean) {
231+
presenceData.details = document.querySelector('.A6AH2oNkXUuOKJN5IYrL')?.textContent
232+
233+
if (showCover) {
234+
const channelImg = document.querySelector<HTMLImageElement>('.w4vxo5X8LLEAi6TxyFxu')
235+
if (channelImg) {
236+
if (channelImg.src !== lastImgSrc) {
237+
lastImgSrc = channelImg.src
238+
cachedThumbnailUrl = await getThumbnail(channelImg.src)
239+
}
240+
cachedChannelName = channelImg.alt.split('Logo de la chaîne ')[1] || 'une chaîne'
241+
}
242+
243+
presenceData.largeImageKey = cachedThumbnailUrl || myCANALAssets.Logo
244+
}
245+
246+
presenceData.state = `sur ${cachedChannelName || 'une chaîne'}`
247+
presenceData.startTimestamp = browsingTimestamp
248+
249+
presenceData.smallImageKey = Assets.Live
250+
presenceData.smallImageText = 'En direct'
251+
}
252+
253+
async function handleCinemaContent(presenceData: MediaPresenceData | NonMediaPresenceData, video: HTMLVideoElement, showCover: boolean) {
254+
presenceData.details = document.querySelector('.A6AH2oNkXUuOKJN5IYrL')?.textContent
255+
presenceData.state = document.head.querySelector('[name="description"]')?.getAttribute('content')?.trim();
256+
257+
[presenceData.startTimestamp, presenceData.endTimestamp] = getTimestamps(video.currentTime, video.duration)
258+
259+
if (showCover) {
260+
presenceData.largeImageKey = await getThumbnail(
261+
document.querySelector<HTMLMetaElement>('[property=\'og:image\']')?.content,
262+
)
263+
}
264+
265+
presenceData.smallImageKey = video.paused ? Assets.Pause : Assets.Play
266+
presenceData.smallImageText = video.paused ? (await strings).pause : (await strings).play
267+
}
268+
269+
async function handleSeriesContent(presenceData: PresenceData, video: HTMLVideoElement, showCover: boolean, showTitleAsActivity: boolean, mainTitle: Element | null) {
270+
const episodeTitle = cachedTitleTvShows[1]?.substring(cachedTitleTvShows[1]?.indexOf(':') + 1)?.trim()
271+
272+
if (showTitleAsActivity && mainTitle) {
273+
presenceData.details = episodeTitle
274+
if (episodeTitle && episodeTitle !== cachedEpisodeTitle) {
275+
cachedEpisodeTitle = episodeTitle
276+
cachedSynopsis = null
277+
for (const el of document.querySelectorAll('[class*="episode-editorial__editorial-title"]')) {
278+
if (el.textContent === episodeTitle) {
279+
cachedSynopsis = el.nextSibling?.textContent?.trim() || null
280+
break
281+
}
282+
}
283+
}
284+
if (cachedSynopsis) {
285+
presenceData.state = cachedSynopsis
286+
}
287+
}
288+
else {
289+
presenceData.details = mainTitle?.textContent?.trim()
290+
presenceData.state = episodeTitle
291+
}
292+
293+
[presenceData.startTimestamp, presenceData.endTimestamp] = getTimestamps(video.currentTime, video.duration)
294+
295+
if (showCover) {
296+
presenceData.largeImageKey = await getThumbnail(
297+
document.querySelector<HTMLMetaElement>('[property=\'og:image\']')?.content,
298+
)
299+
}
300+
301+
const showSeason = `${cachedTitleTvShows[0]?.split('-').at(-1)?.trim()}`
302+
const showEpisode = `${cachedTitleTvShows[1]?.split(':')[0]?.trim()}`
303+
presenceData.largeImageText = `${showSeason}, ${showEpisode}`
304+
305+
presenceData.smallImageKey = video.paused ? Assets.Pause : Assets.Play
306+
presenceData.smallImageText = video.paused ? (await strings).pause : (await strings).play
307+
}

0 commit comments

Comments
 (0)