From f7662c0f8cfe5792586cb0180780e872b430dfe7 Mon Sep 17 00:00:00 2001 From: SashaXser <24498484+SashaXser@users.noreply.github.com> Date: Wed, 5 Jun 2024 06:34:06 +0400 Subject: [PATCH 1/2] =?UTF-8?q?=D0=94=D0=BE=D1=80=D0=B0=D0=B1=D0=BE=D1=82?= =?UTF-8?q?=D0=BA=D0=B0=20VideoObserver=20=D0=B8=20=D0=BF=D1=80=D0=BE?= =?UTF-8?q?=D1=87=D0=B5=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. VideoObserver ожидает загрузки видео перед проигрыванием, исправляя проблему с некорректной продолжительностью видео 2. Временное решение проблемы с непреднамеренным добавлением избыточных видео на bilibili. 3. Добавлена функция для определения музыкальных видео --- changelog.md | 10 +++ src/config/alternativeUrls.js | 4 +- src/config/sites.js | 49 +++++----- src/index.js | 13 ++- src/utils/VideoObserver.js | 32 ++++++- src/utils/utils.js | 164 +++++++++++++++++++--------------- src/utils/weverseUtils.js | 130 ++++++++++++++------------- src/utils/youtubeUtils.js | 86 ++++++++++++++++++ 8 files changed, 325 insertions(+), 163 deletions(-) diff --git a/changelog.md b/changelog.md index 37024819..d8f0e5f4 100644 --- a/changelog.md +++ b/changelog.md @@ -7,10 +7,20 @@ # 1.x +- Добавлена поддержка bitchute embed +- Обновлена логика получения айди для clips.twitch.tv. Добавлена поддержка не только встроенных (embed) клипов +- Исправлено неправильное формирование параметра запроса для weverse +- Для dailymotion и yadisk итоговые ссылки заменены на короткие варианты +- Исправлен дополнительный селектор для twitter +- Исправлен селектор для bitchute +- Исправлен селектор для facebook +- Proxytok переименован в Proxitok +- Стандартный формат загружаемых субтитров изменен на srt (#644) - Фикс формирования строки с оставшимся временем перевода (#643) - Добавлены часы в иконку при ожидание перевода - Исправлен баг из-за которого реклама считалась за отдельные видео (#642) - Фикс отображения кнопки для youku (#636) +- Другие мелкие фиксы # 1.5.3.1 diff --git a/src/config/alternativeUrls.js b/src/config/alternativeUrls.js index f42172f6..9a2d270e 100644 --- a/src/config/alternativeUrls.js +++ b/src/config/alternativeUrls.js @@ -49,7 +49,7 @@ const sitesPiped = [ "piped.frontendfriendly.xyz", ]; -const sitesProxyTok = [ +const sitesProxiTok = [ "proxitok.pabloferreiro.es", "proxitok.pussthecat.org", "tok.habedieeh.re", @@ -78,4 +78,4 @@ const sitesPeertube = [ "peertube.su", ]; -export { sitesInvidious, sitesPiped, sitesProxyTok, sitesPeertube }; +export { sitesInvidious, sitesPiped, sitesProxiTok, sitesPeertube }; diff --git a/src/config/sites.js b/src/config/sites.js index 411ef8ed..bdce5c43 100644 --- a/src/config/sites.js +++ b/src/config/sites.js @@ -1,7 +1,7 @@ import { sitesInvidious, sitesPiped, - sitesProxyTok, + sitesProxiTok, sitesPeertube, } from "./alternativeUrls.js"; @@ -34,9 +34,9 @@ const sites = () => { selector: null, }, { - host: "proxytok", + host: "proxitok", url: "https://www.tiktok.com/", - match: sitesProxyTok, + match: sitesProxiTok, selector: ".column.has-text-centered", }, { @@ -99,7 +99,7 @@ const sites = () => { }, { host: "ok.ru", - url: "https://ok.ru/", + url: "https://ok.ru/video/", match: /^ok.ru$/, selector: ".html5-vpl_vid", }, @@ -119,7 +119,7 @@ const sites = () => { host: "bitchute", url: "https://www.bitchute.com/video/", match: /^(www.)?bitchute.com$/, - selector: "#player", + selector: "div#player", // video#player also using for initializing player }, { host: "rutube", @@ -138,15 +138,16 @@ const sites = () => { host: "bilibili", url: "https://www.bilibili.com/video/", match: /^(www|m|player).bilibili.com$/, - selector: ".bpx-player-video-wrap", - }, - { - additionalData: "old", // /blackboard/webplayer/embed-old.html - host: "bilibili", - url: "https://www.bilibili.com/video/", - match: /^(www|m).bilibili.com$/, - selector: null, + selector: "#bilibili-player", }, + // Добавляет лишние видео в обработчик + // { + // additionalData: "old", // /blackboard/webplayer/embed-old.html + // host: "bilibili", + // url: "https://www.bilibili.com/video/", + // match: /^(www|m).bilibili.com$/, + // selector: null, + // }, { host: "twitter", url: "https://twitter.com/i/status/", @@ -169,7 +170,7 @@ const sites = () => { { // ONLY IF YOU LOGINED TO UDEMY /course/NAME/learn/lecture/XXXX host: "udemy", - url: "https://www.udemy.com", + url: "https://www.udemy.com/", match: /udemy.com$/, selector: 'div[data-purpose="curriculum-item-viewer-content"] > section > div > div > div > div:nth-of-type(2)', @@ -190,7 +191,7 @@ const sites = () => { }, { host: "rumble", - url: "https://rumble.com", // <-- there should be no slash because we take the whole pathname + url: "https://rumble.com/", match: /^rumble.com$/, selector: "#videoPlayer > .videoPlayer-Rumble-cls > div", }, @@ -202,13 +203,13 @@ const sites = () => { }, { host: "peertube", - url: "tube.shanti.cafe", // This is a stub. The present value is set using window.location.origin. Check "src/index.js:videoObserver.onVideoAdded.addListener" to get more info + url: "stub", // This is a stub. The present value is set using window.location.origin. Check "src/index.js:videoObserver.onVideoAdded.addListener" to get more info match: sitesPeertube, selector: ".vjs-v7", }, { host: "dailymotion", - url: "https://www.dailymotion.com/video/", // This is a stub. The present value is set using window.location.origin. Check "src/index.js:videoObserver.onVideoAdded.addListener" to get more info + url: "https://dai.ly/", // This is a stub. The present value is set using window.location.origin. Check "src/index.js:videoObserver.onVideoAdded.addListener" to get more info match: /^geo.dailymotion.com$/, selector: ".player", }, @@ -220,7 +221,7 @@ const sites = () => { }, { host: "yandexdisk", - url: "https://disk.yandex.ru/i/", + url: "https://yadi.sk/i/", match: /^disk.yandex.ru$/, selector: "yaplayertag > div:nth-of-type(1)", }, @@ -244,15 +245,15 @@ const sites = () => { }, { host: "facebook", - url: "https://facebook.com", // <-- there should be no slash because we take the whole pathname + url: "https://facebook.com/", match: (url) => url.host.includes("facebook.com") && url.pathname.includes("/videos/"), - selector: 'div[data-pagelet="WatchPermalinkVideo"]', + selector: 'div[role="main"] div[data-pagelet$="video" i]', }, { additionalData: "reels", host: "facebook", - url: "https://facebook.com", // <-- there should be no slash because we take the whole pathname + url: "https://facebook.com/", match: (url) => url.host.includes("facebook.com") && url.pathname.includes("/reel/"), selector: 'div[role="main"]', @@ -270,9 +271,9 @@ const sites = () => { selector: ".ng-video-player", }, { - // TODO: Добавить поддержку tips и платных курсов + // TODO: Добавить поддержку tips (сделать через m3u8 т.к. обычная ссылка не принимается) и платных курсов host: "egghead", - url: "https://egghead.io", + url: "https://egghead.io/", match: /^egghead.io$/, selector: ".cueplayer-react-video-holder", }, @@ -284,7 +285,7 @@ const sites = () => { }, { host: "directlink", - url: "any", // This is a stub. The present value is set using window.location.origin. Check "src/index.js:videoObserver.onVideoAdded.addListener" to get more info + url: "stub", // This is a stub. The present value is set using window.location.origin. Check "src/index.js:videoObserver.onVideoAdded.addListener" to get more info match: (url) => /([^.]+).mp4/.test(url.pathname), selector: null, }, diff --git a/src/index.js b/src/index.js index d4bff1de..51e911d2 100644 --- a/src/index.js +++ b/src/index.js @@ -59,6 +59,8 @@ import { const browserInfo = Bowser.getParser(window.navigator.userAgent).getResult(); +const dontTranslateMusic = false; // Пока не придумал как стоит реализовать + const sitesChromiumBlocked = [...sitesInvidious, ...sitesPiped]; const videoLipSyncEvents = [ @@ -269,6 +271,13 @@ class VideoHandler { } async autoTranslate() { + if ( + this.site.host === "youtube" && + dontTranslateMusic && + youtubeUtils.isMusic() + ) { + return; + } if ( !( this.firstPlay && @@ -1390,7 +1399,7 @@ class VideoHandler { this.container.style.height = "100%"; } - addExtraEventListener(this.video, "loadeddata", async () => { + addExtraEventListener(this.video, "canplaythrough", async () => { // Временное решение if (this.site.host === "rutube" && this.video.src) { return; @@ -1894,7 +1903,7 @@ class VideoHandler { switch (this.site.host) { case "twitter": document - .querySelector('div[data-testid="app-bar-back"][role="button"]') + .querySelector('button[data-testid="app-bar-back"][role="button"]') .addEventListener("click", this.stopTranslationBound); break; case "invidious": diff --git a/src/utils/VideoObserver.js b/src/utils/VideoObserver.js index 5947c40c..167152c0 100644 --- a/src/utils/VideoObserver.js +++ b/src/utils/VideoObserver.js @@ -15,6 +15,22 @@ function filterVideoNodes(nodes) { }); } +function isVideoReady(video) { + return video.readyState >= 3; +} + +function waitForVideoReady(video, callback) { + function checkVideoState() { + if (isVideoReady(video)) { + callback(video); + } else { + requestAnimationFrame(checkVideoState); + } + } + + checkVideoState(); +} + export class VideoObserver { constructor() { this.onVideoAdded = new EventImpl(); @@ -28,7 +44,7 @@ export class VideoObserver { const addedNodes = filterVideoNodes(mutation.addedNodes); for (let j = 0; j < addedNodes.length; j++) { - this.handleVideoAdded(addedNodes[j]); + this.checkAndHandleVideo(addedNodes[j]); } const removedNodes = filterVideoNodes(mutation.removedNodes); @@ -40,6 +56,7 @@ export class VideoObserver { { timeout: 1000 }, ); }); + this.videoCache = new Set(); } enable() { @@ -49,7 +66,7 @@ export class VideoObserver { }); const videos = document.querySelectorAll("video"); for (let i = 0; i < videos.length; i++) { - this.handleVideoAdded(videos[i]); + this.checkAndHandleVideo(videos[i]); } } @@ -57,6 +74,17 @@ export class VideoObserver { this.observer.disconnect(); } + checkAndHandleVideo(video) { + if (this.videoCache.has(video)) { + return; + } + + waitForVideoReady(video, (readyVideo) => { + this.handleVideoAdded(readyVideo); + this.videoCache.add(readyVideo); + }); + } + handleVideoAdded = (video) => { this.onVideoAdded.dispatch(video); }; diff --git a/src/utils/utils.js b/src/utils/utils.js index 5f10814a..d6e51817 100644 --- a/src/utils/utils.js +++ b/src/utils/utils.js @@ -49,50 +49,74 @@ const getVideoId = (service, video) => { url.searchParams.get("v") ); } - case "vk": - if (url.pathname.match(/^\/video-?[0-9]{8,9}_[0-9]{9}$/)) { - return url.pathname.match(/^\/video-?[0-9]{8,9}_[0-9]{9}$/)[0].slice(1); - } else if (url.searchParams.get("z")) { - return url.searchParams.get("z").split("/")[0]; - } else if (url.searchParams.get("oid") && url.searchParams.get("id")) { - return `video-${Math.abs( - url.searchParams.get("oid"), - )}_${url.searchParams.get("id")}`; - } else { - return false; + case "vk": { + const pathID = url.pathname.match(/^\/video-?[0-9]{8,9}_[0-9]{9}$/); + const paramZ = url.searchParams.get("z"); + const paramOID = url.searchParams.get("oid"); + const paramID = url.searchParams.get("id"); + if (pathID) { + return pathID[0].slice(1); + } else if (paramZ) { + return paramZ.split("/")[0]; + } else if (paramOID && paramID) { + return `video-${Math.abs(parseInt(paramOID))}_${paramID}`; } + + return null; + } case "nine_gag": case "9gag": case "gag": return url.pathname.match(/gag\/([^/]+)/)?.[1]; - case "twitch": - if (/^m\.twitch\.tv$/.test(window.location.hostname)) { - const linkUrl = document.head.querySelector('link[rel="canonical"]'); - return ( - linkUrl?.href.match(/videos\/([^/]+)/)?.[0] || url.pathname.slice(1) - ); - } else if (/^player\.twitch\.tv$/.test(window.location.hostname)) { + case "twitch": { + const clipPath = url.pathname.match(/([^/]+)\/(?:clip)\/([^/]+)/); + if (/^m\.twitch\.tv$/.test(url.hostname)) { + return url.href.match(/videos\/([^/]+)/)?.[0] || url.pathname.slice(1); + } else if (/^player\.twitch\.tv$/.test(url.hostname)) { return `videos/${url.searchParams.get("video")}`; - } else if (/^clips\.twitch\.tv$/.test(window.location.hostname)) { - // get link to twitch channel (ex.: https://www.twitch.tv/xqc) - const channelLink = document.querySelector( - ".tw-link[data-test-selector='stream-info-card-component__stream-avatar-link']", + } else if (/^clips\.twitch\.tv$/.test(url.hostname)) { + // https://clips.twitch.tv/clipId + const schema = document.querySelector( + "script[type='application/ld+json']", ); - if (!channelLink) { - return false; + const pathname = url.pathname.slice(1); + if (!schema) { + // иногда из-за не прогрузов твича это не работает, но пусть лучше будет (можно переделать все в async и ждать элемента, но нужно ли это ради 1 сайта) + // ссылки вида https://clips.twitch.tv/embed?clip=clipId грузятся нормально + const isEmbed = pathname === "embed"; + const channelLink = document.querySelector( + isEmbed + ? ".tw-link[data-test-selector='stream-info-card-component__stream-avatar-link']" + : ".clips-player a:not([class])", + ); + + if (!channelLink) { + return; + } + + const channelName = channelLink.href.replace( + "https://www.twitch.tv/", + "", + ); + + return `${channelName}/clip/${isEmbed ? url.searchParams.get("clip") : pathname}`; } - const channelName = channelLink.href.replace( - "https://www.twitch.tv/", - "", - ); - return `${channelName}/clip/${url.searchParams.get("clip")}`; - } else if (url.pathname.match(/([^/]+)\/(?:clip)\/([^/]+)/)) { - return url.pathname.match(/([^/]+)\/(?:clip)\/([^/]+)/)[0]; - } else { - return url.pathname.match(/(?:videos)\/([^/]+)/)?.[0]; + const schemaJSON = JSON.parse(schema.innerText); + const channelLink = schemaJSON["@graph"].find( + (obj) => obj["@type"] === "VideoObject", + )?.creator.url; + + const channelName = channelLink.replace("https://www.twitch.tv/", ""); + return `${channelName}/clip/${pathname}`; + } else if (clipPath) { + return clipPath[0]; } - case "proxytok": + + return url.pathname.match(/(?:videos)\/([^/]+)/)?.[0]; + } + + case "proxitok": return url.pathname.match(/([^/]+)\/video\/([^/]+)/)?.[0]; case "tiktok": { let id = url.pathname.match(/([^/]+)\/video\/([^/]+)/)?.[0]; @@ -132,49 +156,49 @@ const getVideoId = (service, video) => { case "twitter": return url.pathname.match(/status\/([^/]+)/)?.[1]; case "udemy": - return url.pathname; case "rumble": - return url.pathname; case "facebook": - return url.pathname; + return url.pathname.slice(1); case "rutube": return url.pathname.match(/(?:video|embed)\/([^/]+)/)?.[1]; case "coub": - if (url.pathname.includes("/view")) { - return url.pathname.match(/view\/([^/]+)/)?.[1]; - } else if (url.pathname.includes("/embed")) { - return url.pathname.match(/embed\/([^/]+)/)?.[1]; - } else { - return document.querySelector(".coub.active")?.dataset?.permalink; - } + return ( + url.pathname.match(/(?:view|embed)\/([^/]+)/)?.[1] || + document.querySelector(".coub.active")?.dataset?.permalink + ); case "bilibili": { const bvid = url.searchParams.get("bvid"); if (bvid) { return bvid; - } else { - let vid = url.pathname.match(/video\/([^/]+)/)?.[1]; - if (vid && url.search && url.searchParams.get("p") !== null) { - vid += `/?p=${url.searchParams.get("p")}`; - } - return vid; } + + let vid = url.pathname.match(/video\/([^/]+)/)?.[1]; + if (vid && url.searchParams.get("p") !== null) { + vid += `/?p=${url.searchParams.get("p")}`; + } + + return vid; } - case "mail_ru": - if (url.pathname.startsWith("/v/") || url.pathname.startsWith("/mail/")) { - return url.pathname; - } else if (url.pathname.match(/video\/embed\/([^/]+)/)) { - const referer = document.querySelector( - ".b-video-controls__mymail-link", - ); - if (!referer) { - return false; - } + case "mail_ru": { + const pathname = url.pathname; + if (pathname.startsWith("/v/") || pathname.startsWith("/mail/")) { + return pathname.slice(1); + } - return referer?.href.split("my.mail.ru")?.[1]; + const videoId = pathname.match(/video\/embed\/([^/]+)/)?.[1]; + if (!videoId) { + return null; } - return false; + + const referer = document.querySelector(".b-video-controls__mymail-link"); + if (!referer) { + return false; + } + + return referer?.href.split("my.mail.ru")?.[1]; + } case "bitchute": - return url.pathname.match(/video\/([^/]+)/)?.[1]; + return url.pathname.match(/(video|embed)\/([^/]+)/)?.[2]; case "coursera": // ! LINK SHOULD BE LIKE THIS https://www.coursera.org/learn/learning-how-to-learn/lecture/75EsZ // return url.pathname.match(/lecture\/([^/]+)\/([^/]+)/)?.[1]; // <--- COURSE PREVIEW @@ -199,18 +223,14 @@ const getVideoId = (service, video) => { } } case "trovo": { - if (!url.pathname.startsWith("/s/")) { - return false; - } - const vid = url.searchParams.get("vid"); if (!vid) { - return false; + return null; } const path = url.pathname.match(/([^/]+)\/([\d]+)/)?.[0]; if (!path) { - return false; + return null; } return `${path}?vid=${vid}`; @@ -222,7 +242,7 @@ const getVideoId = (service, video) => { return courseId ? courseId + url.search : false; } case "ok.ru": { - return url.pathname.match(/\/video\/(\d+)/)?.[0]; + return url.pathname.match(/\/video\/(\d+)/)?.[1]; } case "googledrive": return url.searchParams.get("docid"); @@ -233,7 +253,7 @@ const getVideoId = (service, video) => { case "newgrounds": return url.pathname.match(/([^/]+)\/(view)\/([^/]+)/)?.[0]; case "egghead": - return url.pathname; + return url.pathname.slice(1); case "youku": return url.pathname.match(/v_show\/id_[\w=]+/)?.[0]; // case "sibnet": { @@ -331,7 +351,7 @@ function cleanText(title, description) { .replace(/[^\p{L}\s]/gu, " ") .trim() .replace(/\s+/g, " ") - .slice(0, 1000); + .slice(0, 450); } async function GM_fetch(url, opt = {}) { diff --git a/src/utils/weverseUtils.js b/src/utils/weverseUtils.js index 889d0dbf..a0013392 100644 --- a/src/utils/weverseUtils.js +++ b/src/utils/weverseUtils.js @@ -3,14 +3,14 @@ import { getHmacSha1 } from "./crypto.js"; const API_ORIGIN = "https://global.apis.naver.com/weverse/wevweb"; // find as REACT_APP_API_GW_ORIGIN in main..js const API_APP_ID = "be4d79eb8fc7bd008ee82c8ec4ff6fd4"; // find as REACT_APP_API_APP_ID in main..js -const API_HMAC_KEY = "1b9cb6378d959b45714bec49971ade22e6e24e42"; // find as c.active near `createHmac("sha1"...` in main..js +const API_HMAC_KEY = "1b9cb6378d959b45714bec49971ade22e6e24e42"; // find as c.active near `createHmac('sha1'...` in main..js async function createHash(pathname) { // pathname example: /post/v1.0/post-3-142049908/preview?fieldSet=postForPreview... const timestamp = Date.now(); - let salt = pathname.substring(0, Math.min(255, pathname.length)) + timestamp; // example salt is /video/v1.1/vod/67134/inKey?gcc=RU&appId=be4d79eb8fc7bd008ee82c8ec4ff6fd4&language=en&os=WEB&platform=WEB&wpf=pc1707527163588 + let salt = pathname.substring(0, Math.min(255, pathname.length)) + timestamp; const sign = await getHmacSha1(API_HMAC_KEY, salt); @@ -40,16 +40,16 @@ async function getVideoPreview(postId) { const hash = await createHash(pathname); - return await fetch(API_ORIGIN + pathname + "&" + new URLSearchParams(hash)) - .then((res) => res.json()) - .catch((err) => { - console.error(err); - return { - extension: { - video: {}, - }, - }; - }); + try { + const res = await fetch( + API_ORIGIN + pathname + "&" + new URLSearchParams(hash), + ); + + return await res.json(); + } catch (err) { + console.error(err); + return false; + } } async function getVideoInKey(videoId) { @@ -61,49 +61,51 @@ async function getVideoInKey(videoId) { }); // ! DON'T EDIT ME const hash = await createHash(pathname); - return await fetch(API_ORIGIN + pathname + "&" + new URLSearchParams(hash), { - method: "POST", - }) - .then((res) => res.json()) - .catch((err) => { - console.error(err); - return {}; - }); + try { + const res = await fetch( + API_ORIGIN + pathname + "&" + new URLSearchParams(hash), + { + method: "POST", + }, + ); + + return await res.json(); + } catch (err) { + console.error(err); + return false; + } } async function getVideoInfo(infraVideoId, inkey, serviceId) { const timestamp = Date.now(); - return await fetch( - `https://global.apis.naver.com/rmcnmv/rmcnmv/vod/play/v2.0/${infraVideoId}?` + - new URLSearchParams({ - key: inkey, - sid: serviceId, - // pid: "863c411f-fbf0-4b67-868a-ef54427e5316", // возможно не нужен - nonce: timestamp, - devt: "html5_pc", - prv: "N", - aup: "N", - stpb: "N", - cpl: "en", - env: "prod", - lc: "en", - adi: [ - { - adSystem: null, - }, - ], - adu: "/", - }), - ) - .then((res) => res.json()) - .catch((err) => { - console.error(err); - return { - videos: { - list: [], - }, - }; - }); + try { + const res = await fetch( + `https://global.apis.naver.com/rmcnmv/rmcnmv/vod/play/v2.0/${infraVideoId}?` + + new URLSearchParams({ + key: inkey, + sid: serviceId, + nonce: timestamp, + devt: "html5_pc", + prv: "N", + aup: "N", + stpb: "N", + cpl: "en", + env: "prod", + lc: "en", + adi: JSON.stringify([ + { + adSystem: null, + }, + ]), + adu: "/", + }), + ); + + return await res.json(); + } catch (err) { + console.error(err); + return false; + } } function extractVideoInfo(videoList) { @@ -121,32 +123,38 @@ async function getVideoData() { )?.[3]; const videoPreview = await getVideoPreview(postId); + if (!videoPreview) { + return undefined; + } + debug.log("weverse video preview data:", videoPreview); const { videoId, serviceId, infraVideoId } = videoPreview.extension.video; - if (!(videoId && serviceId && infraVideoId)) { return false; } - const { inKey } = await getVideoInKey(videoId); + const inkeyData = await getVideoInKey(videoId); debug.log("weverse video inKey data:", videoPreview); - if (!inKey) { + if (!inkeyData) { return false; } - const videoData = await getVideoInfo(infraVideoId, inKey, serviceId); - debug.log("weverse video data:", videoData); + const videoInfo = await getVideoInfo( + infraVideoId, + inkeyData.inKey, + serviceId, + ); + debug.log("weverse video info:", videoInfo); - const videoSource = extractVideoInfo(videoData.videos.list); - if (!videoSource) { + const videoItem = extractVideoInfo(videoInfo.videos.list); + if (!videoItem) { return false; } - const { source: url, duration } = videoSource; return { - url, - duration, + url: videoItem.source, + duration: videoItem.duration, }; } diff --git a/src/utils/youtubeUtils.js b/src/utils/youtubeUtils.js index 7adc1471..5e50b158 100644 --- a/src/utils/youtubeUtils.js +++ b/src/utils/youtubeUtils.js @@ -99,6 +99,91 @@ function videoSeek(video, time) { video.currentTime = finalTime; } +function isMusic() { + // Нужно доработать логику + const channelName = getPlayerData().author, + titleStr = getPlayerData().title.toUpperCase(), + titleWordsList = titleStr.match(/\w+/g), + playerData = document.body.querySelector("ytd-watch-flexy")?.playerData; + + return ( + [ + titleStr, + document.URL, + channelName, + playerData?.microformat?.playerMicroformatRenderer.category, + playerData?.title, + ].some((i) => i?.toUpperCase().includes("MUSIC")) || + document.body.querySelector( + "#upload-info #channel-name .badge-style-type-verified-artist", + ) || + (channelName && + /(VEVO|Topic|Records|RECORDS|Recordings|AMV)$/.test(channelName)) || + (channelName && + /(MUSIC|ROCK|SOUNDS|SONGS)/.test(channelName.toUpperCase())) || + (titleWordsList?.length && + [ + "🎵", + "♫", + "SONG", + "SONGS", + "SOUNDTRACK", + "LYRIC", + "LYRICS", + "AMBIENT", + "MIX", + "VEVO", + "CLIP", + "KARAOKE", + "OPENING", + "COVER", + "COVERED", + "VOCAL", + "INSTRUMENTAL", + "ORCHESTRAL", + "DUBSTEP", + "DJ", + "DNB", + "BASS", + "BEAT", + "ALBUM", + "PLAYLIST", + "DUBSTEP", + "CHILL", + "RELAX", + "CLASSIC", + "CINEMATIC", + ].some((i) => titleWordsList.includes(i))) || + [ + "OFFICIAL VIDEO", + "OFFICIAL AUDIO", + "FEAT.", + "FT.", + "LIVE RADIO", + "DANCE VER", + "HIP HOP", + "ROCK N ROLL", + "HOUR VER", + "HOURS VER", + "INTRO THEME", + ].some((i) => titleStr.includes(i)) || + (titleWordsList?.length && + [ + "OP", + "ED", + "MV", + "OST", + "NCS", + "BGM", + "EDM", + "GMV", + "AMV", + "MMD", + "MAD", + ].some((i) => titleWordsList.includes(i))) + ); +} + function getSubtitles() { const response = getPlayerResponse(); let captionTracks = @@ -162,4 +247,5 @@ export default { setVideoVolume, videoSeek, isMuted, + isMusic, }; From 18f76065c5115dc3e731b59f48a23ab4f9506bb1 Mon Sep 17 00:00:00 2001 From: SashaXser <24498484+SashaXser@users.noreply.github.com> Date: Wed, 5 Jun 2024 06:44:57 +0400 Subject: [PATCH 2/2] Update VideoObserver.js --- src/utils/VideoObserver.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/utils/VideoObserver.js b/src/utils/VideoObserver.js index 167152c0..9807d393 100644 --- a/src/utils/VideoObserver.js +++ b/src/utils/VideoObserver.js @@ -75,10 +75,6 @@ export class VideoObserver { } checkAndHandleVideo(video) { - if (this.videoCache.has(video)) { - return; - } - waitForVideoReady(video, (readyVideo) => { this.handleVideoAdded(readyVideo); this.videoCache.add(readyVideo);