From a6728d34a03f93176741e32b39fe683e0625ec90 Mon Sep 17 00:00:00 2001 From: Ajay Ramachandran Date: Sun, 20 Jun 2021 20:56:42 -0400 Subject: [PATCH 1/9] Add more parameters to make get_video_info api work again (cherry picked from commit 930911e2b90d3b74a0358345f9109172dc8ff06b) --- src/content.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content.ts b/src/content.ts index 8ecddad1..85bae210 100644 --- a/src/content.ts +++ b/src/content.ts @@ -721,7 +721,7 @@ function startSkipScheduleCheckingForStartSponsors() { * Get the video info for the current tab from YouTube */ async function getVideoInfo(): Promise { - const result = await utils.asyncRequestToCustomServer("GET", "https://www.youtube.com/get_video_info?video_id=" + sponsorVideoID + "&html5=1"); + const result = await utils.asyncRequestToCustomServer("GET", "https://www.youtube.com/get_video_info?video_id=" + sponsorVideoID + "&html5=1&c=TVHTML5&cver=7.20190319"); if (result.ok) { const decodedData = decodeURIComponent(result.responseText).match(/player_response=([^&]*)/)[1]; From e80b7afe809fef5d11d7621aa2299ae27d5a7957 Mon Sep 17 00:00:00 2001 From: Ajay Ramachandran Date: Wed, 23 Jun 2021 21:09:59 -0400 Subject: [PATCH 2/9] Add initial unlisted detection for getting a list of unlisted videos A temporary measure which will be removed next month https://support.google.com/youtube/answer/9230970 --- src/config.ts | 4 ++ src/content.ts | 72 ++++++++++++++++++++- src/render/GenericNotice.tsx | 111 ++++++++++++++++++++++++++++++++ src/render/SkipNotice.tsx | 24 ++----- src/render/SubmissionNotice.tsx | 20 ++---- src/utils.ts | 23 +++++++ 6 files changed, 217 insertions(+), 37 deletions(-) create mode 100644 src/render/GenericNotice.tsx diff --git a/src/config.ts b/src/config.ts index 6877400a..75392f98 100644 --- a/src/config.ts +++ b/src/config.ts @@ -38,6 +38,8 @@ interface SBConfig { testingServer: boolean, refetchWhenNotFound: boolean, ytInfoPermissionGranted: boolean, + askAboutUnlistedVideos: boolean, + allowExpirements: boolean, // What categories should be skipped categorySelections: CategorySelection[], @@ -174,6 +176,8 @@ const Config: SBObject = { testingServer: false, refetchWhenNotFound: true, ytInfoPermissionGranted: false, + askAboutUnlistedVideos: true, + allowExpirements: true, categorySelections: [{ name: "sponsor", diff --git a/src/content.ts b/src/content.ts index 85bae210..5a7286d7 100644 --- a/src/content.ts +++ b/src/content.ts @@ -13,6 +13,7 @@ import SkipNotice from "./render/SkipNotice"; import SkipNoticeComponent from "./components/SkipNoticeComponent"; import SubmissionNotice from "./render/SubmissionNotice"; import { Message, MessageResponse } from "./messageTypes"; +import GenericNotice from "./render/GenericNotice"; // Hack to get the CSS loaded on permission-based sites (Invidious) utils.wait(() => Config.config !== null, 5000, 10).then(addCSS); @@ -271,6 +272,9 @@ async function videoIDChange(id) { // Update whitelist data when the video data is loaded whitelistCheck(); + // Temporary expirement + unlistedCheck(); + //setup the preview bar if (previewBar === null) { if (onMobileYouTube) { @@ -391,7 +395,7 @@ function startSponsorSchedule(includeIntersectingSegments = false, currentTime?: return; } - if (video.paused) return; + if (!video || video.paused) return; if (Config.config.disableSkipping || channelWhitelisted || (channelIDInfo.status === ChannelIDStatus.Fetching && Config.config.forceChannelCheck)){ return; @@ -864,6 +868,72 @@ async function whitelistCheck() { if (Config.config.forceChannelCheck && sponsorTimes?.length > 0) startSkipScheduleCheckingForStartSponsors(); } +async function unlistedCheck() { + if (!Config.config.allowExpirements || !Config.config.askAboutUnlistedVideos) return; + + try { + await utils.wait(() => !!videoInfo && !!document.getElementById("info-text") + && !!document.querySelector(".ytd-video-primary-info-renderer > .badge > yt-icon > svg"), 6000, 1000); + + const isUnlisted = document.querySelector(".ytd-video-primary-info-renderer > .badge > yt-icon > svg > g > path") + ?.getAttribute("d")?.includes("M3.9 12c0-1.71 1.39-3.1 3.1-3.1h"); // Icon of unlisted badge + const yearMatches = document.querySelector("#info-text > #info-strings > yt-formatted-string") + ?.innerHTML?.match(/20[0-9]{2}/); + const year = yearMatches ? parseInt(yearMatches[0]) : -1; + const isOld = !isNaN(year) && year < 2017 && year > 2004; + const isHighViews = parseInt(videoInfo?.videoDetails?.viewCount) > 20000; + + console.log({ + isUnlisted, + year, + isOld, + isHighViews + }) + + if (isUnlisted && isOld && isHighViews) { + // Ask if they want to submit this videoID + const notice = new GenericNotice(skipNoticeContentContainer, "unlistedWarning", { + title: "Help prevent this from disappearing", + textBoxes: ("This video is detected as unlisted and uploaded before 2017\n" + + "Old unlisted videos are being set to private soon\n" + + "We are collecting *public* videos to back up\n" + + "Would you like anonymously to submit this video?").split("\n"), + buttons: [ + { + name: "Opt-out of all future experiments", + listener: () => { + Config.config.allowExpirements = false; + + notice.close(); + } + }, + { + name: "Never show this", + listener: () => { + Config.config.askAboutUnlistedVideos = false; + + notice.close(); + } + }, + { + name: "Submit", + listener: () => { + utils.asyncRequestToServer("POST", "/api/unlistedVideo", { + videoID: sponsorVideoID + }); + + notice.close(); + } + } + ] + }); + } + + } catch (e) { + return; + } +} + /** * Returns info about the next upcoming sponsor skip */ diff --git a/src/render/GenericNotice.tsx b/src/render/GenericNotice.tsx new file mode 100644 index 00000000..08570eab --- /dev/null +++ b/src/render/GenericNotice.tsx @@ -0,0 +1,111 @@ +import * as React from "react"; +import * as ReactDOM from "react-dom"; +import NoticeComponent from "../components/NoticeComponent"; + +import Utils from "../utils"; +const utils = new Utils(); + +import { ContentContainer } from "../types"; +import NoticeTextSelectionComponent from "../components/NoticeTextSectionComponent"; + +export interface ButtonListener { + name: string, + listener: (e?: React.MouseEvent) => void +} + +export interface NoticeOptions { + title: string, + textBoxes?: string[], + buttons?: ButtonListener[], + fadeIn?: boolean, + timed?: boolean +} + +export default class GenericNotice { + // Contains functions and variables from the content script needed by the skip notice + contentContainer: ContentContainer; + + noticeElement: HTMLDivElement; + noticeRef: React.MutableRefObject; + + constructor(contentContainer: ContentContainer, idSuffix: string, options: NoticeOptions) { + this.noticeRef = React.createRef(); + + this.contentContainer = contentContainer; + + const referenceNode = utils.findReferenceNode(); + + this.noticeElement = document.createElement("div"); + this.noticeElement.id = "sponsorSkipNoticeContainer" + idSuffix; + + referenceNode.prepend(this.noticeElement); + + ReactDOM.render( + this.close()} > + + {this.getMessageBox(idSuffix, options.textBoxes)} + + + + +
+ + {this.getButtons(options.buttons)} +
+
, + this.noticeElement + ); + } + + getMessageBox(idSuffix: string, textBoxes: string[]): JSX.Element[] { + if (textBoxes) { + const result = []; + for (let i = 0; i < textBoxes.length; i++) { + result.push( + + ) + } + + return result; + } else { + return null; + } + } + + getButtons(buttons?: ButtonListener[]): JSX.Element[] { + if (buttons) { + const result: JSX.Element[] = []; + + for (const button of buttons) { + result.push( + + ) + } + + return result; + } else { + return null; + } + } + + close(): void { + ReactDOM.unmountComponentAtNode(this.noticeElement); + + this.noticeElement.remove(); + } +} \ No newline at end of file diff --git a/src/render/SkipNotice.tsx b/src/render/SkipNotice.tsx index 426b1460..58600034 100644 --- a/src/render/SkipNotice.tsx +++ b/src/render/SkipNotice.tsx @@ -1,6 +1,9 @@ import * as React from "react"; import * as ReactDOM from "react-dom"; +import Utils from "../utils"; +const utils = new Utils(); + import SkipNoticeComponent, { SkipNoticeAction } from "../components/SkipNoticeComponent"; import { SponsorTime, ContentContainer } from "../types"; @@ -21,26 +24,7 @@ class SkipNotice { this.autoSkip = autoSkip; this.contentContainer = contentContainer; - //get reference node - let referenceNode = document.getElementById("player-container-id") - ?? document.getElementById("movie_player") - ?? document.querySelector("#main-panel.ytmusic-player-page") // YouTube music - ?? document.querySelector("#player-container .video-js") // Invidious - ?? document.querySelector(".main-video-section > .video-container"); // Cloudtube - if (referenceNode == null) { - //for embeds - const player = document.getElementById("player"); - referenceNode = player.firstChild as HTMLElement; - let index = 1; - - //find the child that is the video player (sometimes it is not the first) - while (index < player.children.length && (!referenceNode.classList.contains("html5-video-player") || !referenceNode.classList.contains("ytp-embed"))) { - referenceNode = player.children[index] as HTMLElement; - - index++; - } - } - + const referenceNode = utils.findReferenceNode(); const amountOfPreviousNotices = document.getElementsByClassName("sponsorSkipNotice").length; //this is the suffix added at the end of every id diff --git a/src/render/SubmissionNotice.tsx b/src/render/SubmissionNotice.tsx index 8c267854..5f646063 100644 --- a/src/render/SubmissionNotice.tsx +++ b/src/render/SubmissionNotice.tsx @@ -1,6 +1,9 @@ import * as React from "react"; import * as ReactDOM from "react-dom"; +import Utils from "../utils"; +const utils = new Utils(); + import SubmissionNoticeComponent from "../components/SubmissionNoticeComponent"; import { ContentContainer } from "../types"; @@ -20,22 +23,7 @@ class SubmissionNotice { this.contentContainer = contentContainer; this.callback = callback; - //get reference node - let referenceNode = document.getElementById("player-container-id") - || document.getElementById("movie_player") || document.querySelector("#player-container .video-js"); - if (referenceNode == null) { - //for embeds - const player = document.getElementById("player"); - referenceNode = player.firstChild as HTMLElement; - let index = 1; - - //find the child that is the video player (sometimes it is not the first) - while (!referenceNode.classList.contains("html5-video-player") || !referenceNode.classList.contains("ytp-embed")) { - referenceNode = player.children[index] as HTMLElement; - - index++; - } - } + const referenceNode = utils.findReferenceNode(); this.noticeElement = document.createElement("div"); this.noticeElement.id = "submissionNoticeContainer"; diff --git a/src/utils.ts b/src/utils.ts index fd5684f4..4aa21dbf 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -366,6 +366,29 @@ export default class Utils { }); } + findReferenceNode(): HTMLElement { + let referenceNode = document.getElementById("player-container-id") + ?? document.getElementById("movie_player") + ?? document.querySelector("#main-panel.ytmusic-player-page") // YouTube music + ?? document.querySelector("#player-container .video-js") // Invidious + ?? document.querySelector(".main-video-section > .video-container"); // Cloudtube + if (referenceNode == null) { + //for embeds + const player = document.getElementById("player"); + referenceNode = player.firstChild as HTMLElement; + let index = 1; + + //find the child that is the video player (sometimes it is not the first) + while (index < player.children.length && (!referenceNode.classList.contains("html5-video-player") || !referenceNode.classList.contains("ytp-embed"))) { + referenceNode = player.children[index] as HTMLElement; + + index++; + } + } + + return referenceNode; + } + getFormattedTime(seconds: number, precise?: boolean): string { const hours = Math.floor(seconds / 60 / 60); const minutes = Math.floor(seconds / 60) % 60; From e7c92467bd10b5c108f5bcac4c98abd2c4ce1e7a Mon Sep 17 00:00:00 2001 From: Ajay Ramachandran Date: Wed, 23 Jun 2021 21:38:23 -0400 Subject: [PATCH 3/9] Add localisation to unlisted experiement --- public/_locales/en/messages.json | 14 ++++++++++++-- src/content.ts | 11 ++++------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/public/_locales/en/messages.json b/public/_locales/en/messages.json index 1ad9b1f2..2da08151 100644 --- a/public/_locales/en/messages.json +++ b/public/_locales/en/messages.json @@ -654,7 +654,17 @@ "categoryUpdate2": { "message": "Open the options to skip intros, outros, merch, etc." }, - "help": { - "message": "Help" + "experimentUnlistedTitle": { + "message": "Help prevent this from disappearing" + }, + "experimentUnlistedText": { + "message": "This video is detected as unlisted and uploaded before 2017\nOld unlisted videos are being set to private soon\nWe are collecting *public* videos to back up\nWould you like anonymously to submit this video?" + }, + "experiementOptOut": { + "message": "Opt-out of all future experiments", + "description": "This is used in a popup about a new experiment to get a list of unlisted videos to back up since all unlisted videos uploaded before 2017 will be set to private." + }, + "hideForever": { + "message": "Hide forever" } } diff --git a/src/content.ts b/src/content.ts index 5a7286d7..d0a1def1 100644 --- a/src/content.ts +++ b/src/content.ts @@ -893,14 +893,11 @@ async function unlistedCheck() { if (isUnlisted && isOld && isHighViews) { // Ask if they want to submit this videoID const notice = new GenericNotice(skipNoticeContentContainer, "unlistedWarning", { - title: "Help prevent this from disappearing", - textBoxes: ("This video is detected as unlisted and uploaded before 2017\n" - + "Old unlisted videos are being set to private soon\n" - + "We are collecting *public* videos to back up\n" - + "Would you like anonymously to submit this video?").split("\n"), + title: chrome.i18n.getMessage("experimentUnlistedTitle"), + textBoxes: chrome.i18n.getMessage("experimentUnlistedText").split("\n"), buttons: [ { - name: "Opt-out of all future experiments", + name: chrome.i18n.getMessage("experiementOptOut"), listener: () => { Config.config.allowExpirements = false; @@ -908,7 +905,7 @@ async function unlistedCheck() { } }, { - name: "Never show this", + name: chrome.i18n.getMessage("hideForever"), listener: () => { Config.config.askAboutUnlistedVideos = false; From 693997d35121becef883548cea40ec74d2b15902 Mon Sep 17 00:00:00 2001 From: Ajay Ramachandran Date: Wed, 23 Jun 2021 22:21:22 -0400 Subject: [PATCH 4/9] Clarify what submit means --- public/_locales/en/messages.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/_locales/en/messages.json b/public/_locales/en/messages.json index 2da08151..d31186df 100644 --- a/public/_locales/en/messages.json +++ b/public/_locales/en/messages.json @@ -658,7 +658,7 @@ "message": "Help prevent this from disappearing" }, "experimentUnlistedText": { - "message": "This video is detected as unlisted and uploaded before 2017\nOld unlisted videos are being set to private soon\nWe are collecting *public* videos to back up\nWould you like anonymously to submit this video?" + "message": "This video is detected as unlisted and uploaded before 2017\nOld unlisted videos are being set to private soon\nWe are collecting *public* videos to back up\nWould you like anonymously to send this video to us?" }, "experiementOptOut": { "message": "Opt-out of all future experiments", From e9b0fae747fd5d23750dc402773abbd6240c0f53 Mon Sep 17 00:00:00 2001 From: Ajay Ramachandran Date: Wed, 23 Jun 2021 22:23:23 -0400 Subject: [PATCH 5/9] Increase version number --- manifest/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifest/manifest.json b/manifest/manifest.json index 53e87021..4ca2ae37 100644 --- a/manifest/manifest.json +++ b/manifest/manifest.json @@ -1,7 +1,7 @@ { "name": "__MSG_fullName__", "short_name": "SponsorBlock", - "version": "2.1.0.1", + "version": "2.1.0.2", "default_locale": "en", "description": "__MSG_Description__", "content_scripts": [{ From db72e490dfd46531f72d321fae4262b2b0ed356f Mon Sep 17 00:00:00 2001 From: Ajay Ramachandran Date: Wed, 23 Jun 2021 22:57:46 -0400 Subject: [PATCH 6/9] Chnage wording of experiment --- public/_locales/en/messages.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/_locales/en/messages.json b/public/_locales/en/messages.json index d31186df..1603318e 100644 --- a/public/_locales/en/messages.json +++ b/public/_locales/en/messages.json @@ -658,7 +658,7 @@ "message": "Help prevent this from disappearing" }, "experimentUnlistedText": { - "message": "This video is detected as unlisted and uploaded before 2017\nOld unlisted videos are being set to private soon\nWe are collecting *public* videos to back up\nWould you like anonymously to send this video to us?" + "message": "This video is detected as unlisted and uploaded before 2017\nOld unlisted videos are being set to private next month\nWe are collecting *public* videos to back up\nWould you like anonymously to send this video to us?" }, "experiementOptOut": { "message": "Opt-out of all future experiments", From bd2dac69b960cf58dd9ef70c30e89b66eb210597 Mon Sep 17 00:00:00 2001 From: Ajay Ramachandran Date: Wed, 23 Jun 2021 23:46:21 -0400 Subject: [PATCH 7/9] Also send views, year and channelID --- src/content.ts | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/content.ts b/src/content.ts index d0a1def1..1bd64b36 100644 --- a/src/content.ts +++ b/src/content.ts @@ -881,14 +881,8 @@ async function unlistedCheck() { ?.innerHTML?.match(/20[0-9]{2}/); const year = yearMatches ? parseInt(yearMatches[0]) : -1; const isOld = !isNaN(year) && year < 2017 && year > 2004; - const isHighViews = parseInt(videoInfo?.videoDetails?.viewCount) > 20000; - - console.log({ - isUnlisted, - year, - isOld, - isHighViews - }) + const views = parseInt(videoInfo?.videoDetails?.viewCount); + const isHighViews = views > 20000; if (isUnlisted && isOld && isHighViews) { // Ask if they want to submit this videoID @@ -916,7 +910,10 @@ async function unlistedCheck() { name: "Submit", listener: () => { utils.asyncRequestToServer("POST", "/api/unlistedVideo", { - videoID: sponsorVideoID + videoID: sponsorVideoID, + year, + views, + channelID: channelIDInfo.status === ChannelIDStatus.Found ? channelIDInfo.id : undefined }); notice.close(); From 551d9144b7d61641d54c02958ede76f21c965d6c Mon Sep 17 00:00:00 2001 From: Ajay Ramachandran Date: Wed, 23 Jun 2021 23:50:16 -0400 Subject: [PATCH 8/9] Add url to support page for unlisted videos experiment --- public/_locales/en/messages.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/_locales/en/messages.json b/public/_locales/en/messages.json index 1603318e..1e3bc3c8 100644 --- a/public/_locales/en/messages.json +++ b/public/_locales/en/messages.json @@ -658,7 +658,7 @@ "message": "Help prevent this from disappearing" }, "experimentUnlistedText": { - "message": "This video is detected as unlisted and uploaded before 2017\nOld unlisted videos are being set to private next month\nWe are collecting *public* videos to back up\nWould you like anonymously to send this video to us?" + "message": "This video is detected as unlisted and uploaded before 2017\nOld unlisted videos are being set to private next month\nWe are collecting *public* videos to back up\nWould you like anonymously to send this video to us?\nhttps://support.google.com/youtube/answer/9230970" }, "experiementOptOut": { "message": "Opt-out of all future experiments", From a2c0c3f79e3c5e07cc7fd961cbca003b9979102d Mon Sep 17 00:00:00 2001 From: Ajay Ramachandran Date: Wed, 23 Jun 2021 23:51:20 -0400 Subject: [PATCH 9/9] Lower minimum view count --- src/content.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content.ts b/src/content.ts index 1bd64b36..aaf79e62 100644 --- a/src/content.ts +++ b/src/content.ts @@ -882,7 +882,7 @@ async function unlistedCheck() { const year = yearMatches ? parseInt(yearMatches[0]) : -1; const isOld = !isNaN(year) && year < 2017 && year > 2004; const views = parseInt(videoInfo?.videoDetails?.viewCount); - const isHighViews = views > 20000; + const isHighViews = views > 15000; if (isUnlisted && isOld && isHighViews) { // Ask if they want to submit this videoID