From 3cfc143e67715a332ace545e85fa86a48aa8448a Mon Sep 17 00:00:00 2001 From: SHISEN Date: Tue, 23 Dec 2025 13:05:53 +0400 Subject: [PATCH] feat(Kinopoisk): fixed selectors, added type Watching --- websites/K/Kinopoisk/metadata.json | 16 ++- websites/K/Kinopoisk/presence.ts | 173 ++++++++++++++++++----------- 2 files changed, 121 insertions(+), 68 deletions(-) diff --git a/websites/K/Kinopoisk/metadata.json b/websites/K/Kinopoisk/metadata.json index 0789203970ae..6eb0a6b97042 100644 --- a/websites/K/Kinopoisk/metadata.json +++ b/websites/K/Kinopoisk/metadata.json @@ -3,7 +3,7 @@ "apiVersion": 1, "author": { "id": "257151906038677504", - "name": "SHISEN" + "name": "shisen" }, "service": "Kinopoisk", "description": { @@ -15,7 +15,7 @@ "hd.kinopoisk.ru" ], "regExp": "^https?[:][/][/](www|hd)[.]kinopoisk[.]ru[/]", - "version": "1.1.0", + "version": "1.2.0", "logo": "https://cdn.rcd.gg/PreMiD/websites/K/Kinopoisk/assets/logo.png", "thumbnail": "https://cdn.rcd.gg/PreMiD/websites/K/Kinopoisk/assets/thumbnail.png", "color": "#ff5900", @@ -34,9 +34,21 @@ }, { "id": "time", + "if": { + "privacy": false + }, "title": "Показывать оставшееся время просмотра", "icon": "fad fa-stopwatch", "value": true + }, + { + "id": "useActivityName", + "if": { + "privacy": false + }, + "title": "Отображать название как Активность", + "icon": "fad fa-user-edit", + "value": false } ] } diff --git a/websites/K/Kinopoisk/presence.ts b/websites/K/Kinopoisk/presence.ts index 715230fecc40..c23d2a9aaf18 100644 --- a/websites/K/Kinopoisk/presence.ts +++ b/websites/K/Kinopoisk/presence.ts @@ -1,9 +1,8 @@ -import { Assets } from 'premid' +import { ActivityType, Assets, getTimestampsFromMedia } from 'premid' const presence = new Presence({ clientId: '1034799018980679680', }) -const browsingTimestamp = Math.floor(Date.now() / 1000) async function getStrings() { return presence.getStrings( @@ -12,11 +11,11 @@ async function getStrings() { pause: 'general.paused', view: 'general.view', }, - ) } let strings: Awaited> +let contentType: string | null | undefined let contentTitle: string | null | undefined let contentSerieTitle: string | null | undefined @@ -24,11 +23,13 @@ function textContent(tags: string) { return document.querySelector(tags)?.textContent?.trim() } -function pageTitle(string: string) { - return document +function pageTitle(string?: string): string | string[] { + const pageTitle = document .querySelector('title') - ?.textContent - ?.split(string) + ?.textContent ?? '' + if (string) + return pageTitle.split(string) + return pageTitle } enum ActivityAssets { @@ -41,9 +42,10 @@ presence.on('UpdateData', async () => { details: 'Где-то на сайте', smallImageText: 'Kinopoisk', } - const [privacy, time] = await Promise.all([ + const [privacy, time, useActivityName] = await Promise.all([ presence.getSetting('privacy'), presence.getSetting('time'), + presence.getSetting('useActivityName'), ]) const { hostname, pathname } = document.location @@ -63,18 +65,18 @@ presence.on('UpdateData', async () => { switch (pathname.split('/')[2]) { case 'categories': presenceData.details = 'Смотрит списки' - presenceData.state = textContent('.styles_activeCategory__mDu7i') + presenceData.state = textContent('a[aria-current="location"]') break case 'movies': presenceData.details = 'Смотрит список' - presenceData.state = textContent('.styles_title__jB8AZ') + presenceData.state = textContent('main > div:nth-of-type(1) h1') break } break case 'film': presenceData.details = 'Смотрит страницу фильма' if (!pathname.split('/')[3]) { - presenceData.state = textContent('.styles_title__65Zwx') + presenceData.state = textContent('h1 > span') } else { presenceData.state = `${textContent( @@ -85,7 +87,7 @@ presence.on('UpdateData', async () => { case 'series': presenceData.details = 'Смотрит страницу сериала' if (!pathname.split('/')[3]) { - presenceData.state = textContent('.styles_title___itJ6 span') + presenceData.state = textContent('h1 > span') } else { presenceData.state = `${textContent( @@ -96,12 +98,10 @@ presence.on('UpdateData', async () => { case 'name': presenceData.details = 'Смотрит страницу человека' if (!pathname.split('/')[3]) { - presenceData.state = pageTitle(' — ')?.[0] + presenceData.state = textContent('h1') } else { - presenceData.state = `${pageTitle(' — ')?.[0]} – ${ - pageTitle(' — ')?.[1] - }` + presenceData.state = `${pageTitle(' — ')?.[0]} – ${pageTitle(' — ')?.[1]}` } break case 'media': @@ -173,72 +173,108 @@ presence.on('UpdateData', async () => { break case 'user': presenceData.details = 'Смотрит профиль' - presenceData.state = pageTitle(':')?.[1] - break - case 'mykp': - presenceData.details = 'Смотрит свой профиль' - presenceData.state = pageTitle(':')?.[1] + presenceData.state = pageTitle() as string break } break - case 'hd.kinopoisk.ru': + case 'hd.kinopoisk.ru': { + presenceData.largeImageKey = ActivityAssets.Movies; + (presenceData as PresenceData).type = ActivityType.Watching presenceData.details = 'В онлайн-кинотеатре' - presenceData.largeImageKey = ActivityAssets.Movies + presenceData.state = textContent('div[data-tid="HeaderContentComponent"] a[aria-current="page"]') - if (document.querySelector('.FilmContent_wrapper__EicQU')) { - presenceData.details = `Смотрит информацию ${textContent( - '.FilmContent_wrapper__EicQU .TabList_root__Kwcez button', - )?.toLowerCase()}` - presenceData.state = document - .querySelector('.FilmContent_wrapper__EicQU img') - ?.alt - .replace('Смотреть', '') - .replace('фильм', '') - presenceData.smallImageKey = Assets.Viewing - } + const filmContents = document.querySelectorAll('div[data-tid="FilmContent"]') + const videoPlayerWrapper = document.querySelector('div[data-tid="ContentPlayerBody"]') + ?? document.querySelector('div[data-tid="PlayerView"]') + + // Movies or Series previews + if (filmContents.length > 0) { + const lastFilmContent = filmContents[filmContents.length - 1] + + if (lastFilmContent) { + const buttonElement = lastFilmContent.querySelector('ul > li:first-child > button') + const typeContent = buttonElement?.textContent?.trim()?.toLowerCase() + const imgElement = lastFilmContent.querySelector('section h1 > img') + const imgAlt = imgElement?.alt + .replace('Смотреть', '') + .replace('фильм', '') + + if (typeContent) { + presenceData.details = `Смотрит ${typeContent}` + } + + if (imgAlt) { + presenceData.state = imgAlt + } - if (document.querySelector('.CrispySlideDown_fade_active__StELV')) { - presenceData.details = `Смотрит информацию ${textContent( - '.CrispySlideDown_fade_active__StELV .TabList_root__Kwcez button', - )?.toLowerCase()}` - presenceData.state = document - .querySelector( - '.CrispySlideDown_fade_active__StELV img', - ) - ?.alt - .replace('Смотреть', '') - .replace('фильм', '') - presenceData.smallImageKey = Assets.Viewing + presenceData.smallImageKey = Assets.Viewing + } } - if (document.querySelector('.PlayerManager_body__rOEVd')) { - if (document.querySelector('.Meta_image__CXoKi')) { - contentTitle = document.querySelector('.Meta_image__CXoKi')?.alt + if (videoPlayerWrapper) { + // Movies or Series + if (videoPlayerWrapper.querySelector('div[data-tid="Image"] > img')) { + contentType = 'фильм' + contentTitle = videoPlayerWrapper.querySelector('div[data-tid="Meta"] div[data-tid="Image"] > img')?.alt + } + if (videoPlayerWrapper.querySelector('div[data-tid="Meta"] > div:last-child > div')) { + contentType = 'сериал' + contentSerieTitle = videoPlayerWrapper.querySelector('div[data-tid="Meta"] > div:last-child > div')?.textContent.trim() } - if (document.querySelector('.Meta_subtitle__jnooi')) - contentSerieTitle = textContent('.Meta_subtitle__jnooi') + if (videoPlayerWrapper.querySelector('section[data-tid="ChannelPlayerMeta"]')) { // TV's + contentType = 'канал' + contentTitle = videoPlayerWrapper.querySelector('section[data-tid="ChannelPlayerMeta"] p[data-tid="Text"]')?.textContent.trim() + contentSerieTitle = videoPlayerWrapper.querySelector('section[data-tid="ChannelPlayerMeta"] > p')?.textContent?.trim() + if (!privacy) + presenceData.largeImageKey = videoPlayerWrapper.querySelector('.channel-icon')?.src ?? ActivityAssets.Movies + } + else if (videoPlayerWrapper.querySelector('section[data-tid="SportEventPlayerMeta"]')) { // Sport VOD's + contentTitle = videoPlayerWrapper.querySelector('section[data-tid="SportEventPlayerMeta"] > header')?.textContent?.trim() + contentSerieTitle = [...videoPlayerWrapper.querySelectorAll('section[data-tid="SportEventPlayerMeta"] div[data-tid="Scoreboard"] > span')].map(el => el.textContent.trim()).join(' - ') + } + else if (videoPlayerWrapper.querySelector('div[data-tid="HighlightPlayerMeta"]')) { // Sport Highlights + contentTitle = videoPlayerWrapper.querySelector('div[data-tid="HighlightPlayerMeta"] > h4')?.textContent?.trim() + contentSerieTitle = videoPlayerWrapper.querySelector('div[data-tid="HighlightPlayerMeta"] > h5')?.textContent.trim() + } + + if (contentTitle) { + if (contentType) + presenceData.details = `Смотрит ${contentType}` + if (!privacy) { + if (useActivityName) + presenceData.name = contentTitle - if (contentTitle || contentSerieTitle) { - presenceData.details = `Смотрит ${ - !privacy ? contentTitle : contentSerieTitle ? 'сериал' : 'фильм' - }` + presenceData.details = contentTitle + delete presenceData.state - const contentTimestamps = presence.getTimestampsfromMedia( - document.querySelector('video')!, - ); + if (contentSerieTitle) { + const [seasonNumber, episodeNumber, episodeTitle] = contentSerieTitle + .split(/[,.]\s*/) + .flatMap(x => Number.parseInt(x) || x) - [presenceData.startTimestamp, presenceData.endTimestamp] = presence.getTimestamps(contentTimestamps[0], contentTimestamps[1]) - } - else { - presenceData.details = 'Смотрит телеканал' - presenceData.startTimestamp = browsingTimestamp + if (seasonNumber && episodeNumber && episodeTitle) { + presenceData.details = useActivityName ? (episodeTitle as string) : contentTitle + presenceData.state = useActivityName + ? `Сезон ${seasonNumber}, Эпизод ${episodeNumber}` + : `S${seasonNumber}:E${episodeNumber} ${episodeTitle}` + } + else { + presenceData.state = contentSerieTitle + } + } + } + + if (videoPlayerWrapper.querySelector('video')) { + [presenceData.startTimestamp, presenceData.endTimestamp] = getTimestampsFromMedia( + videoPlayerWrapper.querySelector('video')!, + ) + } } - isPaused = document.querySelector('.styles_play__lWZwM') + isPaused = videoPlayerWrapper.querySelector('button[data-tid="PlayToggle"][aria-label="Смотреть"]') - presenceData.state = contentSerieTitle presenceData.smallImageKey = isPaused ? Assets.Pause : Assets.Play presenceData.smallImageText = isPaused ? strings.pause : strings.play @@ -248,13 +284,18 @@ presence.on('UpdateData', async () => { } } else { + contentType = null contentTitle = null contentSerieTitle = null } break + } } - if (privacy) + if (privacy) { delete presenceData.state + delete presenceData.startTimestamp + delete presenceData.endTimestamp + } presence.setActivity(presenceData) })