From bf84139ea7530f2b03fecc5b8429a4f818d99e8b Mon Sep 17 00:00:00 2001 From: Ajay Ramachandran Date: Wed, 24 Mar 2021 20:13:33 -0400 Subject: [PATCH] Prompt to accept youtube.com permission if video info fails to load Should fix #698, #687, #611 and #635 (cherry picked from commit 3ff5fdb3a10d09de34d979f696133c17b5b58c31) --- public/_locales/en/messages.json | 15 ++ public/permissions/index.html | 28 +++ public/permissions/styles.css | 356 +++++++++++++++++++++++++++++++ src/background.ts | 3 + src/content.ts | 23 +- src/options.ts | 2 +- src/permissions.ts | 33 +++ src/utils.ts | 41 ++-- webpack/webpack.common.js | 3 +- 9 files changed, 483 insertions(+), 21 deletions(-) create mode 100644 public/permissions/index.html create mode 100644 public/permissions/styles.css create mode 100644 src/permissions.ts diff --git a/public/_locales/en/messages.json b/public/_locales/en/messages.json index 51eceee8..fbf06ec7 100644 --- a/public/_locales/en/messages.json +++ b/public/_locales/en/messages.json @@ -594,9 +594,24 @@ "adblockerIssue": { "message": "It seems that something is blocking SponsorBlock's ability to get video data. This is probably your ad blocker. Please check https://github.com/ajayyy/SponsorBlock/wiki/Fix-Ad-Blocker-Blocking-SponsorBlock's-Requests" }, + "youtubePermissionRequest": { + "message": "It seems that SponsorBlock is unable to reach the YouTube API. To fix this, accept the permission prompt that will appear next, wait a few seconds, and then reload the page." + }, + "acceptPermission": { + "message": "Accept permission" + }, + "permissionRequestSuccess": { + "message": "Permission success succeeded!" + }, + "permissionRequestFailed": { + "message": "Permission request failed, did you click deny?" + }, "adblockerIssueUnlistedVideosInfo": { "message": "If you are unable to resolve this, then disable the setting 'Ignore unlisted/private videos', as SponsorBlock is unable to retrieve the visibility information for this video" }, + "adblockerIssueWhitelist": { + "message": "If you are unable to resolve this, then disable the setting 'Force Channel Check Before Skipping', as SponsorBlock is unable to retrieve the visibility information for this video" + }, "itCouldBeAdblockerIssue": { "message": "If this keeps occuring, it could be caused by your ad blocker. Please check https://github.com/ajayyy/SponsorBlock/wiki/Fix-Ad-Blocker-Blocking-SponsorBlock's-Requests" }, diff --git a/public/permissions/index.html b/public/permissions/index.html new file mode 100644 index 00000000..4475f039 --- /dev/null +++ b/public/permissions/index.html @@ -0,0 +1,28 @@ + + + + Permissions - SponsorBlock + + + + + + + + + + +
+ + SponsorBlock +
+ +
+ +
+
+ __MSG_acceptPermission__ +
+
+ + diff --git a/public/permissions/styles.css b/public/permissions/styles.css new file mode 100644 index 00000000..971c6893 --- /dev/null +++ b/public/permissions/styles.css @@ -0,0 +1,356 @@ +/* Options page CSS */ +body { + font-family: sans-serif; +} + +.center { + text-align: center; +} + +.inline { + display: inline-block; +} + +.bold { + font-weight: bold; +} + +.hidden { + display: none !important; +} + +.keybind-status { + display: inline; +} + +.small-description { + color: white; + font-size: 13px; +} + +.medium-description { + color: white; + font-size: 15px; +} + +.option-text-box { + width: 300px; +} + +.option-button { + cursor: pointer; + + background-color: #c00000; + padding: 10px; + color: white; + border-radius: 5px; + font-size: 14px; + + width: max-content; +} + +.option-button:hover { + background-color: #fc0303; +} + +.option-button.disabled { + cursor: default; + + background-color: #520000; + color: grey; +} + +#options { + max-width: 60%; + text-align: left; + display: inline-block; +} + +.switch-container:after { + content: attr(label-name); + position: absolute; + padding: 4px; + width: max-content; + + font-size: 14px; + color: white; +} + +.text-label-container { + font-size: 14px; + color: white; +} + +.switch { + position: relative; + display: inline-block; + width: 40px; + height: 24px; +} + +.switch input { + opacity: 0; + width: 0; + height: 0; +} + +.slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: #707070; +} + +.animated * { + -webkit-transition: .4s; + transition: .4s; +} + +.slider:before { + position: absolute; + content: ""; + height: 16px; + width: 16px; + left: 4px; + bottom: 4px; + background-color: white; +} + +.animated .slider:before { + -webkit-transition: .4s; + transition: .4s; +} + +input:checked + .slider { + background-color: #fc0303; +} + +input:checked + .slider:before { + -webkit-transform: translateX(16px); + -ms-transform: translateX(16px); + transform: translateX(16px); +} + +/* Rounded sliders */ +.slider.round { + border-radius: 34px; +} + +.slider.round:before { + border-radius: 50%; +} + + +/* Boilerplate CSS from https://ajay.app */ + +body { + background-color: #333333; +} + +.projectPreview { + position: relative; +} + +.projectPreviewImage { + position: absolute; + left: -90px; + width: 80px; + top: 50%; + transform: translateY(-50%); +} + +.projectPreviewImageLarge { + position: absolute; + left: -210px; + width: 200px; + top: 50%; + transform: translateY(-20%); +} + +.projectPreviewImageLargeRight { + position: absolute; + right: -210px; + width: 200px; + top: 50%; + transform: translateY(-50%); +} + +.createdBy { + font-size: 14px; + text-align: center; + padding-top: 0px; + padding-bottom: 0px; + + display: inline-block; +} + +#title { + background-color: #636363; + + text-align: center; + vertical-align: middle; + + font-size: 50px; + color: #212121; + + padding: 20px; + + text-decoration: none; + + transition: font-size 1s; +} + +.subtitle { + font-size: 40px; + color: #dad8d8; + + padding-top: 10px; + + transition: font-size 0.4s; +} + +.subtitle:hover { + font-size: 45px; + + transition: font-size 0.4s; +} + +.profilepic { + background-color: #636363 !important; + vertical-align: middle; +} + +.profilepiccircle { + vertical-align: middle; + overflow: hidden; + border-radius: 50%; +} + +a { + text-decoration: underline; + color: inherit; +} + +.link { + padding: 20px; + + height: 80px; + + transition: height 0.2s; +} + +.link:hover { + height: 95px; + + transition: height 0.2s; +} + +#contact,.smalllink { + font-size: 25px; + color: #e8e8e8; + + text-align: center; + + padding: 10px; +} + +#contact { + text-decoration: none; +} + +p,li { + font-size: 20px; + color: #c4c4c4; + + padding: 10px; +} + +p,li,code,a { + max-width: 60%; + text-align: left; + overflow-wrap: break-word; +} + +@media screen and (orientation:portrait) { + p,li,code,a { + max-width: 100%; + } + + .projectPreviewImage { + position: unset; + width: 130px; + display: block; + margin: auto; + transform: none; + } +} + +.previewImage { + max-height: 200px; +} + +img { + max-width: 100%; + + text-align: center; +} + +#recentPostTitle { + font-size: 30px; + color: #dad8d8; +} + +#recentPostDate { + font-size: 15px; + color: #dad8d8; +} + +h1,h2,h3,h4,h5,h6 { + color: #dad8d8; +} + +svg { + text-decoration: none; +} + +.number-container:before { + content: attr(label-name); + padding-right: 4px; + width: max-content; + + font-size: 14px; + color: white; +} + +/* React styles */ + +.categoryTableElement { + font-size: 16px; + + color: white; +} + +.categoryTableElement > * { + padding-right: 15px; + padding-bottom: 15px; +} + +.categoryOptionsSelector { + background-color: #c00000; + color: white; + + border: none; + font-size: 14px; + padding: 5px; + border-radius: 5px; +} + +.categoryColorTextBox { + width: 60px; + + background: none; + border: none; +} \ No newline at end of file diff --git a/src/background.ts b/src/background.ts index b562c4e7..e44dd28a 100644 --- a/src/background.ts +++ b/src/background.ts @@ -37,6 +37,9 @@ chrome.runtime.onMessage.addListener(function (request, sender, callback) { case "openHelp": chrome.tabs.create({url: chrome.runtime.getURL('help/index_en.html')}); return; + case "openPage": + chrome.tabs.create({url: chrome.runtime.getURL(request.url)}); + return; case "sendRequest": sendRequestToCustomServer(request.type, request.url, request.data).then(async (response) => { callback({ diff --git a/src/content.ts b/src/content.ts index 33842dd3..dd219456 100644 --- a/src/content.ts +++ b/src/content.ts @@ -258,7 +258,7 @@ async function videoIDChange(id) { try { await utils.wait(() => !!videoInfo, 5000, 1); } catch (err) { - alert(chrome.i18n.getMessage("adblockerIssue") + "\n\n" + chrome.i18n.getMessage("adblockerIssueUnlistedVideosInfo")); + await videoInfoFetchFailed("adblockerIssueUnlistedVideosInfo"); } if (isUnlisted()) { @@ -268,7 +268,11 @@ async function videoIDChange(id) { } // Update whitelist data when the video data is loaded - utils.wait(() => !!videoInfo, 5000, 10).then(whitelistCheck); + utils.wait(() => !!videoInfo, 5000, 10).then(whitelistCheck).catch(() => { + if (Config.config.forceChannelCheck) { + videoInfoFetchFailed("adblockerIssueWhitelist"); + } + }); //setup the preview bar if (previewBar === null) { @@ -727,6 +731,21 @@ async function getVideoInfo(): Promise { } } +async function videoInfoFetchFailed(errorMessage: string): Promise { + console.log("failed\t" + errorMessage) + if (utils.isFirefox()) { + // Attempt to ask permission for youtube.com domain + alert(chrome.i18n.getMessage("youtubePermissionRequest")); + + chrome.runtime.sendMessage({ + message: "openPage", + url: "permissions/index.html#youtube.com" + }); + } else { + alert(chrome.i18n.getMessage("adblockerIssue") + "\n\n" + chrome.i18n.getMessage(errorMessage)); + } +} + function getYouTubeVideoID(url: string) { // For YouTube TV support if(url.startsWith("https://www.youtube.com/tv#/")) url = url.replace("#", ""); diff --git a/src/options.ts b/src/options.ts index 186e442a..815aff02 100644 --- a/src/options.ts +++ b/src/options.ts @@ -288,7 +288,7 @@ function invidiousInit(checkbox: HTMLInputElement, option: string) { if (utils.isFirefox()) permissions = []; chrome.permissions.contains({ - origins: utils.getInvidiousInstancesRegex(), + origins: utils.getPermissionRegex(), permissions: permissions }, function (result) { if (result != checkbox.checked) { diff --git a/src/permissions.ts b/src/permissions.ts new file mode 100644 index 00000000..b6a41dce --- /dev/null +++ b/src/permissions.ts @@ -0,0 +1,33 @@ +import Config from "./config"; +import Utils from "./utils"; +const utils = new Utils(); + +// This is needed, if Config is not imported before Utils, things break. +// Probably due to cyclic dependencies +Config.config; + +window.addEventListener('DOMContentLoaded', init); + +async function init() { + utils.localizeHtmlPage(); + + const domains = document.location.hash.replace("#", "").split(","); + + const acceptButton = document.getElementById("acceptPermissionButton"); + acceptButton.addEventListener("click", () => { + chrome.permissions.request({ + origins: utils.getPermissionRegex(domains), + permissions: [] + }, (granted) => { + if (granted) { + alert(chrome.i18n.getMessage("permissionRequestSuccess")); + + chrome.tabs.getCurrent((tab) => { + chrome.tabs.remove(tab.id); + }); + } else { + alert(chrome.i18n.getMessage("permissionRequestFailed")); + } + }); + }); +} \ No newline at end of file diff --git a/src/utils.ts b/src/utils.ts index 7658cab0..bb42afc5 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -3,10 +3,10 @@ import { CategorySelection, SponsorTime, FetchResponse, BackgroundScriptContaine import * as CompileConfig from "../config.json"; -class Utils { +export default class Utils { // Contains functions needed from the background script - backgroundScriptContainer: BackgroundScriptContainer | null = null; + backgroundScriptContainer: BackgroundScriptContainer | null; // Used to add content scripts and CSS required js = [ @@ -19,7 +19,7 @@ class Utils { "popup.css" ]; - constructor(backgroundScriptContainer?: BackgroundScriptContainer) { + constructor(backgroundScriptContainer: BackgroundScriptContainer = null) { this.backgroundScriptContainer = backgroundScriptContainer; } @@ -43,6 +43,12 @@ class Utils { }); } + containsPermission(permissions: chrome.permissions.Permissions): Promise { + return new Promise((resolve) => { + chrome.permissions.contains(permissions, resolve) + }); + } + /** * Asks for the optional permissions required for all extra sites. * It also starts the content script registrations. @@ -57,7 +63,7 @@ class Utils { if (this.isFirefox()) permissions = []; chrome.permissions.request({ - origins: this.getInvidiousInstancesRegex(), + origins: this.getPermissionRegex(), permissions: permissions }, async (granted) => { if (granted) { @@ -78,7 +84,6 @@ class Utils { * For now, it is just SB.config.invidiousInstances. */ setupExtraSiteContentScripts(): void { - if (this.isFirefox()) { const firefoxJS = []; for (const file of this.js) { @@ -95,7 +100,7 @@ class Utils { allFrames: true, js: firefoxJS, css: firefoxCSS, - matches: this.getInvidiousInstancesRegex() + matches: this.getPermissionRegex() }; if (this.backgroundScriptContainer) { @@ -106,7 +111,7 @@ class Utils { } else { chrome.declarativeContent.onPageChanged.removeRules(["invidious"], () => { const conditions = []; - for (const regex of this.getInvidiousInstancesRegex()) { + for (const regex of this.getPermissionRegex()) { conditions.push(new chrome.declarativeContent.PageStateMatcher({ pageUrl: { urlMatches: regex } })); @@ -149,7 +154,7 @@ class Utils { } chrome.permissions.remove({ - origins: this.getInvidiousInstancesRegex() + origins: this.getPermissionRegex() }); } @@ -250,16 +255,20 @@ class Utils { } /** - * @returns {String[]} Invidious Instances in regex form + * @returns {String[]} Domains in regex form */ - getInvidiousInstancesRegex(): string[] { - const invidiousInstancesRegex: string[] = []; - for (const url of Config.config.invidiousInstances) { - invidiousInstancesRegex.push("https://*." + url + "/*"); - invidiousInstancesRegex.push("http://*." + url + "/*"); + getPermissionRegex(domains: string[] = []): string[] { + const permissionRegex: string[] = []; + if (domains.length === 0) { + domains = [...Config.config.invidiousInstances]; } - return invidiousInstancesRegex; + for (const url of domains) { + permissionRegex.push("https://*." + url + "/*"); + permissionRegex.push("http://*." + url + "/*"); + } + + return permissionRegex; } generateUserID(length = 36): string { @@ -434,5 +443,3 @@ class Utils { } } - -export default Utils; diff --git a/webpack/webpack.common.js b/webpack/webpack.common.js index 79fea60b..1d130af0 100644 --- a/webpack/webpack.common.js +++ b/webpack/webpack.common.js @@ -9,7 +9,8 @@ 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') + options: path.join(__dirname, srcDir + 'options.ts'), + permissions: path.join(__dirname, srcDir + 'permissions.ts') }, output: { path: path.join(__dirname, '../dist/js'),