diff options
author | Ajay Ramachandran <[email protected]> | 2022-09-02 15:20:40 -0400 |
---|---|---|
committer | GitHub <[email protected]> | 2022-09-02 15:20:40 -0400 |
commit | 4f0f8655f4fccb71034490d44b3d409e5f01e224 (patch) | |
tree | 44020c8209972eaaf08b6030a185197e8ad5b312 | |
parent | 668f6856d1c7a4ffcb83fa1ae1cdd888996d19af (diff) | |
parent | 29c6151fe3cde9fa5d4ad26f36613f12eba660a8 (diff) | |
download | SponsorBlock-4f0f8655f4fccb71034490d44b3d409e5f01e224.tar.gz SponsorBlock-4f0f8655f4fccb71034490d44b3d409e5f01e224.zip |
Merge pull request #1425 from mchangrh/contentScriptRebase5.0
rebase document script out of videoInfo
-rw-r--r-- | manifest/manifest.json | 3 | ||||
-rw-r--r-- | src/content.ts | 94 | ||||
-rw-r--r-- | src/document.ts | 80 | ||||
-rw-r--r-- | src/types.ts | 8 | ||||
-rw-r--r-- | webpack/webpack.common.js | 7 |
5 files changed, 167 insertions, 25 deletions
diff --git a/manifest/manifest.json b/manifest/manifest.json index a1b15423..5276e5eb 100644 --- a/manifest/manifest.json +++ b/manifest/manifest.json @@ -69,7 +69,8 @@ "icons/PlayerInfoIconSponsorBlocker.svg", "icons/PlayerDeleteIconSponsorBlocker.svg", "popup.html", - "content.css" + "content.css", + "js/document.js" ], "permissions": [ "storage", diff --git a/src/content.ts b/src/content.ts index cb2d3964..0aa6e64d 100644 --- a/src/content.ts +++ b/src/content.ts @@ -18,6 +18,7 @@ import { ToggleSkippable, VideoID, VideoInfo, + PageType } from "./types"; import Utils from "./utils"; import PreviewBar, { PreviewBarSegment } from "./js-components/previewBar"; @@ -55,6 +56,10 @@ let activeSkipKeybindElement: ToggleSkippable = null; // JSON video info let videoInfo: VideoInfo = null; +// Page Type - browse/watch etc... +let pageType: PageType; +// if video is live or premiere +let isLivePremiere: boolean // The channel this video is about let channelIDInfo: ChannelIDInfo; // Locked Categories in this tab, like: ["sponsor","intro","outro"] @@ -90,7 +95,7 @@ let onInvidious: boolean; let onMobileYouTube: boolean; //the video id of the last preview bar update -let lastPreviewBarUpdate; +let lastPreviewBarUpdate: VideoID; // Is the video currently being switched let switchingVideos = null; @@ -334,12 +339,14 @@ function resetValues() { sponsorSkipped = []; videoInfo = null; + pageType = null; channelWhitelisted = false; channelIDInfo = { status: ChannelIDStatus.Fetching, id: null }; lockedCategories = []; + isLivePremiere = false; //empty the preview bar if (previewBar !== null) { @@ -1157,6 +1164,8 @@ function startSkipScheduleCheckingForStartSponsors() { function getYouTubeVideoID(document: Document, url?: string): string | boolean { url ||= document.URL; + // pageType shortcut + if (pageType === PageType.Channel) return getYouTubeVideoIDFromDocument() // clips should never skip, going from clip to full video has no indications. if (url.includes("youtube.com/clip/")) return false; // skip to document and don't hide if on /embed/ @@ -1164,17 +1173,19 @@ function getYouTubeVideoID(document: Document, url?: string): string | boolean { // skip to URL if matches youtube watch or invidious or matches youtube pattern if ((!url.includes("youtube.com")) || url.includes("/watch") || url.includes("/shorts/") || url.includes("playlist")) return getYouTubeVideoIDFromURL(url); // skip to document if matches pattern - if (url.includes("/channel/") || url.includes("/user/") || url.includes("/c/")) return getYouTubeVideoIDFromDocument(); + if (url.includes("/channel/") || url.includes("/user/") || url.includes("/c/")) return getYouTubeVideoIDFromDocument(true, PageType.Channel); // not sure, try URL then document return getYouTubeVideoIDFromURL(url) || getYouTubeVideoIDFromDocument(false); } -function getYouTubeVideoIDFromDocument(hideIcon = true): string | boolean { +function getYouTubeVideoIDFromDocument(hideIcon = true, pageHint = PageType.Watch): string | boolean { // get ID from document (channel trailer / embedded playlist) const element = video?.parentElement?.parentElement?.querySelector("a.ytp-title-link[data-sessionlink='feature=player-title']"); const videoURL = element?.getAttribute("href"); if (videoURL) { onInvidious = hideIcon; + // if href found, hint was correct + pageType = pageHint; return getYouTubeVideoIDFromURL(videoURL); } else { return false; @@ -1294,25 +1305,29 @@ function updatePreviewBar(): void { async function whitelistCheck() { const whitelistedChannels = Config.config.whitelistedChannels; - const getChannelID = () => - (document.querySelector("a.ytd-video-owner-renderer") // YouTube - ?? document.querySelector("a.ytp-title-channel-logo") // YouTube Embed - ?? document.querySelector(".channel-profile #channel-name")?.parentElement.parentElement // Invidious - ?? document.querySelector("a.slim-owner-icon-and-title")) // Mobile YouTube - ?.getAttribute("href")?.match(/\/(?:channel|c|user)\/(UC[a-zA-Z0-9_-]{22}|[a-zA-Z0-9_-]+)/)?.[1]; - try { - await utils.wait(() => !!getChannelID(), 6000, 20); + await utils.wait(() => channelIDInfo.status === ChannelIDStatus.Found, 6000, 20); - channelIDInfo = { - status: ChannelIDStatus.Found, - id: getChannelID().match(/^\/?([^\s/]+)/)[0] - }; + // If found, continue on, it was set by the listener } catch (e) { - channelIDInfo = { - status: ChannelIDStatus.Failed, - id: null - }; + // Try fallback + const channelIDFallback = (document.querySelector("a.ytd-video-owner-renderer") // YouTube + ?? document.querySelector("a.ytp-title-channel-logo") // YouTube Embed + ?? document.querySelector(".channel-profile #channel-name")?.parentElement.parentElement // Invidious + ?? document.querySelector("a.slim-owner-icon-and-title")) // Mobile YouTube + ?.getAttribute("href")?.match(/\/(?:channel|c|user)\/(UC[a-zA-Z0-9_-]{22}|[a-zA-Z0-9_-]+)/)?.[1]; + + if (channelIDFallback) { + channelIDInfo = { + status: ChannelIDStatus.Found, + id: channelIDFallback + }; + } else { + channelIDInfo = { + status: ChannelIDStatus.Failed, + id: null + }; + } } //see if this is a whitelisted channel @@ -1731,7 +1746,7 @@ function updateEditButtonsOnPlayer(): void { // Don't try to update the buttons if we aren't on a YouTube video page if (!sponsorVideoID || onMobileYouTube) return; - const buttonsEnabled = !Config.config.hideVideoPlayerControls && !onInvidious; + const buttonsEnabled = !(Config.config.hideVideoPlayerControls || onInvidious); let creatingSegment = false; let submitButtonVisible = false; @@ -2069,7 +2084,7 @@ function submitSponsorTimes() { //called after all the checks have been made that it's okay to do so async function sendSubmitMessage() { // Block if submitting on a running livestream or premiere - if (isVisible(document.querySelector(".ytp-live-badge"))) { + if (isLivePremiere || isVisible(document.querySelector(".ytp-live-badge"))) { alert(chrome.i18n.getMessage("liveOrPremiere")); return; } @@ -2182,6 +2197,36 @@ function getSegmentsMessage(sponsorTimes: SponsorTime[]): string { return sponsorTimesMessage; } +function windowListenerHandler(event: MessageEvent): void { + const data = event.data; + const dataType = data.type; + if (data.source !== "sponsorblock") return; + + if (dataType === "navigation") { + sponsorVideoID = data.videoID; + pageType = data.pageType; + + if (data.channelID) { + channelIDInfo = { + id: data.channelID, + status: ChannelIDStatus.Found + }; + } + } else if (dataType === "ad") { + if (isAdPlaying != data.playing) { + isAdPlaying = data.playing + updatePreviewBar(); + updateVisibilityOfPlayerControlsButton(); + } + } else if (dataType === "data") { + if (data.video !== sponsorVideoID) { + sponsorVideoID = data.videoID; + videoIDChange(sponsorVideoID); + } + isLivePremiere = data.isLive || data.isPremiere + } +} + function updateActiveSegment(currentTime: number): void { previewBar?.updateChapterText(sponsorTimes, sponsorTimesSubmitting, currentTime); chrome.runtime.sendMessage({ @@ -2227,7 +2272,14 @@ function addPageListeners(): void { } }; + // inject into document + const docScript = document.createElement("script"); + docScript.src = chrome.runtime.getURL("js/document.js"); + (document.head || document.documentElement).appendChild(docScript); + + document.addEventListener("yt-navigate-start", resetValues); document.addEventListener("yt-navigate-finish", refreshListners); + window.addEventListener("message", windowListenerHandler); } function addHotkeyListener(): void { diff --git a/src/document.ts b/src/document.ts new file mode 100644 index 00000000..d537dd0e --- /dev/null +++ b/src/document.ts @@ -0,0 +1,80 @@ +/* + Content script are run in an isolated DOM so it is not possible to access some key details that are sanitized when passed cross-dom + This script is used to get the details from the page and make them available for the content script by being injected directly into the page +*/ + +import { PageType } from "./types"; + +interface StartMessage { + type: "navigation", + pageType: PageType + videoID: string | null, +} + +interface FinishMessage extends StartMessage { + channelID: string, + channelTitle: string +} + +interface AdMessage { + type: "ad", + playing: boolean +} + +interface VideoData { + type: "data", + videoID: string, + isLive: boolean, + isPremiere: boolean +} + +type WindowMessage = StartMessage | FinishMessage | AdMessage | VideoData; + +// global playerClient - too difficult to type +// eslint-disable-next-line @typescript-eslint/no-explicit-any +let playerClient: any; + +const sendMessage = (message: WindowMessage): void => { + window.postMessage({ source: "sponsorblock", ...message }, "/"); +} + +function setupPlayerClient(e: CustomEvent): void { + if (playerClient) return; // early exit if already defined + + playerClient = e.detail; + sendVideoData(); // send playerData after setup + + e.detail.addEventListener('onAdStart', () => sendMessage({ type: "ad", playing: true } as AdMessage)); + e.detail.addEventListener('onAdFinish', () => sendMessage({ type: "ad", playing: false } as AdMessage)); +} + +document.addEventListener("yt-player-updated", setupPlayerClient); +document.addEventListener("yt-navigate-start", navigationStartSend); +document.addEventListener("yt-navigate-finish", navigateFinishSend); + +function navigationParser(event: CustomEvent): StartMessage { + const pageType: PageType = event.detail.pageType; + const result: StartMessage = { type: "navigation", pageType, videoID: null }; + if (pageType === "shorts" || pageType === "watch") { + const endpoint = event.detail.endpoint + result.videoID = (pageType === "shorts" ? endpoint.reelWatchEndpoint : endpoint.watchEndpoint).videoId; + } + + return result; +} + +function navigationStartSend(event: CustomEvent): void { + sendMessage(navigationParser(event) as StartMessage); +} + +function navigateFinishSend(event: CustomEvent): void { + sendVideoData(); // arrived at new video, send video data + const videoDetails = event.detail?.response?.playerResponse?.videoDetails; + sendMessage({ channelID: videoDetails.channelId, channelTitle: videoDetails.author, ...navigationParser(event) } as FinishMessage); +} + +function sendVideoData(): void { + if (!playerClient) return; + const { video_id, isLive, isPremiere } = playerClient.getVideoData(); + sendMessage({ type: "data", videoID: video_id, isLive, isPremiere } as VideoData); +}
\ No newline at end of file diff --git a/src/types.ts b/src/types.ts index 6b8f9969..f90d199d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -239,6 +239,14 @@ export type Keybind = { shift?: boolean } +export enum PageType { + Shorts = "shorts", + Watch = "watch", + Search = "search", + Browse = "browse", + Channel = "channel" +} + export interface ButtonListener { name: string, listener: (e?: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void diff --git a/webpack/webpack.common.js b/webpack/webpack.common.js index 73c17807..d05dea03 100644 --- a/webpack/webpack.common.js +++ b/webpack/webpack.common.js @@ -29,9 +29,10 @@ module.exports = env => ({ popup: path.join(__dirname, srcDir + 'popup.ts'), background: path.join(__dirname, srcDir + 'background.ts'), content: path.join(__dirname, srcDir + 'content.ts'), - options: path.join(__dirname, srcDir + 'options.ts'), - help: path.join(__dirname, srcDir + 'help.ts'), - permissions: path.join(__dirname, srcDir + 'permissions.ts'), + options: path.join(__dirname, srcDir + 'options.ts'), + help: path.join(__dirname, srcDir + 'help.ts'), + permissions: path.join(__dirname, srcDir + 'permissions.ts'), + document: path.join(__dirname, srcDir + 'document.ts'), upsell: path.join(__dirname, srcDir + 'upsell.ts') }, output: { |