Userscript fix - disappearing dislike counter (#1000)

* Fix disappearing dislike counter - userscript

* Fix rate bar in userscript, disable it by default (it has been broken for ages)

* Fix event listeners being thrown away by YouTube in some cases

* Fix disappearing dislike count on button focus/unfocus

* Remove repetitive code from the userscript

* Userscript fix for disappearing dislikes while smartimations play

* Check if smartimationContainer exists

* Properly handle non-existing dislike button

In some cases, dislike button doesn't exist when we're trying to
interact with it. For end users, it manifested as dislike counter
appearing on both dislike button and share button (because
getDislikeButton was falling back to the second child of getButtons()).
I don't see an elegant way of fixing this without creating a fake button
in DOM specifically for this case or rewriting the whole userscript.
Ignoring this would result in errors appearing in console.

---------

Co-authored-by: Felix Nguyen <kokonguyen191@gmail.com>
This commit is contained in:
Moondarker
2023-12-12 21:30:17 +03:00
committed by GitHub
parent 614c33376d
commit 2f2153ed37
@@ -2,7 +2,7 @@
// @name Return YouTube Dislike
// @namespace https://www.returnyoutubedislike.com/
// @homepage https://www.returnyoutubedislike.com/
// @version 3.1.3
// @version 3.1.4
// @encoding utf-8
// @description Return of the YouTube Dislike, Based off https://www.returnyoutubedislike.com/
// @icon https://github.com/Anarios/return-youtube-dislike/raw/main/Icons/Return%20Youtube%20Dislike%20-%20Transparent.png
@@ -35,6 +35,7 @@ const extConfig = {
numberDisplayRoundDown: true, // [true*, false] Round down numbers (Show rounded down numbers)
tooltipPercentageMode: "none", // [none*, dash_like, dash_dislike, both, only_like, only_dislike] Mode of showing percentage in like/dislike bar tooltip.
numberDisplayReformatLikes: false, // [true, false*] Re-format like numbers (Make likes and dislikes format consistent)
rateBarEnabled: false // [true, false*] Enables ratio bar under like/dislike buttons
// END USER OPTIONS
};
@@ -44,6 +45,7 @@ const NEUTRAL_STATE = "NEUTRAL_STATE";
let previousState = 3; //1=LIKED, 2=DISLIKED, 3=NEUTRAL
let likesvalue = 0;
let dislikesvalue = 0;
let preNavigateLikeButton = null;
let isMobile = location.hostname == "m.youtube.com";
let isShorts = () => location.pathname.startsWith("/shorts");
@@ -100,10 +102,23 @@ function getButtons() {
}
function getDislikeButton() {
return getButtons().children[0].tagName ===
"YTD-SEGMENTED-LIKE-DISLIKE-BUTTON-RENDERER"
? getButtons().children[0].children[1] === undefined ? document.querySelector("#segmented-dislike-button") : getButtons().children[0].children[1]
: getButtons().querySelector("dislike-button-view-model") ?? getButtons().children[1];
if (getButtons().children[0].tagName ===
"YTD-SEGMENTED-LIKE-DISLIKE-BUTTON-RENDERER")
{
if (getButtons().children[0].children[1] === undefined) {
return document.querySelector("#segmented-dislike-button");
} else {
return getButtons().children[0].children[1];
}
} else {
if (getButtons().querySelector("segmented-like-dislike-button-view-model")) {
const dislikeViewModel = getButtons().querySelector("dislike-button-view-model");
if (!dislikeViewModel) cLog("Dislike button wasn't added to DOM yet...");
return dislikeViewModel;
} else {
return getButtons().children[1];
}
}
}
function getLikeButton() {
@@ -123,35 +138,38 @@ function getLikeTextContainer() {
function getDislikeTextContainer() {
const dislikeButton = getDislikeButton();
let result =
getDislikeButton().querySelector("#text") ??
getDislikeButton().getElementsByTagName("yt-formatted-string")[0] ??
getDislikeButton().querySelector("span[role='text']")
dislikeButton?.querySelector("#text") ??
dislikeButton?.getElementsByTagName("yt-formatted-string")[0] ??
dislikeButton?.querySelector("span[role='text']")
if (result === null) {
let textSpan = document.createElement("span");
textSpan.id = "text";
textSpan.style.marginLeft = "6px";
getDislikeButton().querySelector("button").appendChild(textSpan);
getDislikeButton().querySelector("button").style.width = "auto";
result = getDislikeButton().querySelector("#text");
dislikeButton?.querySelector("button").appendChild(textSpan);
if (dislikeButton) dislikeButton.querySelector("button").style.width = "auto";
result = textSpan;
}
return result;
}
let mutationObserver = new Object();
function createObserver(options, callback) {
const observerWrapper = new Object();
observerWrapper.options = options;
observerWrapper.observer = new MutationObserver(callback);
observerWrapper.observe = function (element) { this.observer.observe(element, this.options); }
observerWrapper.disconnect = function () { this.observer.disconnect(); }
return observerWrapper;
}
if (isShorts() && mutationObserver.exists !== true) {
cLog("initializing mutation observer");
mutationObserver.options = {
childList: false,
attributes: true,
subtree: false,
};
mutationObserver.exists = true;
mutationObserver.observer = new MutationObserver(function (
mutationList,
observer
) {
let shortsObserver = null;
if (isShorts() && !shortsObserver) {
cLog("Initializing shorts mutation observer");
shortsObserver = createObserver({
attributes: true
}, (mutationList) => {
mutationList.forEach((mutation) => {
if (
mutation.type === "attributes" &&
@@ -170,7 +188,7 @@ if (isShorts() && mutationObserver.exists !== true) {
return;
}
cLog(
"unexpected mutation observer event: " + mutation.target + mutation.type
"Unexpected mutation observer event: " + mutation.target + mutation.type
);
});
});
@@ -189,11 +207,11 @@ function isVideoLiked() {
function isVideoDisliked() {
if (isMobile) {
return (
getDislikeButton().querySelector("button").getAttribute("aria-label") ==
getDislikeButton()?.querySelector("button").getAttribute("aria-label") ==
"true"
);
}
return getDislikeButton().classList.contains("style-default-active");
return getDislikeButton()?.classList.contains("style-default-active");
}
function isVideoNotLiked() {
@@ -207,7 +225,7 @@ function isVideoNotDisliked() {
if (isMobile) {
return !isVideoDisliked();
}
return getDislikeButton().classList.contains("style-text");
return getDislikeButton()?.classList.contains("style-text");
}
function checkForUserAvatarButton() {
@@ -290,10 +308,10 @@ function getLikeCountFromButton() {
}
.ryd-tooltip {
position: relative;
position: absolute;
display: block;
height: 2px;
top: 9px;
bottom: -10px;
}
.ryd-tooltip-bar-container {
@@ -301,21 +319,28 @@ function getLikeCountFromButton() {
height: 2px;
position: absolute;
padding-top: 6px;
padding-bottom: 28px;
padding-bottom: 12px;
top: -6px;
}
ytd-menu-renderer.ytd-watch-metadata {
overflow-y: visible !important;
}
#top-level-buttons-computed {
position: relative !important;
}
`);
function createRateBar(likes, dislikes) {
if (isMobile) {
if (isMobile || !extConfig.rateBarEnabled) {
return;
}
let rateBar = document.getElementById("return-youtube-dislike-bar-container");
const widthPx =
getButtons().children[0].clientWidth +
getButtons().children[1].clientWidth +
8;
getLikeButton().clientWidth +
(getDislikeButton()?.clientWidth ?? 52);
const widthPercent =
likes + dislikes > 0 ? (likes / (likes + dislikes)) * 100 : 50;
@@ -353,7 +378,7 @@ function createRateBar(likes, dislikes) {
colorDislikeStyle = "; background-color: " + getColorFromTheme(false);
}
document.getElementById("menu-container").insertAdjacentHTML(
getButtons().insertAdjacentHTML(
"beforeend",
`
<div class="ryd-tooltip" style="width: ${widthPx}px">
@@ -374,16 +399,17 @@ function createRateBar(likes, dislikes) {
</div>
`
);
let descriptionAndActionsElement = document.getElementById("top-row");
descriptionAndActionsElement.style.borderBottom =
"1px solid var(--yt-spec-10-percent-layer)";
descriptionAndActionsElement.style.paddingBottom = "10px";
} else {
document.getElementById(
"return-youtube-dislike-bar-container"
document.querySelector(
".ryd-tooltip"
).style.width = widthPx + "px";
document.getElementById("return-youtube-dislike-bar").style.width =
widthPercent + "%";
document.querySelector("#ryd-dislike-tooltip > #tooltip").innerHTML =
tooltipInnerHTML;
if (extConfig.coloredBar) {
document.getElementById(
"return-youtube-dislike-bar-container"
@@ -417,31 +443,28 @@ function setState() {
}
createRateBar(likes, dislikes);
if (extConfig.coloredThumbs === true) {
const dislikeButton = getDislikeButton();
if (isShorts()) {
// for shorts, leave deactived buttons in default color
let shortLikeButton = getLikeButton().querySelector(
const shortLikeButton = getLikeButton().querySelector(
"tp-yt-paper-button#button"
);
let shortDislikeButton = getDislikeButton().querySelector(
const shortDislikeButton = dislikeButton?.querySelector(
"tp-yt-paper-button#button"
);
if (shortLikeButton.getAttribute("aria-pressed") === "true") {
shortLikeButton.style.color = getColorFromTheme(true);
}
if (shortDislikeButton.getAttribute("aria-pressed") === "true") {
if (shortDislikeButton &&
shortDislikeButton.getAttribute("aria-pressed") === "true")
{
shortDislikeButton.style.color = getColorFromTheme(false);
}
mutationObserver.observer.observe(
shortLikeButton,
mutationObserver.options
);
mutationObserver.observer.observe(
shortDislikeButton,
mutationObserver.options
);
shortsObserver.observe(shortLikeButton);
shortsObserver.observe(shortDislikeButton);
} else {
getLikeButton().style.color = getColorFromTheme(true);
getDislikeButton().style.color = getColorFromTheme(false);
if (dislikeButton) dislikeButton.style.color = getColorFromTheme(false);
}
}
}
@@ -449,22 +472,25 @@ function setState() {
});
}
function updateDOMDislikes() {
setDislikes(numberFormat(dislikesvalue));
createRateBar(likesvalue, dislikesvalue);
}
function likeClicked() {
if (checkForUserAvatarButton() == true) {
if (previousState == 1) {
likesvalue--;
createRateBar(likesvalue, dislikesvalue);
setDislikes(numberFormat(dislikesvalue));
updateDOMDislikes();
previousState = 3;
} else if (previousState == 2) {
likesvalue++;
dislikesvalue--;
setDislikes(numberFormat(dislikesvalue));
createRateBar(likesvalue, dislikesvalue);
updateDOMDislikes();
previousState = 1;
} else if (previousState == 3) {
likesvalue++;
createRateBar(likesvalue, dislikesvalue);
updateDOMDislikes();
previousState = 1;
}
if (extConfig.numberDisplayReformatLikes === true) {
@@ -480,19 +506,16 @@ function dislikeClicked() {
if (checkForUserAvatarButton() == true) {
if (previousState == 3) {
dislikesvalue++;
setDislikes(numberFormat(dislikesvalue));
createRateBar(likesvalue, dislikesvalue);
updateDOMDislikes();
previousState = 2;
} else if (previousState == 2) {
dislikesvalue--;
setDislikes(numberFormat(dislikesvalue));
createRateBar(likesvalue, dislikesvalue);
updateDOMDislikes();
previousState = 3;
} else if (previousState == 1) {
likesvalue--;
dislikesvalue++;
setDislikes(numberFormat(dislikesvalue));
createRateBar(likesvalue, dislikesvalue);
updateDOMDislikes();
previousState = 2;
if (extConfig.numberDisplayReformatLikes === true) {
const nativeLikes = getLikeCountFromButton();
@@ -625,6 +648,8 @@ function getColorFromTheme(voteIsLike) {
return colorString;
}
let smartimationObserver = null;
function setEventListeners(evt) {
let jsInitChecktimer;
@@ -632,21 +657,44 @@ function setEventListeners(evt) {
//console.log();
if (isShorts() || (getButtons()?.offsetParent && isVideoLoaded())) {
const buttons = getButtons();
const dislikeButton = getDislikeButton();
if (!window.returnDislikeButtonlistenersSet) {
if (preNavigateLikeButton !== getLikeButton() && dislikeButton) {
cLog("Registering button listeners...");
try {
getLikeButton().addEventListener("click", likeClicked);
getDislikeButton().addEventListener("click", dislikeClicked);
dislikeButton?.addEventListener("click", dislikeClicked);
getLikeButton().addEventListener("touchstart", likeClicked);
getDislikeButton().addEventListener("touchstart", dislikeClicked);
dislikeButton?.addEventListener("touchstart", dislikeClicked);
dislikeButton?.addEventListener("focusin", updateDOMDislikes);
dislikeButton?.addEventListener("focusout", updateDOMDislikes);
preNavigateLikeButton = getLikeButton();
if (!smartimationObserver) {
smartimationObserver = createObserver({
attributes: true,
subtree: true
}, updateDOMDislikes);
smartimationObserver.container = null;
}
const smartimationContainer = buttons.querySelector('yt-smartimation');
if (smartimationContainer &&
smartimationObserver.container != smartimationContainer)
{
cLog("Initializing smartimation mutation observer");
smartimationObserver.disconnect();
smartimationObserver.observe(smartimationContainer);
smartimationObserver.container = smartimationContainer;
}
} catch {
return;
} //Don't spam errors into the console
window.returnDislikeButtonlistenersSet = true;
}
setInitialState();
clearInterval(jsInitChecktimer);
if (dislikeButton) {
setInitialState();
clearInterval(jsInitChecktimer);
}
}
}
@@ -667,11 +715,12 @@ if (isMobile) {
return originalPush.apply(history, args);
};
setInterval(() => {
if(getDislikeButton().querySelector(".button-renderer-text") === null){
const dislikeButton = getDislikeButton();
if(dislikeButton?.querySelector(".button-renderer-text") === null){
getDislikeTextContainer().innerText = mobileDislikes;
}
else{
getDislikeButton().querySelector(".button-renderer-text").innerText =
if (dislikeButton) dislikeButton.querySelector(".button-renderer-text").innerText =
mobileDislikes;
}
}, 1000);