|
// ==UserScript== |
|
// @name Make YouTube dislike count BACK! |
|
// @namespace https://yukai.dev |
|
// @version 0.3 |
|
// @description try to take over the world! |
|
// @author Yukai Huang |
|
// @include https://www.youtube.com/* |
|
// @icon https://www.google.com/s2/favicons?domain=youtube.com |
|
// @downloadURL https://gist.githubusercontent.com/Tajnymag/48801545e9cb2c1e7fb84ac39af112b2/raw/youtube-dislike-back.user.js |
|
// @grant none |
|
// ==/UserScript== |
|
|
|
(function () { |
|
function loadConfig() { |
|
const ITEM_KEY = 'YOUTUBE_DISLIKE_COUNT_API_KEY'; |
|
const apiKey = window.localStorage.getItem(ITEM_KEY); |
|
|
|
if (apiKey) { |
|
return apiKey; |
|
} else { |
|
const key = prompt('Enter your youtube dislike count api key'); |
|
window.localStorage.setItem(ITEM_KEY, key); |
|
return key; |
|
} |
|
} |
|
|
|
function getVideoId() { |
|
const params = new URLSearchParams(window.location.search); |
|
const videoId = params.get('v') || ''; |
|
|
|
return videoId; |
|
} |
|
|
|
function cacheFn(fn) { |
|
const cache = {}; |
|
return async function (...args) { |
|
const key = args.join('-'); |
|
|
|
if (cache[key]) { |
|
return cache[key]; |
|
} else { |
|
const result = await Promise.resolve(fn(...args)); |
|
cache[key] = result; |
|
return result; |
|
} |
|
}; |
|
} |
|
|
|
async function fetchVideoStatistics(videoId) { |
|
if (!videoId) { |
|
return; |
|
} |
|
|
|
const key = loadConfig(); |
|
|
|
const params = new URLSearchParams({ |
|
part: 'statistics', |
|
id: videoId, |
|
key, |
|
}); |
|
|
|
const { items = [] } = await fetch( |
|
`https://www.googleapis.com/youtube/v3/videos?${params}`, |
|
).then((res) => res.json()); |
|
|
|
if (items?.length === 0) { |
|
return {}; |
|
} |
|
|
|
const { likeCount: likeCountString, dislikeCount: dislikeCountString } = |
|
items && items[0] && items[0].statistics; |
|
|
|
const likeCount = parseInt(likeCountString, 10); |
|
const dislikeCount = parseInt(dislikeCountString, 10); |
|
|
|
return { |
|
likeCount, |
|
dislikeCount, |
|
}; |
|
} |
|
|
|
const fetchVideoStatisticsCached = cacheFn(fetchVideoStatistics); |
|
|
|
async function getVideoStatistics() { |
|
const videoId = getVideoId(); |
|
|
|
return fetchVideoStatisticsCached(videoId); |
|
} |
|
|
|
function formatCount(count) { |
|
if (Math.log10(count) > 9) { |
|
return `${Math.floor(count / 1000000)}M`; |
|
} else if (Math.log10(count) > 6) { |
|
return `${Math.floor(count / 1000)}K`; |
|
} else { |
|
return count; |
|
} |
|
} |
|
|
|
function renderStatistics(likeCount, dislikeCount) { |
|
const percentage = (likeCount / (likeCount + dislikeCount)) * 100; |
|
|
|
const existingBar = document.querySelector( |
|
'#sentiment.ytd-video-primary-info-renderer', |
|
); |
|
|
|
if (existingBar) { |
|
existingBar.remove(); |
|
} |
|
|
|
const barElement = document.createElement('div'); |
|
barElement.setAttribute('id', 'sentiment'); |
|
barElement.setAttribute('system-icons', ''); |
|
barElement.setAttribute('style', `width: 145px;`); |
|
barElement.className = 'style-scope ytd-video-primary-info-renderer'; |
|
barElement.title = `${likeCount} / ${likeCount + dislikeCount}`; |
|
barElement.innerHTML = ` |
|
<div id="container" class="style-scope ytd-sentiment-bar-renderer"> |
|
<div id="like-bar" class="style-scope ytd-sentiment-bar-renderer" style="width: ${percentage}%;"></div> |
|
</div>`; |
|
|
|
document |
|
.querySelector('#menu-container.ytd-video-primary-info-renderer') |
|
?.append(barElement); |
|
|
|
const dislikeTextNode = document.querySelectorAll( |
|
'#menu ytd-toggle-button-renderer yt-formatted-string', |
|
)[1]; |
|
|
|
if (dislikeTextNode) { |
|
dislikeTextNode.textContent = `${formatCount(dislikeCount)}`; |
|
} |
|
} |
|
|
|
async function run() { |
|
const { dislikeCount, likeCount } = await getVideoStatistics(); |
|
|
|
if (likeCount && dislikeCount) { |
|
renderStatistics(likeCount, dislikeCount); |
|
} |
|
} |
|
|
|
const menuSelector = '#menu'; |
|
|
|
new MutationObserver(function (mutations) { |
|
mutations.forEach(function (mutation) { |
|
if (mutation.addedNodes.length > 0) { |
|
const menu = |
|
mutation.addedNodes[0].querySelector && |
|
mutation.addedNodes[0].querySelector(menuSelector); |
|
if (menu) { |
|
run(); |
|
} |
|
} |
|
}); |
|
}).observe(document.body, { |
|
childList: true, |
|
subtree: true, |
|
}); |
|
|
|
new MutationObserver(function (mutations) { |
|
mutations.forEach(function (mutation) { |
|
const menu = document.querySelector(menuSelector); |
|
if (menu) { |
|
run(); |
|
} |
|
}); |
|
}).observe(document.querySelector('ytd-app'), { |
|
attributes: true, |
|
}); |
|
})(); |
總之先把那條加回來了:
TODOs