From 966e6a169aeaa0a162fffb847e5d83274602265a Mon Sep 17 00:00:00 2001 From: Rasz_pl Date: Wed, 19 Jun 2024 08:31:29 +0200 Subject: [PATCH 01/17] Update core.js properly initialize blocklist storage --- js&css/extension/core.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/js&css/extension/core.js b/js&css/extension/core.js index 6d0e44987..6a61e12b3 100644 --- a/js&css/extension/core.js +++ b/js&css/extension/core.js @@ -305,6 +305,16 @@ extension.storage.load = function (callback) { chrome.storage.local.get(function (items) { extension.storage.data = items; + if (!extension.storage.data.blocklist) { + extension.storage.data.blocklist = {}; + } + if (!extension.storage.data.blocklist.channels) { + extension.storage.data.blocklist.channels = {}; + } + if (!extension.storage.data.blocklist.videos) { + extension.storage.data.blocklist.videos = {}; + } + // initialize theme in case YT is in Dark cookie mode if (!extension.storage.data['theme'] && document.documentElement.hasAttribute('dark')) { extension.storage.data['theme'] = 'dark'; From 261bf2f2a5fdcb834130f819bae7bb08ef67b8db Mon Sep 17 00:00:00 2001 From: Rasz_pl Date: Wed, 19 Jun 2024 09:16:27 +0200 Subject: [PATCH 02/17] Update core.js now works --- js&css/extension/core.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/js&css/extension/core.js b/js&css/extension/core.js index 6a61e12b3..a0010cb23 100644 --- a/js&css/extension/core.js +++ b/js&css/extension/core.js @@ -303,17 +303,17 @@ extension.storage.listener = function () { extension.storage.load = function (callback) { chrome.storage.local.get(function (items) { - extension.storage.data = items; - - if (!extension.storage.data.blocklist) { - extension.storage.data.blocklist = {}; + // initialize Blocklist + if (!items.blocklist) { + items.blocklist = {}; } - if (!extension.storage.data.blocklist.channels) { - extension.storage.data.blocklist.channels = {}; + if (!items.blocklist.channels) { + items.blocklist.channels = {}; } - if (!extension.storage.data.blocklist.videos) { - extension.storage.data.blocklist.videos = {}; + if (!items.blocklist.videos) { + items.blocklist.videos = {}; } + extension.storage.data = items; // initialize theme in case YT is in Dark cookie mode if (!extension.storage.data['theme'] && document.documentElement.hasAttribute('dark')) { From 847fdce4aeb11602a5be705bb5a17cfc584d06d8 Mon Sep 17 00:00:00 2001 From: Rasz_pl Date: Wed, 19 Jun 2024 23:12:03 +0200 Subject: [PATCH 03/17] Update styles.css blocklist fixing/adding search/subscriptions with thumbnails Previews --- js&css/extension/www.youtube.com/styles.css | 46 +++++++++++++++------ 1 file changed, 33 insertions(+), 13 deletions(-) diff --git a/js&css/extension/www.youtube.com/styles.css b/js&css/extension/www.youtube.com/styles.css index d017b09e7..b09e08e58 100644 --- a/js&css/extension/www.youtube.com/styles.css +++ b/js&css/extension/www.youtube.com/styles.css @@ -365,9 +365,9 @@ html[it-channel-hide-featured-content=true] #secondary ytd-browse-secondary-cont content: ''; border-radius: 50%; background: #f00; - /* background-image: url('/stuff/icons/48.png'); - background-size: cover; - background-position: center; */ + /* background-image: url('/stuff/icons/48.png'); + background-size: cover; + background-position: center; */ } .it-button::after { @@ -463,7 +463,7 @@ ytd-guide-section-renderer .it-button::after { position: absolute; top: 4px; left: 4px; - z-index: 999; + z-index: 2400; visibility: hidden; width: 28px; height: 28px; @@ -530,6 +530,8 @@ ytd-guide-section-renderer .it-button::after { cursor: pointer; } +ytd-video-preview.it-blocklisted-video:hover .it-add-to-blocklist, +ytd-video-preview.it-blocklisted-channel:hover .it-add-to-blocklist, *:hover>.it-add-to-blocklist { visibility: visible; } @@ -545,11 +547,11 @@ ytd-guide-section-renderer .it-button::after { } .it-blocklisted-video .it-add-to-blocklist::after { - content: "Unblock Video"; + content: "Unblock Video"; } .it-blocklisted-channel .it-add-to-blocklist::after { - content: "Unblock Channel"!important; + content: "Unblock Channel"!important; } .it-blocklisted-video, @@ -560,11 +562,27 @@ ytd-guide-section-renderer .it-button::after { transition: max-height 0.4s ease 0.1s; } +.it-blocklisted-video.ytd-vertical-list-renderer, +.it-blocklisted-video.ytd-item-section-renderer, +.it-blocklisted-channel.ytd-vertical-list-renderer, +.it-blocklisted-channel.ytd-item-section-renderer { + max-height: 120px; +} + ytd-grid-video-renderer .it-blocklisted-video, ytd-grid-video-renderer .it-blocklisted-channel, ytd-rich-grid-media .it-blocklisted-video, ytd-rich-grid-media .it-blocklisted-channel { overflow: visible; + max-height: 120px; +} + +ytd-video-preview.it-blocklisted-video:hover .it-add-to-blocklist, +ytd-video-preview.it-blocklisted-channel:hover .it-add-to-blocklist, +body:has(.it-blocklisted-video:hover) ytd-video-preview.it-blocklisted-video, +body:has(.it-blocklisted-channel:hover) ytd-video-preview.it-blocklisted-channel { + max-height: var(--ytd-video-preview-height); + opacity: 1; } .it-blocklisted-video ytd-thumbnail, @@ -578,7 +596,8 @@ ytd-rich-grid-media .it-blocklisted-channel { .it-blocklisted-channel:hover { opacity: 1; overflow: visible; - max-height: 120px; + height: fit-content; + max-height: fit-content; transition: max-height 0.4s ease 1.1s; } @@ -587,6 +606,7 @@ ytd-rich-grid-media .it-blocklisted-channel { visibility: visible; max-width: 220px; transition: max-width 0.4s ease 1.1s; + height: fit-content; } /*------------NEW---------------*/ @@ -1975,7 +1995,7 @@ html[it-theme=sunset][data-system-color-scheme=light][it-schedule=system_peferen --ytd-simple-badge-color: hsla(0, 0%, 100%, .6); --ytd-ad-badge-text-color: hsl(0, 0%, 7%); --ytd-shopping-product-info: hsla(0, 100%, 100%, .74); - --ytd-toggle-color: hsl(0, 0%, 93.3%); + --ytd-toggle-color: hsl(0, 0%, 93.3%); --ytd-survey-button-color: var(--yt-primary-text-color); --ytd-transcript-cue-hover-background-color: hsla(0, 0%, 53.3%, .4); --ytd-transcript-toolbar-background-color: hsla(0, 0%, 53.3%, .4); @@ -2076,7 +2096,7 @@ Need HTML in front to make CSS rule more specific than one they are overiding /*possible fix: #hover-overlays .yt-spec-icon-shape, -ytd-thumbnail-overlay-toggle-button-renderer .yt-spec-icon-shape {color:white;} +ytd-thumbnail-overlay-toggle-button-renderer .yt-spec-icon-shape {color:white;} */ /* html .yt-spec-icon-shape, */ @@ -2084,12 +2104,12 @@ html .yt-spec-icon-badge-shape--style-overlay .yt-spec-icon-badge-shape__icon, html .yt-spec-button-shape-next--mono.yt-spec-button-shape-next--text, html .yt-spec-button-shape-next--mono.yt-spec-button-shape-next--tonal, html .yt-video-attribute-view-model__title { - color: var(--yt-spec-text-primary); + color: var(--yt-spec-text-primary); } /*Dark colors get highlight*/ html .yt-spec-button-shape-next--mono.yt-spec-button-shape-next--tonal { - background-color: rgba(255, 255, 255, 0.1); + background-color: rgba(255, 255, 255, 0.1); } /*Light colors get shadow, overrides above highlight*/ html[it-theme=desert] .yt-spec-button-shape-next--mono.yt-spec-button-shape-next--tonal, @@ -2100,8 +2120,8 @@ html:not([dark]):not([it-theme=black]):not([it-theme=sunset]):not([it-theme=nigh /*subscribe button when not subscribed*/ html .yt-spec-button-shape-next--mono.yt-spec-button-shape-next--filled { - color: var(--yt-spec-base-background); - background-color: var(--yt-spec-text-primary); + color: var(--yt-spec-base-background); + background-color: var(--yt-spec-text-primary); } /*override bell and thumbs up icons hardcoded colors inside SVG data*/ From 0e9a189071f0abbe20b7c74bdb9e5640d6f8c26b Mon Sep 17 00:00:00 2001 From: Rasz_pl Date: Wed, 19 Jun 2024 23:13:31 +0200 Subject: [PATCH 04/17] Update core.js reverting, turns out its not needed --- js&css/extension/core.js | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/js&css/extension/core.js b/js&css/extension/core.js index a0010cb23..6d0e44987 100644 --- a/js&css/extension/core.js +++ b/js&css/extension/core.js @@ -303,16 +303,6 @@ extension.storage.listener = function () { extension.storage.load = function (callback) { chrome.storage.local.get(function (items) { - // initialize Blocklist - if (!items.blocklist) { - items.blocklist = {}; - } - if (!items.blocklist.channels) { - items.blocklist.channels = {}; - } - if (!items.blocklist.videos) { - items.blocklist.videos = {}; - } extension.storage.data = items; // initialize theme in case YT is in Dark cookie mode From ca8483379c4ae8567ebfbc6e8efdb80af87a45c2 Mon Sep 17 00:00:00 2001 From: Rasz_pl Date: Thu, 20 Jun 2024 01:22:35 +0200 Subject: [PATCH 05/17] Update blocklist.js safety --- .../www.youtube.com/blocklist.js | 406 ++++++++++-------- 1 file changed, 217 insertions(+), 189 deletions(-) diff --git a/js&css/web-accessible/www.youtube.com/blocklist.js b/js&css/web-accessible/www.youtube.com/blocklist.js index 9a7cef9e4..307c8023c 100644 --- a/js&css/web-accessible/www.youtube.com/blocklist.js +++ b/js&css/web-accessible/www.youtube.com/blocklist.js @@ -8,192 +8,218 @@ ImprovedTube.blocklist = function (type, node) { if (this.storage.blocklist_activate) { - if (type === 'video') { - if (node.nodeName !== 'A' || !node.href) { alert(1) }; - const video = node.href.match(ImprovedTube.regex.video_id)?.[1], - channel = node.parentNode.__dataHost?.__data?.data?.shortBylineText?.runs?.[0]?.navigationEndpoint?.commandMetadata?.webCommandMetadata?.url ? node.parentNode.__dataHost.__data.data.shortBylineText.runs[0].navigationEndpoint.commandMetadata.webCommandMetadata.url.match(ImprovedTube.regex.channel).groups.name : undefined; - let mode = 'video', - blockedElement; - if (!video) return; // no video ID, something went horribly wrong, bail - - // YT reuses VIDEO elements dynamically, need to monitor and also dynamically readjust BLOCK style - if (!this.elements.observerList.includes(node)) { - // YT reuses VIDEO elements dynamically, need to monitor and also dynamically readjust BLOCK style whenever href is modified - this.blocklistObserver.observe(node, {attributes: true, - attributeFilter: ['href']}); - // keep track to only attach one observer per element - this.elements.observerList.push(node); - } - - switch(node.parentNode.className.replace('style-scope ','')) { - case 'ytd-compact-video-renderer': - // list next to player - // node.parentNode.__dataHost.$.dismissible; - case 'ytd-rich-item-renderer': - // short reel - case 'ytd-rich-grid-media': - // grid reel - case 'ytd-rich-grid-slim-media': - // short grid reel - case 'ytd-playlist-video-renderer': - // playlist page - case 'ytd-playlist-panel-video-renderer': - // playlist next to player - // node.parentNode.closest('ytd-playlist-panel-video-renderer') - case 'ytd-structured-description-video-lockup-renderer': - // list under the player - // node.parentNode.closest('ytd-structured-description-video-lockup-renderer') - // or even node.parentNode.closest('ytd-compact-infocard-renderer') === node.parentNode.parentNode.parentNode.parentNode - blockedElement = node.parentNode.parentNode.parentNode; - break; - case 'ytd-grid-video-renderer': - // channel home screen grid - case 'ytd-reel-item-renderer': - // reel - blockedElement = node.parentNode.parentNode; - break; - } - - if (!blockedElement) return; // couldnt find valid enveloping element, bail - - node.blockedElement = blockedElement; - if (this.storage.blocklist) { - if (this.storage.blocklist.videos) { - if (this.storage.blocklist.videos[video] && !blockedElement.classList.contains('it-blocklisted-video')) { - // blocklisted video - blockedElement.classList.add('it-blocklisted-video'); - } else if (!this.storage.blocklist.videos[video] && blockedElement.classList.contains('it-blocklisted-video')) { - // video not blocklisted, show it - blockedElement.classList.remove('it-blocklisted-video'); - } - } - if (channel && this.storage.blocklist.channels ) { - // this thumbnail has channel information, can try channel blocklist - if (this.storage.blocklist.channels[channel] && !blockedElement.classList.contains('it-blocklisted-channel')) { - // blocked channel? = block all videos from that channel - blockedElement.classList.add('it-blocklisted-channel'); - } else if (!this.storage.blocklist.channels[channel] && blockedElement.classList.contains('it-blocklisted-channel')) { - // channel not blocked, show it - blockedElement.classList.remove('it-blocklisted-channel'); + + switch(type) { + case 'video': + const video = node?.href?.match(ImprovedTube.regex.video_id)?.[1] || (node?.classList?.contains('ytd-video-preview')?'video-preview':undefined), + channel = node?.parentNode?.__dataHost?.__data?.data?.shortBylineText?.runs?.[0]?.navigationEndpoint?.commandMetadata?.webCommandMetadata?.url?.match(ImprovedTube.regex.channel)?.groups?.name; + let blockedElement; + + if (!video) return; // no video ID, something went horribly wrong, bail + + // YT reuses VIDEO elements dynamically, need to monitor and also dynamically readjust BLOCK style + if (!this.elements.observerList.includes(node)) { + // YT reuses VIDEO elements dynamically, need to monitor and also dynamically readjust BLOCK style whenever href is modified + this.blocklistObserver.observe(node, {attributes: true, + attributeFilter: ['href']}); + // keep track to only attach one observer per element + this.elements.observerList.push(node); } - } - } - if (node.querySelector("button.it-add-to-blocklist")) return; // skip blocklist button creation if one already exists + + switch(node.parentNode.className.replace('style-scope ','')) { + case 'ytd-compact-video-renderer': + // list next to player + // node.parentNode.__dataHost.$.dismissible; + case 'ytd-rich-item-renderer': + // short reel + case 'ytd-rich-grid-media': + // grid reel + case 'ytd-rich-grid-slim-media': + // short grid reel + case 'ytd-playlist-video-renderer': + // playlist page + case 'ytd-playlist-panel-video-renderer': + // playlist next to player + // node.parentNode.closest('ytd-playlist-panel-video-renderer') + case 'ytd-structured-description-video-lockup-renderer': + // list under the player + // node.parentNode.closest('ytd-structured-description-video-lockup-renderer') + // or even node.parentNode.closest('ytd-compact-infocard-renderer') === node.parentNode.parentNode.parentNode.parentNode + case 'ytd-video-renderer': + // search results + case 'ytd-video-preview': + // subscriptions/search thumbnail video-preview + blockedElement = node.parentNode.parentNode.parentNode; + break; + + case 'ytd-grid-video-renderer': + // channel home screen grid + case 'ytd-reel-item-renderer': + // reel + blockedElement = node.parentNode.parentNode; + break; + + default: + // unknown, bail out + return; + break; + } + + node.blockedElement = blockedElement; + + if (this.storage.blocklist) { + if (this.storage.blocklist.videos && ImprovedTube.storage.blocklist.videos[video]) { + // blocklisted video + blockedElement.classList.add('it-blocklisted-video'); + } else { + // video not blocklisted, show it + blockedElement.classList.remove('it-blocklisted-video'); + } + if (this.storage.blocklist.channels && channel && ImprovedTube.storage.blocklist.channels[channel]) { + // blocked channel + blockedElement.classList.add('it-blocklisted-channel'); + } else { + // channel not blocked, show it + blockedElement.classList.remove('it-blocklisted-channel'); + } + } + + if (node.querySelector("button.it-add-to-blocklist")) return; // skip blocklist button creation if one already exists + + let buttonV = document.createElement('button'), + svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'), + path = document.createElementNS('http://www.w3.org/2000/svg', 'path'); + + buttonV.className = 'it-add-to-blocklist'; + buttonV.addEventListener('click', function (event) { + event.preventDefault(); + event.stopPropagation(); + if (this.parentNode.href) { + const video = this.parentNode.href?.match(ImprovedTube.regex.video_id)?.[1], + channel = this.parentNode.parentNode?.__dataHost?.__data?.data?.shortBylineText?.runs?.[0]?.navigationEndpoint?.commandMetadata?.webCommandMetadata?.url?.match(ImprovedTube.regex.channel)?.groups?.name; + + // Yes, this is horrible. Cant find better way of extracting title :( + title = this.parentNode?.__dataHost?.__data?.data?.title?.runs?.[0]?.text + || this.parentNode.__dataHost?.__data?.data?.title?.simpleText + || this.parentNode.__dataHost?.__data?.videoPreviewData?.accessibilityText, + blockedElement = node.blockedElement; + let added = false, + type = 'video'; + + if (!video || !blockedElement || !title) { + console.error('blocklist: need video ID, blockedElement and title'); + return; + } - let button = document.createElement('button'), - svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'), - path = document.createElementNS('http://www.w3.org/2000/svg', 'path'); + if (channel && blockedElement.classList.contains('it-blocklisted-channel')) { + // unblocking channel + type = 'channel'; + } else if (blockedElement.classList.contains('it-blocklisted-video')) { + // unblocking blocklisted video + } else { + // nothing blocked, clicking should block this video + added = true; + } + ImprovedTube.messages.send({action: 'blocklist', + added: added, + type: type, + id: type == 'channel' ? channel : video, + title: title}); + } + }, true); + + svg.setAttributeNS(null, 'viewBox', '0 0 24 24'); + path.setAttributeNS(null, 'd', 'M12 2a10 10 0 100 20 10 10 0 000-20zm0 18A8 8 0 015.69 7.1L16.9 18.31A7.9 7.9 0 0112 20zm6.31-3.1L7.1 5.69A8 8 0 0118.31 16.9z'); + + svg.appendChild(path); + buttonV.appendChild(svg); + + node.appendChild(buttonV); + this.elements.blocklist_buttons.push(buttonV); + break; - button.className = 'it-add-to-blocklist'; - button.addEventListener('click', function (event) { - if (this.parentNode.href) { + case 'channel': + let buttonC = node?.parentNode?.parentNode?.querySelector("button.it-add-channel-to-blocklist"), + id = location.href.match(ImprovedTube.regex.channel)?.groups?.name; - const video = node.href.match(ImprovedTube.regex.video_id)?.[1], - channel = node.parentNode.__dataHost?.__data?.data?.shortBylineText?.runs?.[0]?.navigationEndpoint?.commandMetadata?.webCommandMetadata?.url ? node.parentNode.__dataHost.__data.data.shortBylineText.runs[0].navigationEndpoint.commandMetadata.webCommandMetadata.url.match(ImprovedTube.regex.channel).groups.name : undefined, - data = this.parentNode.__dataHost.__data?.data?.title, - blockedElement = node.blockedElement; - let title, - added = false, - type = 'video'; + if (!id) { + console.error('blocklist: no channel ID'); + return; + } - if (!video || !blockedElement) return; // need both video ID and blockedElement, otherwise bail + // skip channel blocklist button creation if one already exists + if (buttonC) { + if (this.storage.blocklist.channels[id]) { + buttonC.innerText = 'Remove from blocklist'; + } else if (!this.storage.blocklist.channels[id]) { + buttonC.innerText = 'Add to blocklist'; + } + return; + } + + buttonC = document.createElement('button'); + buttonC.className = 'it-add-channel-to-blocklist'; + + if (this.storage.blocklist.channels[id]) { + buttonC.innerText = 'Remove from blocklist'; + } else { + buttonC.innerText = 'Add to blocklist'; + } + + buttonC.addEventListener('click', function (event) { + const data = ytInitialData?.metadata?.channelMetadataRenderer, + //let data = this.parentNode.__dataHost.__data.data, + id = location.href.match(ImprovedTube.regex.channel)?.groups?.name; + let added = false; - if (data?.runs?.[0]?.text) { - title = data.runs[0].text; - } else if (data?.simpleText) { - title = data.simpleText; + event.preventDefault(); + event.stopPropagation(); + + if (!id || !data) { + console.error('blocklist click: no channel ID or metadata'); + return; } - if (channel && blockedElement.classList.contains('it-blocklisted-channel')) { - // unblocking channel - type = 'channel'; - } else if (blockedElement.classList.contains('it-blocklisted-video')) { - // unblocking blocklisted video + if (ImprovedTube.storage.blocklist.channels[id]) { + delete ImprovedTube.storage.blocklist.channels[id]; + buttonC.innerText = 'Add to blocklist'; } else { - // nothing blocked, clicking should block this video + ImprovedTube.storage.blocklist.channels[id] = {title: data.title, + preview: data.avatar?.thumbnails?.[0]?.url}; + buttonC.innerText = 'Remove from blocklist'; added = true; } ImprovedTube.messages.send({action: 'blocklist', added: added, - type: type, - id: type == 'channel' ? channel : video, - title: title}); - event.preventDefault(); - event.stopPropagation(); - } - }, true); + type: 'channel', + id: id, + title: data.title, + preview: data.avatar?.thumbnails[0].url}); + }, true); + + node.parentNode.parentNode.appendChild(buttonC); + this.elements.blocklist_buttons.push(buttonC); + break; - svg.setAttributeNS(null, 'viewBox', '0 0 24 24'); - path.setAttributeNS(null, 'd', 'M12 2a10 10 0 100 20 10 10 0 000-20zm0 18A8 8 0 015.69 7.1L16.9 18.31A7.9 7.9 0 0112 20zm6.31-3.1L7.1 5.69A8 8 0 0118.31 16.9z'); - - svg.appendChild(path); - button.appendChild(svg); - - node.appendChild(button); - this.elements.blocklist_buttons.push(button); - } else if (type === 'channel') { - let button = node.parentNode.parentNode.querySelector("button.it-add-channel-to-blocklist"), - id = location.href.match(ImprovedTube.regex.channel).groups.name; - - // skip channel blocklist button creation if one already exists - if (button) { - if (this.storage.blocklist.channels[id] && button.added) { - button.innerText = 'Remove from blocklist'; - button.added = false; - } else if (!this.storage.blocklist.channels[id] && !button.added) { - button.innerText = 'Add to blocklist'; - button.added = true; + default: + // initialize and scan whole page. Called only once on load after 'storage-loaded'. + if (!this.storage.blocklist) { + this.storage.blocklist = {videos: {}, channels: {}}; } - return; - } - - button = document.createElement('button'); - button.className = 'it-add-channel-to-blocklist'; - - if (this.storage.blocklist.channels[id]) { - button.innerText = 'Remove from blocklist'; - button.added = false; - } else { - button.innerText = 'Add to blocklist'; - button.added = true; - } - - button.addEventListener('click', function (event) { - const data = ytInitialData.metadata.channelMetadataRenderer, - //let data = this.parentNode.__dataHost.__data.data, - id = location.href.match(ImprovedTube.regex.channel).groups.name; - - if (this.added) { // adding - ImprovedTube.storage.blocklist.channels[id] = {title: data.title, - preview: data.avatar.thumbnails[0].url}; - button.innerText = 'Remove from blocklist'; - } else { // removing - delete ImprovedTube.storage.blocklist.channels[id]; - button.innerText = 'Add to blocklist'; + if (!this.storage.blocklist.videos) { + this.storage.blocklist.videos = {}; } - ImprovedTube.messages.send({action: 'blocklist', - added: this.added, - type: 'channel', - id: id, - title: data.title, - preview: data.avatar.thumbnails[0].url}); - this.added = !this.added; - - event.preventDefault(); - event.stopPropagation(); - }, true); - - node.parentNode.parentNode.appendChild(button); - this.elements.blocklist_buttons.push(button); - } else if (arguments.length == 0) { - // scan whole page - for (let thumbnails of document.querySelectorAll('a.ytd-thumbnail[href]')) { - this.blocklist('video', thumbnails); - } - if (document.querySelector('YT-SUBSCRIBE-BUTTON-VIEW-MODEL')) { - this.blocklist('channel', document.querySelector('YT-SUBSCRIBE-BUTTON-VIEW-MODEL')); + if (!this.storage.blocklist.channels) { + this.storage.blocklist.channels = {}; + } + for (let thumbnails of document.querySelectorAll('a.ytd-thumbnail[href], a.ytd-video-preview')) { + this.blocklist('video', thumbnails); + } + if (document.querySelector('YT-SUBSCRIBE-BUTTON-VIEW-MODEL')) { + this.blocklist('channel', document.querySelector('YT-SUBSCRIBE-BUTTON-VIEW-MODEL')); + } + break; } - } } else { // remove blocklist buttons for (let blocked of this.elements.blocklist_buttons) { @@ -207,39 +233,41 @@ ImprovedTube.blocklist = function (type, node) { ImprovedTube.blocklistObserver.disconnect(); } // remove all blocks from videos\channels - for (let blocked of document.querySelectorAll('.it-blocklisted-video, .it-blocklisted-channel')) { + for (let blocked of document.querySelectorAll('.it-blocklisted-video')) { blocked.classList.remove('it-blocklisted-video'); + } + for (let blocked of document.querySelectorAll('.it-blocklisted-channel')) { blocked.classList.remove('it-blocklisted-channel'); } } }; ImprovedTube.blocklistObserver = new MutationObserver(function (mutationList) { - for (var mutation of mutationList) { - const video = mutation.target.href.match(ImprovedTube.regex.video_id)?.[1], - channel = mutation.target.parentNode.__dataHost?.__data?.data?.shortBylineText?.runs?.[0]?.navigationEndpoint?.commandMetadata?.webCommandMetadata?.url ? mutation.target.parentNode.__dataHost.__data.data.shortBylineText.runs[0].navigationEndpoint.commandMetadata.webCommandMetadata.url.match(ImprovedTube.regex.channel).groups.name : undefined, + for (const mutation of mutationList) { + const video = mutation.target.href?.match(ImprovedTube.regex.video_id)?.[1], + channel = mutation.target.parentNode?.__dataHost?.__data?.data?.shortBylineText?.runs?.[0]?.navigationEndpoint?.commandMetadata?.webCommandMetadata?.url?.match(ImprovedTube.regex.channel)?.groups?.name, blockedElement = mutation.target.blockedElement; - if (!video || !blockedElement) return; // need both video ID and blockedElement, otherwise bail + if (!blockedElement) return; // missing blockedElement? lets panic and run away! + + if (!video) { + // nop video ID means most likely video-preview node went inactive + blockedElement.classList.remove('it-blocklisted-video'); + blockedElement.classList.remove('it-blocklisted-channel'); + return; + } - if (ImprovedTube.storage.blocklist.videos[video]) { - if (!blockedElement.classList.contains('it-blocklisted-video')) { + if (ImprovedTube.storage.blocklist) { + if (ImprovedTube.storage.blocklist.videos && ImprovedTube.storage.blocklist.videos[video]) { blockedElement.classList.add('it-blocklisted-video'); - } - } else { - if (blockedElement.classList.contains('it-blocklisted-video')) { + } else { blockedElement.classList.remove('it-blocklisted-video'); } + if (ImprovedTube.storage.blocklist.channels && channel && ImprovedTube.storage.blocklist.channels[channel]) { + blockedElement.classList.add('it-blocklisted-channel'); + } else { + blockedElement.classList.remove('it-blocklisted-channel'); + } } - - - if (channel && ImprovedTube.storage.blocklist.channels[channel] && !blockedElement.classList.contains('it-blocklisted-channel')) { - // blocked channel? = block all videos from that channel - blockedElement.classList.add('it-blocklisted-channel'); - } else if ((!channel || !ImprovedTube.storage.blocklist.channels[channel]) && blockedElement.classList.contains('it-blocklisted-channel')) { - // channel not blocked, show it - blockedElement.classList.remove('it-blocklisted-channel'); - } - } }); From 6b0c4ff18a20229838b537d4f6a7df05c025dbee Mon Sep 17 00:00:00 2001 From: Rasz_pl Date: Thu, 20 Jun 2024 01:29:48 +0200 Subject: [PATCH 06/17] Update functions.js blocklist also works on video previews now, moving commented stuff to more logical place --- js&css/web-accessible/functions.js | 34 +++++------------------------- 1 file changed, 5 insertions(+), 29 deletions(-) diff --git a/js&css/web-accessible/functions.js b/js&css/web-accessible/functions.js index 02530b626..33aa4dca8 100644 --- a/js&css/web-accessible/functions.js +++ b/js&css/web-accessible/functions.js @@ -33,43 +33,19 @@ ImprovedTube.childHandler = function (node) { //console.log(node.nodeName); }; */ ImprovedTube.ytElementsHandler = function (node) { - var name = node.nodeName, + const name = node.nodeName, id = node.id; if (name === 'A') { if (node.href) { this.channelDefaultTab(node); - - if (this.storage.blocklist_activate && node.classList.contains('ytd-thumbnail')) { + } + if (this.storage.blocklist_activate) { + if (node.classList.contains('ytd-video-preview') || (node.href && (node.classList.contains('ytd-thumbnail')))) { this.blocklist('video', node); } } - } /* else if (name === 'META') { // infos are not updated when clicking related videos... - if(node.getAttribute('name')) { - //if(node.getAttribute('name') === 'title') {ImprovedTube.title = node.content;} //duplicate - //if(node.getAttribute('name') === 'description') {ImprovedTube.description = node.content;} //duplicate - //if node.getAttribute('name') === 'themeColor') {ImprovedTube.themeColor = node.content;} //might help our darkmode/themes -//Do we need any of these here before the player starts? - //if(node.getAttribute('name') === 'keywords') {ImprovedTube.keywords = node.content;} - } else if (node.getAttribute('itemprop')) { - //if(node.getAttribute('itemprop') === 'name') {ImprovedTube.title = node.content;} - if(node.getAttribute('itemprop') === 'genre') {ImprovedTube.category = node.content;} - //if(node.getAttribute('itemprop') === 'channelId') {ImprovedTube.channelId = node.content;} - //if(node.getAttribute('itemprop') === 'videoId') {ImprovedTube.videoId = node.content;} -//The following infos will enable awesome, smart features. Some of which everyone should use. - //if(node.getAttribute('itemprop') === 'description') {ImprovedTube.description = node.content;} - //if(node.getAttribute('itemprop') === 'duration') {ImprovedTube.duration = node.content;} - //if(node.getAttribute('itemprop') === 'interactionCount'){ImprovedTube.views = node.content;} - //if(node.getAttribute('itemprop') === 'isFamilyFriendly'){ImprovedTube.isFamilyFriendly = node.content;} - //if(node.getAttribute('itemprop') === 'unlisted') {ImprovedTube.unlisted = node.content;} - //if(node.getAttribute('itemprop') === 'regionsAllowed'){ImprovedTube.regionsAllowed = node.content;} - //if(node.getAttribute('itemprop') === 'paid') {ImprovedTube.paid = node.content;} - // if(node.getAttribute('itemprop') === 'datePublished' ){ImprovedTube.datePublished = node.content;} - //to use in the "how long ago"-feature, not to fail without API key? just like the "day-of-week"-feature above - // if(node.getAttribute('itemprop') === 'uploadDate') {ImprovedTube.uploadDate = node.content;} - } - } */ - else if (name === 'YTD-TOGGLE-BUTTON-RENDERER' || name === 'YTD-PLAYLIST-LOOP-BUTTON-RENDERER') { + } else if (name === 'YTD-TOGGLE-BUTTON-RENDERER' || name === 'YTD-PLAYLIST-LOOP-BUTTON-RENDERER') { //can be precise previously node.parentComponent & node.parentComponent.parentComponent if (node.closest("YTD-MENU-RENDERER") && node.closest("YTD-PLAYLIST-PANEL-RENDERER")) { From 895cb7410f8ccb1f49e6071e3c516772b15a55f3 Mon Sep 17 00:00:00 2001 From: Rasz_pl Date: Thu, 20 Jun 2024 01:32:29 +0200 Subject: [PATCH 07/17] Update init.js better place for stuff initialized on page load --- js&css/web-accessible/init.js | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/js&css/web-accessible/init.js b/js&css/web-accessible/init.js index b4f5e01a8..66c09bcd9 100644 --- a/js&css/web-accessible/init.js +++ b/js&css/web-accessible/init.js @@ -121,6 +121,30 @@ ImprovedTube.init = function () { }; document.addEventListener('yt-navigate-finish', function () { +/* if (name === 'META') { // infos are not updated when clicking related videos... + if(node.getAttribute('name')) { + //if(node.getAttribute('name') === 'title') {ImprovedTube.title = node.content;} //duplicate + //if(node.getAttribute('name') === 'description') {ImprovedTube.description = node.content;} //duplicate + //if node.getAttribute('name') === 'themeColor') {ImprovedTube.themeColor = node.content;} //might help our darkmode/themes +//Do we need any of these here before the player starts? + //if(node.getAttribute('name') === 'keywords') {ImprovedTube.keywords = node.content;} + } else if (node.getAttribute('itemprop')) { + //if(node.getAttribute('itemprop') === 'name') {ImprovedTube.title = node.content;} + if(node.getAttribute('itemprop') === 'genre') {ImprovedTube.category = node.content;} + //if(node.getAttribute('itemprop') === 'channelId') {ImprovedTube.channelId = node.content;} + //if(node.getAttribute('itemprop') === 'videoId') {ImprovedTube.videoId = node.content;} +//The following infos will enable awesome, smart features. Some of which everyone should use. + //if(node.getAttribute('itemprop') === 'description') {ImprovedTube.description = node.content;} + //if(node.getAttribute('itemprop') === 'duration') {ImprovedTube.duration = node.content;} + //if(node.getAttribute('itemprop') === 'interactionCount'){ImprovedTube.views = node.content;} + //if(node.getAttribute('itemprop') === 'isFamilyFriendly'){ImprovedTube.isFamilyFriendly = node.content;} + //if(node.getAttribute('itemprop') === 'unlisted') {ImprovedTube.unlisted = node.content;} + //if(node.getAttribute('itemprop') === 'regionsAllowed'){ImprovedTube.regionsAllowed = node.content;} + //if(node.getAttribute('itemprop') === 'paid') {ImprovedTube.paid = node.content;} + // if(node.getAttribute('itemprop') === 'datePublished' ){ImprovedTube.datePublished = node.content;} + //to use in the "how long ago"-feature, not to fail without API key? just like the "day-of-week"-feature above + // if(node.getAttribute('itemprop') === 'uploadDate') {ImprovedTube.uploadDate = node.content;} +*/ ImprovedTube.pageType(); if(ImprovedTube.storage.undo_the_new_sidebar === true){ImprovedTube.undoTheNewSidebar();} ImprovedTube.commentsSidebar(); From 4e40a621d27558f9232a8568da9c4dc69351079a Mon Sep 17 00:00:00 2001 From: Rasz_pl Date: Sat, 22 Jun 2024 16:31:54 +0200 Subject: [PATCH 08/17] Update styles.css blocklist tuning --- js&css/extension/www.youtube.com/styles.css | 24 ++++++++++----------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/js&css/extension/www.youtube.com/styles.css b/js&css/extension/www.youtube.com/styles.css index b09e08e58..49e4ea1ef 100644 --- a/js&css/extension/www.youtube.com/styles.css +++ b/js&css/extension/www.youtube.com/styles.css @@ -555,7 +555,7 @@ ytd-video-preview.it-blocklisted-channel:hover .it-add-to-blocklist, } .it-blocklisted-video, -.it-blocklisted-channel { +html:not([data-page-type=channel]) .it-blocklisted-channel { opacity: .15; max-height: 4rem; overflow: hidden; @@ -564,49 +564,49 @@ ytd-video-preview.it-blocklisted-channel:hover .it-add-to-blocklist, .it-blocklisted-video.ytd-vertical-list-renderer, .it-blocklisted-video.ytd-item-section-renderer, -.it-blocklisted-channel.ytd-vertical-list-renderer, -.it-blocklisted-channel.ytd-item-section-renderer { +html:not([data-page-type=channel]) .it-blocklisted-channel.ytd-vertical-list-renderer, +html:not([data-page-type=channel]) .it-blocklisted-channel.ytd-item-section-renderer { max-height: 120px; } ytd-grid-video-renderer .it-blocklisted-video, -ytd-grid-video-renderer .it-blocklisted-channel, +html:not([data-page-type=channel]) ytd-grid-video-renderer .it-blocklisted-channel, ytd-rich-grid-media .it-blocklisted-video, -ytd-rich-grid-media .it-blocklisted-channel { +html:not([data-page-type=channel]) ytd-rich-grid-media .it-blocklisted-channel { overflow: visible; max-height: 120px; } ytd-video-preview.it-blocklisted-video:hover .it-add-to-blocklist, -ytd-video-preview.it-blocklisted-channel:hover .it-add-to-blocklist, +html:not([data-page-type=channel]) ytd-video-preview.it-blocklisted-channel:hover .it-add-to-blocklist, body:has(.it-blocklisted-video:hover) ytd-video-preview.it-blocklisted-video, -body:has(.it-blocklisted-channel:hover) ytd-video-preview.it-blocklisted-channel { +html:not([data-page-type=channel]) body:has(.it-blocklisted-channel:hover) ytd-video-preview.it-blocklisted-channel { max-height: var(--ytd-video-preview-height); opacity: 1; } .it-blocklisted-video ytd-thumbnail, -.it-blocklisted-channel ytd-thumbnail { +html:not([data-page-type=channel]) .it-blocklisted-channel ytd-thumbnail { visibility: hidden; max-width: 0; transition: max-width 0.4s ease 0.1s; } .it-blocklisted-video:hover, -.it-blocklisted-channel:hover { +html:not([data-page-type=channel]) .it-blocklisted-channel:hover { opacity: 1; overflow: visible; - height: fit-content; + height: auto; max-height: fit-content; transition: max-height 0.4s ease 1.1s; } .it-blocklisted-video:hover ytd-thumbnail, -.it-blocklisted-channel:hover ytd-thumbnail { +html:not([data-page-type=channel]) .it-blocklisted-channel:hover ytd-thumbnail { visibility: visible; max-width: 220px; transition: max-width 0.4s ease 1.1s; - height: fit-content; + height: auto; } /*------------NEW---------------*/ From e43df364ecc7f47387808486202fee7c184a6670 Mon Sep 17 00:00:00 2001 From: Rasz_pl Date: Sat, 22 Jun 2024 16:32:45 +0200 Subject: [PATCH 09/17] Update blocklist.js full rewrite separate functions for node/channel/initialization --- .../www.youtube.com/blocklist.js | 421 +++++++++--------- 1 file changed, 204 insertions(+), 217 deletions(-) diff --git a/js&css/web-accessible/www.youtube.com/blocklist.js b/js&css/web-accessible/www.youtube.com/blocklist.js index 307c8023c..4eb64a488 100644 --- a/js&css/web-accessible/www.youtube.com/blocklist.js +++ b/js&css/web-accessible/www.youtube.com/blocklist.js @@ -1,227 +1,171 @@ /*------------------------------------------------------------------------------ 4.8.0 BLOCKLIST ------------------------------------------------------------------------------*/ -// usage: -// () called only to turn On (rescans all elements on page)/Off -// ('video', node) called only for 'a#thumbnail.ytd-thumbnail[href]' -// ('channel', node) called only for 'ytd-subscribe-button-renderer.ytd-c4-tabbed-header-renderer' +ImprovedTube.blocklistNode = function (node) { + if (!this.storage.blocklist_activate || !node) return; -ImprovedTube.blocklist = function (type, node) { - if (this.storage.blocklist_activate) { - - switch(type) { - case 'video': - const video = node?.href?.match(ImprovedTube.regex.video_id)?.[1] || (node?.classList?.contains('ytd-video-preview')?'video-preview':undefined), - channel = node?.parentNode?.__dataHost?.__data?.data?.shortBylineText?.runs?.[0]?.navigationEndpoint?.commandMetadata?.webCommandMetadata?.url?.match(ImprovedTube.regex.channel)?.groups?.name; - let blockedElement; - - if (!video) return; // no video ID, something went horribly wrong, bail - - // YT reuses VIDEO elements dynamically, need to monitor and also dynamically readjust BLOCK style - if (!this.elements.observerList.includes(node)) { - // YT reuses VIDEO elements dynamically, need to monitor and also dynamically readjust BLOCK style whenever href is modified - this.blocklistObserver.observe(node, {attributes: true, - attributeFilter: ['href']}); - // keep track to only attach one observer per element - this.elements.observerList.push(node); - } - - switch(node.parentNode.className.replace('style-scope ','')) { - case 'ytd-compact-video-renderer': - // list next to player - // node.parentNode.__dataHost.$.dismissible; - case 'ytd-rich-item-renderer': - // short reel - case 'ytd-rich-grid-media': - // grid reel - case 'ytd-rich-grid-slim-media': - // short grid reel - case 'ytd-playlist-video-renderer': - // playlist page - case 'ytd-playlist-panel-video-renderer': - // playlist next to player - // node.parentNode.closest('ytd-playlist-panel-video-renderer') - case 'ytd-structured-description-video-lockup-renderer': - // list under the player - // node.parentNode.closest('ytd-structured-description-video-lockup-renderer') - // or even node.parentNode.closest('ytd-compact-infocard-renderer') === node.parentNode.parentNode.parentNode.parentNode - case 'ytd-video-renderer': - // search results - case 'ytd-video-preview': - // subscriptions/search thumbnail video-preview - blockedElement = node.parentNode.parentNode.parentNode; - break; - - case 'ytd-grid-video-renderer': - // channel home screen grid - case 'ytd-reel-item-renderer': - // reel - blockedElement = node.parentNode.parentNode; - break; - - default: - // unknown, bail out - return; - break; - } - - node.blockedElement = blockedElement; - - if (this.storage.blocklist) { - if (this.storage.blocklist.videos && ImprovedTube.storage.blocklist.videos[video]) { - // blocklisted video - blockedElement.classList.add('it-blocklisted-video'); - } else { - // video not blocklisted, show it - blockedElement.classList.remove('it-blocklisted-video'); - } - if (this.storage.blocklist.channels && channel && ImprovedTube.storage.blocklist.channels[channel]) { - // blocked channel - blockedElement.classList.add('it-blocklisted-channel'); - } else { - // channel not blocked, show it - blockedElement.classList.remove('it-blocklisted-channel'); - } - } - - if (node.querySelector("button.it-add-to-blocklist")) return; // skip blocklist button creation if one already exists - - let buttonV = document.createElement('button'), - svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'), - path = document.createElementNS('http://www.w3.org/2000/svg', 'path'); - - buttonV.className = 'it-add-to-blocklist'; - buttonV.addEventListener('click', function (event) { - event.preventDefault(); - event.stopPropagation(); - if (this.parentNode.href) { - const video = this.parentNode.href?.match(ImprovedTube.regex.video_id)?.[1], - channel = this.parentNode.parentNode?.__dataHost?.__data?.data?.shortBylineText?.runs?.[0]?.navigationEndpoint?.commandMetadata?.webCommandMetadata?.url?.match(ImprovedTube.regex.channel)?.groups?.name; - - // Yes, this is horrible. Cant find better way of extracting title :( - title = this.parentNode?.__dataHost?.__data?.data?.title?.runs?.[0]?.text - || this.parentNode.__dataHost?.__data?.data?.title?.simpleText - || this.parentNode.__dataHost?.__data?.videoPreviewData?.accessibilityText, - blockedElement = node.blockedElement; - let added = false, - type = 'video'; - - if (!video || !blockedElement || !title) { - console.error('blocklist: need video ID, blockedElement and title'); - return; - } + const video = node.href?.match(ImprovedTube.regex.video_id)?.[1] || (node.classList?.contains('ytd-video-preview') ? 'video-preview' : null), + channel = node.parentNode?.__dataHost?.__data?.data?.shortBylineText?.runs?.[0]?.navigationEndpoint?.commandMetadata?.webCommandMetadata?.url?.match(ImprovedTube.regex.channel)?.groups?.name, + blockedElement = node.blockedElement || this.blockedElementTypeHelper(node); - if (channel && blockedElement.classList.contains('it-blocklisted-channel')) { - // unblocking channel - type = 'channel'; - } else if (blockedElement.classList.contains('it-blocklisted-video')) { - // unblocking blocklisted video - } else { - // nothing blocked, clicking should block this video - added = true; - } - ImprovedTube.messages.send({action: 'blocklist', - added: added, - type: type, - id: type == 'channel' ? channel : video, - title: title}); - } - }, true); - - svg.setAttributeNS(null, 'viewBox', '0 0 24 24'); - path.setAttributeNS(null, 'd', 'M12 2a10 10 0 100 20 10 10 0 000-20zm0 18A8 8 0 015.69 7.1L16.9 18.31A7.9 7.9 0 0112 20zm6.31-3.1L7.1 5.69A8 8 0 0118.31 16.9z'); - - svg.appendChild(path); - buttonV.appendChild(svg); - - node.appendChild(buttonV); - this.elements.blocklist_buttons.push(buttonV); - break; + if (!video) return; // not interested in nodes without one - case 'channel': - let buttonC = node?.parentNode?.parentNode?.querySelector("button.it-add-channel-to-blocklist"), - id = location.href.match(ImprovedTube.regex.channel)?.groups?.name; + // YT reuses Thumbnail cells dynamically, need to monitor all created Thumbnail links and dynamically apply/remove 'it-blocklisted-*' classes + if (!this.elements.observerList.includes(node)) { + this.blocklistObserver.observe(node, {attributes: true, + attributeFilter: ['href']}); + // keeping a list to attach only one observer per tracked element + this.elements.observerList.push(node); + } - if (!id) { - console.error('blocklist: no channel ID'); - return; - } + if (!blockedElement) return; // unknown thumbnail cell type, bail out - // skip channel blocklist button creation if one already exists - if (buttonC) { - if (this.storage.blocklist.channels[id]) { - buttonC.innerText = 'Remove from blocklist'; - } else if (!this.storage.blocklist.channels[id]) { - buttonC.innerText = 'Add to blocklist'; - } - return; - } - - buttonC = document.createElement('button'); - buttonC.className = 'it-add-channel-to-blocklist'; - - if (this.storage.blocklist.channels[id]) { - buttonC.innerText = 'Remove from blocklist'; - } else { - buttonC.innerText = 'Add to blocklist'; - } - - buttonC.addEventListener('click', function (event) { - const data = ytInitialData?.metadata?.channelMetadataRenderer, - //let data = this.parentNode.__dataHost.__data.data, - id = location.href.match(ImprovedTube.regex.channel)?.groups?.name; - let added = false; + if (this.storage.blocklist) { + if (this.storage.blocklist.videos && ImprovedTube.storage.blocklist.videos[video]) { + // blocklisted video + blockedElement.classList.add('it-blocklisted-video'); + } else { + // video not blocklisted, show it. classList.remove() directly as there is no speed benefit to .has() before + blockedElement.classList.remove('it-blocklisted-video'); + } + if (this.storage.blocklist.channels && channel && ImprovedTube.storage.blocklist.channels[channel]) { + // blocked channel + blockedElement.classList.add('it-blocklisted-channel'); + } else { + // channel not blocked, show it. + blockedElement.classList.remove('it-blocklisted-channel'); + } + } + + // skip blocklist button creation if one already exists, in theory this never happens due to check in functions.js + if (node.querySelector("button.it-add-to-blocklist")) return; + + node.blockedElement = blockedElement; - event.preventDefault(); - event.stopPropagation(); - - if (!id || !data) { - console.error('blocklist click: no channel ID or metadata'); - return; - } + const button = this.createIconButton({ + type: 'blocklist', + className: 'it-add-to-blocklist', + onclick: function (event) { + if (!this.parentNode.href) return; // no href no action + const video = this.parentNode.href?.match(ImprovedTube.regex.video_id)?.[1], + channel = this.parentNode.parentNode?.__dataHost?.__data?.data?.shortBylineText?.runs?.[0]?.navigationEndpoint?.commandMetadata?.webCommandMetadata?.url?.match(ImprovedTube.regex.channel)?.groups?.name, + blockedElement = node.blockedElement, - if (ImprovedTube.storage.blocklist.channels[id]) { - delete ImprovedTube.storage.blocklist.channels[id]; - buttonC.innerText = 'Add to blocklist'; - } else { - ImprovedTube.storage.blocklist.channels[id] = {title: data.title, - preview: data.avatar?.thumbnails?.[0]?.url}; - buttonC.innerText = 'Remove from blocklist'; - added = true; - } - ImprovedTube.messages.send({action: 'blocklist', - added: added, - type: 'channel', - id: id, - title: data.title, - preview: data.avatar?.thumbnails[0].url}); - }, true); - - node.parentNode.parentNode.appendChild(buttonC); - this.elements.blocklist_buttons.push(buttonC); - break; + // Yes, this is horrible. Cant find better way of extracting title :( + title = this.parentNode?.__dataHost?.__data?.data?.title?.runs?.[0]?.text + || this.parentNode.__dataHost?.__data?.data?.title?.simpleText + || this.parentNode.__dataHost?.__data?.videoPreviewData?.accessibilityText + || this.parentNode.blockedElement?.querySelector('[title]')?.title; + let added = false, + type = 'video'; + + if (!video || !blockedElement || !title) { + console.error('blocklist: need video ID, blockedElement and title'); + return; + } - default: - // initialize and scan whole page. Called only once on load after 'storage-loaded'. - if (!this.storage.blocklist) { - this.storage.blocklist = {videos: {}, channels: {}}; - } - if (!this.storage.blocklist.videos) { - this.storage.blocklist.videos = {}; - } - if (!this.storage.blocklist.channels) { - this.storage.blocklist.channels = {}; - } - for (let thumbnails of document.querySelectorAll('a.ytd-thumbnail[href], a.ytd-video-preview')) { - this.blocklist('video', thumbnails); - } - if (document.querySelector('YT-SUBSCRIBE-BUTTON-VIEW-MODEL')) { - this.blocklist('channel', document.querySelector('YT-SUBSCRIBE-BUTTON-VIEW-MODEL')); - } - break; + // this button can perform three functions: + if (channel && blockedElement.classList.contains('it-blocklisted-channel')) { + // unblocking whole channel + type = 'channel'; + } else if (blockedElement.classList.contains('it-blocklisted-video')) { + // unblocking blocklisted video + } else { + // block this video + added = true; } + + // this message will trigger 'storage-changed' event and eventually blocklistInit() full rescan + ImprovedTube.messages.send({action: 'blocklist', + added: added, + type: type, + id: type == 'channel' ? channel : video, + title: title, + when: Date.parse(new Date().toDateString()) / 100000 + }); + } + }); + + node.appendChild(button); + this.elements.blocklist_buttons.push(button); +}; + +ImprovedTube.blocklistChannel = function (node) { + if (!this.storage.blocklist_activate || !node) return; + + const id = location.pathname.match(ImprovedTube.regex.channel)?.groups?.name; + let button = node.parentNode?.parentNode?.querySelector("button.it-add-channel-to-blocklist"); + + if (!id) return; // not on channel page + + // skip button.it-add-channel-to-blocklist creation if one already exists, adjust text only + if (button) { + button.innerText = this.storage.blocklist.channels[id] ? 'Remove from blocklist' : 'Add to blocklist'; + return; + } + + button = document.createElement('button'); + + button.className = 'it-add-channel-to-blocklist'; + button.innerText = this.storage.blocklist.channels[id] ? 'Remove from blocklist' : 'Add to blocklist'; + button.onclick = function (event) { + event.preventDefault(); + event.stopPropagation(); + + const data = ytInitialData?.metadata?.channelMetadataRenderer, + // alternatives are: + //this.parentNode.__dataHost.__data.data, + // or directly scraping title and avatar from: + //title = yt-dynamic-text-view-model.yt-core-attributed-string .innerText + //avatar = a.id == 'thumbnail' && a.href?.match(ImprovedTube.regex.video_id)?.[1] === video).parentNode?.__dataHost?.__data?.data?.shortBylineText?.runs?.[0]?.navigationEndpoint?.commandMetadata?.webCommandMetadata?.url?.match(ImprovedTube.regex.channel)?.groups?.name : null), + blockedElement = ImprovedTube.blockedElementTypeHelper(mutation.target); + + if (!blockedElement) return; // unknown thumbnail cell type, bail out + mutation.target.blockedElement = blockedElement; - if (!blockedElement) return; // missing blockedElement? lets panic and run away! - if (!video) { - // nop video ID means most likely video-preview node went inactive + // no video ID means monitored thumbnail/video-preview node went inactive blockedElement.classList.remove('it-blocklisted-video'); blockedElement.classList.remove('it-blocklisted-channel'); return; @@ -271,3 +218,43 @@ ImprovedTube.blocklistObserver = new MutationObserver(function (mutationList) { } } }); + +ImprovedTube.blockedElementTypeHelper = function (node) { + switch(node.parentNode.className.replace('style-scope ','')) { + case 'ytd-compact-video-renderer': + // list next to player + // node.parentNode.__dataHost.$.dismissible; + case 'ytd-rich-item-renderer': + // short reel + case 'ytd-rich-grid-media': + // grid reel + case 'ytd-rich-grid-slim-media': + // short grid reel + case 'ytd-playlist-video-renderer': + // playlist page + case 'ytd-playlist-panel-video-renderer': + // playlist next to player + // node.parentNode.closest('ytd-playlist-panel-video-renderer') + case 'ytd-structured-description-video-lockup-renderer': + // list under the player + // node.parentNode.closest('ytd-structured-description-video-lockup-renderer') + // or even node.parentNode.closest('ytd-compact-infocard-renderer') === node.parentNode.parentNode.parentNode.parentNode + case 'ytd-video-renderer': + // search results + case 'ytd-video-preview': + // subscriptions/search thumbnail video-preview + return node.parentNode.parentNode.parentNode; + break; + + case 'ytd-grid-video-renderer': + // channel home screen grid + case 'ytd-reel-item-renderer': + // reel + return node.parentNode.parentNode; + break; + + default: + // unknown ones land here + break; + } +}; From 2a11f1447c0467f8830f53e455525bc865271f4e Mon Sep 17 00:00:00 2001 From: Rasz_pl Date: Sat, 22 Jun 2024 16:39:08 +0200 Subject: [PATCH 10/17] Update functions.js blocklist only called when needed --- js&css/web-accessible/functions.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/js&css/web-accessible/functions.js b/js&css/web-accessible/functions.js index 33aa4dca8..c769680b2 100644 --- a/js&css/web-accessible/functions.js +++ b/js&css/web-accessible/functions.js @@ -41,8 +41,10 @@ ImprovedTube.ytElementsHandler = function (node) { this.channelDefaultTab(node); } if (this.storage.blocklist_activate) { - if (node.classList.contains('ytd-video-preview') || (node.href && (node.classList.contains('ytd-thumbnail')))) { - this.blocklist('video', node); + // we are interested in thumbnails and video-previews, skip ones with 'button.it-add-to-blocklist' already + if (((node.href && node.classList.contains('ytd-thumbnail')) || node.classList.contains('ytd-video-preview')) + && !node.querySelector("button.it-add-to-blocklist")) { + this.blocklistNode(node); } } } else if (name === 'YTD-TOGGLE-BUTTON-RENDERER' || name === 'YTD-PLAYLIST-LOOP-BUTTON-RENDERER') { From 599f494d3e4cb95d0176b75e1e168c2653a76809 Mon Sep 17 00:00:00 2001 From: Rasz_pl Date: Sat, 22 Jun 2024 16:42:29 +0200 Subject: [PATCH 11/17] Update init.js blocklist more safety checks, added timestamps --- js&css/extension/init.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/js&css/extension/init.js b/js&css/extension/init.js index ac9a91a25..b6f5e5116 100644 --- a/js&css/extension/init.js +++ b/js&css/extension/init.js @@ -176,18 +176,19 @@ document.addEventListener('it-message-from-youtube', function () { } } else if (message.action === 'blocklist') { if (!extension.storage.data.blocklist || typeof extension.storage.data.blocklist !== 'object') { - extension.storage.data.blocklist = {}; + extension.storage.data.blocklist = {videos: {}, channels: {}}; } switch(message.type) { case 'channel': - if (!extension.storage.data.blocklist.channels) { + if (!extension.storage.data.blocklist.channels || typeof extension.storage.data.blocklist.channels !== 'object') { extension.storage.data.blocklist.channels = {}; } if (message.added) { extension.storage.data.blocklist.channels[message.id] = { title: message.title, - preview: message.preview + preview: message.preview, + when: message.when } } else { delete extension.storage.data.blocklist.channels[message.id]; @@ -195,12 +196,13 @@ document.addEventListener('it-message-from-youtube', function () { break case 'video': - if (!extension.storage.data.blocklist.videos) { + if (!extension.storage.data.blocklist.videos || typeof extension.storage.data.blocklist.videos !== 'object') { extension.storage.data.blocklist.videos = {}; } if (message.added) { extension.storage.data.blocklist.videos[message.id] = { - title: message.title + title: message.title, + when: message.when } } else { delete extension.storage.data.blocklist.videos[message.id]; From 05a12b9412fcdcc607b24b40e00c6c59b8aa6249 Mon Sep 17 00:00:00 2001 From: Rasz_pl Date: Sat, 22 Jun 2024 17:48:22 +0200 Subject: [PATCH 12/17] Update core.js blocklist call dedicated init function --- js&css/web-accessible/core.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/js&css/web-accessible/core.js b/js&css/web-accessible/core.js index 1cf120ddf..f2d5cc720 100644 --- a/js&css/web-accessible/core.js +++ b/js&css/web-accessible/core.js @@ -174,8 +174,7 @@ document.addEventListener('it-message-from-extension', function () { } ImprovedTube.init(); - // need to run blocklist once just after page load to catch initial nodes - ImprovedTube.blocklist(); + ImprovedTube.blocklistInit(); // REACTION OR VISUAL FEEDBACK WHEN THE USER CHANGES A SETTING (already automated for our CSS features): } else if (message.action === 'storage-changed') { @@ -199,8 +198,9 @@ document.addEventListener('it-message-from-extension', function () { } switch(camelized_key) { + case 'blocklist': case 'blocklistActivate': - camelized_key = 'blocklist'; + ImprovedTube.blocklistInit(); break case 'playerPlaybackSpeed': From adfb7532d95074802179b0985e3a5ade9e2e7f12 Mon Sep 17 00:00:00 2001 From: Rasz_pl Date: Sat, 22 Jun 2024 17:50:21 +0200 Subject: [PATCH 13/17] Update functions.js blocklist call dedicated channel function --- js&css/web-accessible/functions.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/js&css/web-accessible/functions.js b/js&css/web-accessible/functions.js index c769680b2..5183271e7 100644 --- a/js&css/web-accessible/functions.js +++ b/js&css/web-accessible/functions.js @@ -115,10 +115,7 @@ ImprovedTube.ytElementsHandler = function (node) { else if (name === 'YTD-PLAYLIST-HEADER-RENDERER' || (name === 'YTD-MENU-RENDERER' && node.classList.contains('ytd-playlist-panel-renderer'))) { this.playlistPopupUpdate(); } else if (name === 'YTD-SUBSCRIBE-BUTTON-RENDERER' || name === 'YT-SUBSCRIBE-BUTTON-VIEW-MODEL') { - if (this.storage.blocklist_activate && location.href.match(ImprovedTube.regex.channel)) { - ImprovedTube.blocklist('channel', node); - } - + ImprovedTube.blocklistChannel(node); ImprovedTube.elements.subscribe_button = node; } else if (id === 'chat-messages') { this.elements.livechat.button = document.querySelector('[aria-label="Close"]'); From 282d45f1c0e13ec3beb98cc1e4784b4a151ff861 Mon Sep 17 00:00:00 2001 From: Rasz_pl Date: Sat, 22 Jun 2024 17:52:51 +0200 Subject: [PATCH 14/17] Update functions.js new createIconButton() function --- js&css/web-accessible/functions.js | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/js&css/web-accessible/functions.js b/js&css/web-accessible/functions.js index 5183271e7..458b3d1d4 100644 --- a/js&css/web-accessible/functions.js +++ b/js&css/web-accessible/functions.js @@ -528,6 +528,35 @@ ImprovedTube.setCookie = function (name, value) { document.cookie = name + '=' + value + '; path=/; domain=.youtube.com; expires=' + date.toGMTString(); }; +ImprovedTube.createIconButton = function (options) { + const button = document.createElement('button'), + svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'), + path = document.createElementNS('http://www.w3.org/2000/svg', 'path'), + type = this.button_icons[options.type]; + + for(const attr of type.svg) svg.setAttribute(attr[0], attr[1]); + for(const attr of type.path) path.setAttribute(attr[0], attr[1]); + + svg.appendChild(path); + button.appendChild(svg); + + if (options.className) button.className = options.className; + if (options.id) button.id = options.id; + if (options.onclick) { + if (!options.propagate) { + //we fully own all click events landing on this button + button.onclick = function (event) { + event.preventDefault(); + event.stopPropagation(); + options.onclick.apply(this, arguments); + } + } else { + button.onclick = options.onclick; + } + } + return button; +}; + ImprovedTube.createPlayerButton = function (options) { var controls = options.position == "right" ? this.elements.player_right_controls : this.elements.player_left_controls; if (controls) { From eba0b7056d30c35ea7e1d34dffad946bc8fee720 Mon Sep 17 00:00:00 2001 From: Rasz_pl Date: Sat, 22 Jun 2024 18:13:00 +0200 Subject: [PATCH 15/17] Update core.js button_icons central location for all icon button definitions --- js&css/web-accessible/core.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/js&css/web-accessible/core.js b/js&css/web-accessible/core.js index f2d5cc720..681bf7dc5 100644 --- a/js&css/web-accessible/core.js +++ b/js&css/web-accessible/core.js @@ -40,6 +40,12 @@ var ImprovedTube = { playlist_id: /[?&]list=([^&]+)/, channel_link: /https:\/\/www.youtube.com\/@|((channel|user|c)\/)/ }, + button_icons: { + blocklist:{ + svg: [['viewBox', '0 0 24 24']], + path: [['d', 'M12 2a10 10 0 100 20 10 10 0 000-20zm0 18A8 8 0 015.69 7.1L16.9 18.31A7.9 7.9 0 0112 20zm6.31-3.1L7.1 5.69A8 8 0 0118.31 16.9z']] + } + }, video_src: false, initialVideoUpdateDone: false, latestVideoDuration: 0, From 70d9ae32e913580f72507848e58ff8e9080a25a2 Mon Sep 17 00:00:00 2001 From: Rasz_pl Date: Sat, 22 Jun 2024 18:29:11 +0200 Subject: [PATCH 16/17] Update blocklist.js ytInitialData no longer holds channel metadata --- .../web-accessible/www.youtube.com/blocklist.js | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/js&css/web-accessible/www.youtube.com/blocklist.js b/js&css/web-accessible/www.youtube.com/blocklist.js index 4eb64a488..80815e4ce 100644 --- a/js&css/web-accessible/www.youtube.com/blocklist.js +++ b/js&css/web-accessible/www.youtube.com/blocklist.js @@ -112,16 +112,13 @@ ImprovedTube.blocklistChannel = function (node) { event.preventDefault(); event.stopPropagation(); - const data = ytInitialData?.metadata?.channelMetadataRenderer, - // alternatives are: - //this.parentNode.__dataHost.__data.data, - // or directly scraping title and avatar from: - //title = yt-dynamic-text-view-model.yt-core-attributed-string .innerText - //avatar = Date: Sat, 22 Jun 2024 19:00:07 +0200 Subject: [PATCH 17/17] Update blocklist.js video-preview doesnt have Channel info, extract from source thumbnail --- js&css/web-accessible/www.youtube.com/blocklist.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/js&css/web-accessible/www.youtube.com/blocklist.js b/js&css/web-accessible/www.youtube.com/blocklist.js index 80815e4ce..c66886e1c 100644 --- a/js&css/web-accessible/www.youtube.com/blocklist.js +++ b/js&css/web-accessible/www.youtube.com/blocklist.js @@ -48,7 +48,9 @@ ImprovedTube.blocklistNode = function (node) { onclick: function (event) { if (!this.parentNode.href) return; // no href no action const video = this.parentNode.href?.match(ImprovedTube.regex.video_id)?.[1], - channel = this.parentNode.parentNode?.__dataHost?.__data?.data?.shortBylineText?.runs?.[0]?.navigationEndpoint?.commandMetadata?.webCommandMetadata?.url?.match(ImprovedTube.regex.channel)?.groups?.name, + channel = this.parentNode.parentNode?.__dataHost?.__data?.data?.shortBylineText?.runs?.[0]?.navigationEndpoint?.commandMetadata?.webCommandMetadata?.url?.match(ImprovedTube.regex.channel)?.groups?.name + // video-preview doesnt have Channel info, extract from source thumbnail + || ((video && this.parentNode?.classList.contains('ytd-video-preview')) ? ImprovedTube.elements.observerList.find(a => a.id == 'thumbnail' && a.href?.match(ImprovedTube.regex.video_id)?.[1] === video).parentNode?.__dataHost?.__data?.data?.shortBylineText?.runs?.[0]?.navigationEndpoint?.commandMetadata?.webCommandMetadata?.url?.match(ImprovedTube.regex.channel)?.groups?.name : null), blockedElement = node.blockedElement, // Yes, this is horrible. Cant find better way of extracting title :(