none
// ==UserScript==
// @name yt blocker
// @version 61
// @description none
// @run-at document-start
// @author rssaromeo
// @license GPLv3
// @match *://youtube.com/*
// @match *://*.youtube.com/*
// @icon data:image/png;base64,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
// @grant unsafeWindow
// @require https://update.greasyfork.org/scripts/491829/1671236/tampermonkey%20storage%20proxy.js
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_addValueChangeListener
// @namespace https://greasyfork.org/users/1184528
// ==/UserScript==
const HIDE_WATCHED_VID_PROG = 1
const RCLICK_BLOCKS_URL = true
const RCLICK_BLOCKS_TITLE = false
const UPDATE_INTERVAL_MS = 1e3
const creatorNameCache = {}
let videoCreatorIndex = null
let lastYtDataRef = undefined
let firstNav = true
window.addEventListener("yt-navigate-finish", () => {
if (firstNav) firstNav = false
else if (!location.href.includes("@")) location.reload()
})
;(async () => {
var globalname = null
unsafeWindow.newJsData ??= [unsafeWindow.ytInitialData]
// // Save the original fetch
// const originalFetch = unsafeWindow.fetch;
// unsafeWindow.fetch = async function(input, init) {
// // Check if the request URL contains /youtubei/v1/next
// const url = (typeof input === 'string') ? input : input.url;
// // if (url.includes('/youtubei/v1/next')) {
// console.log('YT next request detected:', url);
// // Call the original fetch
// const response = await originalFetch(input, init);
// // Clone the response so we can read it without affecting YouTube
// const cloned = response.clone();
// try{
// cloned.json().then(data => {
// console.log('ytInitialData (soft nav) updated:', data);
// // You can now store this data in a global variable
// log(findAllKeys(
// unsafeWindow.ytInitialData,
// "lockupMetadataViewModel"
// ), findAllKeys(
// data,
// "lockupMetadataViewModel"
// ))
// // unsafeWindow.newJsData.push(data)
// deepAssignMerge(unsafeWindow.ytInitialData, data)
// });
// }
// catch(e){}
// return response; // return original response
// // }
// // Otherwise, just call fetch normally
// return originalFetch(input, init);
// };
// function deepAssignMerge(...objects) {
// const isObject = v => v && typeof v === "object" && !Array.isArray(v);
// return objects.reduce((acc, obj) => {
// for (const key in obj) {
// const prev = acc[key];
// const next = obj[key];
// if (Array.isArray(prev) && Array.isArray(next)) {
// acc[key] = [...new Set([...prev, ...next])];
// }
// else if (isObject(prev) && isObject(next)) {
// acc[key] = deepAssignMerge(prev, next);
// }
// else {
// acc[key] = next;
// }
// }
// return acc;
// }, {});
// }
function buildVideoCreatorIndex() {
if (
unsafeWindow.ytInitialData === lastYtDataRef &&
videoCreatorIndex
)
return
lastYtDataRef = unsafeWindow.ytInitialData
videoCreatorIndex = new Map()
function indexEntry(entry) {
if (!entry) return
const videoId =
entry.videoId ??
findAllKeys(entry, "watchEndpoint")
.map((e) => e?.videoId)
.find(Boolean)
if (!videoId) return
const ids = [
...new Set(
findAllKeys(entry, "browseEndpoint")
.map((e) => e?.browseId)
.filter((id) => !!id),
),
]
if (ids.length)
videoCreatorIndex.set(
videoId,
ids.length === 1 ? ids[0] : ids,
)
}
for (const e of findAllKeys(
unsafeWindow.ytInitialData,
"lockupMetadataViewModel",
))
indexEntry(e)
for (const e of findAllKeys(
unsafeWindow.ytInitialData,
"videoRenderer",
))
indexEntry(e)
}
const a = loadlib("allfuncs")
const sp = new storageproxy("globaloptions")
let ls = sp.get()
let vidlock = true
const LOC = {}
updateLoc()
function setVidSpeed() {
unsafeWindow.novidspeedcontroller = true
for (const vid of a.qsa("video")) {
vid.playbackRate =
vidlock || !unsafeWindow.ytInitialData ?
0
: Number(!globalname ? 0 : (localStorage.playRate ?? 0))
}
}
ls.blockedCreators ??= []
ls.blockedTitles ??= []
ls.blockedTitlesReg ??= []
ls.blockedCreatorsReg ??= []
ls.blockedUrls ??= []
if (LOC.watch) {
const fastint = setInterval(setVidSpeed, 0)
setVidSpeed()
update()
a.waituntil(() => globalname).then(() => clearInterval(fastint))
}
unsafeWindow.findCreatorNameById = findCreatorNameById
function findCreatorNameById(creatorId, nocache = false) {
function cacheAndFind(pairs) {
let found
for (const { id, name } of pairs) {
if (!id || !name) continue
if (id === creatorId) found = name
creatorNameCache[id] ??= name
}
return found
}
function lookup() {
const watchItem = findAllKeys(
unsafeWindow.ytInitialData,
"listItemViewModel",
)
.flat()
.find((e) => findValue(e, creatorId))
const watchName = watchItem?.title
if (watchName?.content) return watchName.content
const homeContents =
unsafeWindow.ytInitialData?.contents
?.twoColumnBrowseResultsRenderer?.tabs
const homeResult = cacheAndFind(
(
homeContents?.[0]?.tabRenderer?.content?.richGridRenderer
?.contents ?? []
)
.map((e) => {
const meta =
e?.richItemRenderer?.content?.lockupViewModel?.metadata
?.lockupMetadataViewModel
return {
name: meta?.metadata?.contentMetadataViewModel
?.metadataRows?.[0]?.metadataParts?.[0]?.text
?.content,
id: meta?.image?.decoratedAvatarViewModel
?.rendererContext?.commandContext?.onTap
?.innertubeCommand?.browseEndpoint?.browseId,
}
})
.filter((e) => e.name && e.id),
)
if (homeResult) return homeResult
const watchContents =
unsafeWindow.ytInitialData?.contents
?.twoColumnWatchNextResults?.results?.results?.contents
const ownerRuns =
watchContents?.[1]?.videoSecondaryInfoRenderer?.owner
?.videoOwnerRenderer?.title?.runs
const ownerResult = cacheAndFind(
(Array.isArray(ownerRuns) ? ownerRuns : [])
.map((e) => ({
name: e?.text,
id: e?.navigationEndpoint?.browseEndpoint?.browseId,
}))
.filter((e) => e.name && e.id),
)
if (ownerResult) return ownerResult
const secResults =
unsafeWindow.ytInitialData?.contents
?.twoColumnWatchNextResults?.secondaryResults
?.secondaryResults?.results
const secArr = Array.isArray(secResults) ? secResults : []
const secFlatResult = cacheAndFind(
secArr
.map((e) => {
const meta =
e?.lockupViewModel?.metadata?.lockupMetadataViewModel
return {
name: meta?.metadata?.contentMetadataViewModel
?.metadataRows?.[0]?.metadataParts?.[0]?.text
?.content,
id: meta?.image?.decoratedAvatarViewModel
?.rendererContext?.commandContext?.onTap
?.innertubeCommand?.browseEndpoint?.browseId,
}
})
.filter((e) => e.name && e.id),
)
if (secFlatResult) return secFlatResult
return cacheAndFind(
secArr
.flatMap((e) =>
(e?.itemSectionRenderer?.contents ?? []).map((item) => {
const base =
item?.lockupViewModel?.metadata
?.lockupMetadataViewModel
return {
name: base?.metadata?.contentMetadataViewModel
?.metadataRows?.[0]?.metadataParts?.[0]?.text
?.content,
id: base?.image?.decoratedAvatarViewModel
?.rendererContext?.commandContext?.onTap
?.innertubeCommand?.browseEndpoint?.browseId,
}
}),
)
.filter((e) => e.name && e.id),
)
}
if (nocache) return lookup()
return (creatorNameCache[creatorId] ??= lookup())
}
const updateIntervalId = setInterval(update, UPDATE_INTERVAL_MS)
async function customRadioSelection(options) {
return new Promise((resolve) => {
const overlay = document.createElement("div")
Object.assign(overlay.style, {
position: "fixed",
top: "0",
left: "0",
width: "100%",
height: "100%",
backgroundColor: "rgba(0,0,0,0.5)",
zIndex: "9999",
scale: "1.5",
})
const dialog = document.createElement("div")
Object.assign(dialog.style, {
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%,-50%)",
padding: "20px",
backgroundColor: "white",
borderRadius: "5px",
boxShadow: "0 2px 10px rgba(0,0,0,0.1)",
zIndex: "10000",
})
for (const option of options) {
const label = document.createElement("label")
label.style.display = "block"
const radio = document.createElement("input")
radio.type = "radio"
radio.name = "customRadio"
const optionText =
a.gettype(option, "string") ? option : (
JSON.stringify(option)
)
radio.value = optionText
radio.onclick = () => {
document.body.removeChild(overlay)
resolve(option)
}
label.appendChild(radio)
label.appendChild(document.createTextNode(optionText))
dialog.appendChild(label)
}
overlay.appendChild(dialog)
document.body.appendChild(overlay)
})
}
function newBlockBtn(title, creator) {
const VIEW_SUFFIX_RE = / • \d+\w? views$/
const elem = a.newelem("button", {
innerHTML:
isBlocked(creator, title ?? "", null) ? "unblock" : "block",
creator,
title: title ?? "",
id: "blockbtn",
async onclick(e) {
e.stopImmediatePropagation()
e.stopPropagation()
e.preventDefault()
log(this.creator)
if (Array.isArray(this.creator)) {
const creatorArr = this.creator
const blockedList = [
...creatorArr.map((id) => ({
id,
name: findCreatorNameById(id),
isBlocked: ls.blockedCreators.includes(id),
})),
"unblock all",
"block all",
"abort",
]
const choice = await customRadioSelection(blockedList)
if (choice === "abort") return
if (choice === "unblock all") {
for (const c of creatorArr) {
const idx = ls.blockedCreators.indexOf(c)
if (idx !== -1) ls.blockedCreators.splice(idx, 1)
}
} else if (choice === "block all") {
for (const c of creatorArr) {
if (!ls.blockedCreators.includes(c))
ls.blockedCreators.push(c)
}
} else {
if (choice.isBlocked) {
ls.blockedCreators.splice(
ls.blockedCreators.indexOf(choice.id),
1,
)
} else {
ls.blockedCreators.push(choice.id)
}
}
} else {
if (isCreatorBlocked(this.creator)) {
ls.blockedCreators.splice(
ls.blockedCreators.indexOf(this.creator),
1,
)
} else {
ls.blockedCreators.push(this.creator)
}
}
update()
log(ls.blockedCreators, this.creator)
},
oncontextmenu(e) {
e.stopImmediatePropagation()
e.stopPropagation()
e.preventDefault()
if (RCLICK_BLOCKS_URL) {
const idx = ls.blockedUrls.indexOf(this.url)
if (idx !== -1) ls.blockedUrls.splice(idx, 1)
else ls.blockedUrls.push(this.url)
update()
log(ls.blockedUrls, this.url)
}
if (RCLICK_BLOCKS_TITLE) {
const idx = ls.blockedTitles.indexOf(this.title)
if (idx !== -1) ls.blockedTitles.splice(idx, 1)
else ls.blockedTitles.push(this.title)
update()
log(ls.blockedTitles, this.title)
}
},
})
let _creator = creator
Object.defineProperty(elem, "creator", {
get: () => _creator,
set(newVal) {
_creator =
Array.isArray(newVal) ?
newVal.map((e) => e.replace(VIEW_SUFFIX_RE, ""))
: newVal.replace(VIEW_SUFFIX_RE, "")
},
enumerable: true,
configurable: true,
})
return elem
}
update()
function update() {
try {
let failCount = 0
function getCreatorId(viddiv, urlSel) {
const id = a
.qs(urlSel, viddiv)
?.href?.split("?v=")
.at(-1)
?.split("&")[0]
if (!id) {
failCount++
return ""
}
const cached = videoCreatorIndex?.get(id)
if (cached) {
if (failCount > 0) failCount--
return cached
}
const video =
findAllKeys(
unsafeWindow.ytInitialData,
"lockupMetadataViewModel",
).find((e) => findValue(e, id)) ||
findAllKeys(
unsafeWindow.ytInitialData,
"videoRenderer",
).find((e) => findValue(e, id))
const res = [
...new Set(
findAllKeys(video, "browseEndpoint")
.map((e) => e?.browseId)
.filter((x) => !!x),
),
]
const out = res.length === 1 ? res[0] : res
if (res.length) {
videoCreatorIndex?.set(id, out)
if (failCount > 0) failCount--
return out
}
failCount++
if ((LOC.watch || LOC.root || LOC.search) && failCount > 5) {
location.reload()
clearInterval(updateIntervalId)
}
error(video, viddiv)
return ""
}
buildVideoCreatorIndex()
updateLoc()
unsafeWindow.ls = ls = sp.update()
// log(LOC)
if (LOC.search) {
addVid(
"div#dismissible.style-scope.ytd-video-renderer:has(a#video-title)",
"a#video-title",
"#video-title",
getCreatorId,
"#channel-info",
"#text > a",
)
} else if (LOC.root) {
globalname = null
addVid(
"ytd-rich-item-renderer",
"a:has(> .cbCustomTitle:first-child)",
".ytAttributedStringHost:not(.cbCustomTitle)",
getCreatorId,
"yt-lockup-metadata-view-model",
"a.yt-core-attributed-string__link",
)
// OLD
addVid(
"ytd-rich-item-renderer:has(#content > yt-lockup-view-model > div > div > yt-lockup-metadata-view-model > div.yt-lockup-metadata-view-model-wiz__text-container > div > yt-content-metadata-view-model > div:nth-child(1) > span > span > a)",
"#content > yt-lockup-view-model > div > a",
"#content > yt-lockup-view-model > div > div > yt-lockup-metadata-view-model > div.yt-lockup-metadata-view-model-wiz__text-container > h3 > a > span.yt-core-attributed-string.yt-core-attributed-string--white-space-pre-wrap:not(.cbCustomTitle)",
getCreatorId,
"#content > yt-lockup-view-model > div > div > yt-lockup-metadata-view-model",
"#content > yt-lockup-view-model > div > div > yt-lockup-metadata-view-model > div.yt-lockup-metadata-view-model-wiz__text-container > div > yt-content-metadata-view-model > div:nth-child(1) > span > span > a",
)
addVid(
"ytd-rich-item-renderer",
"#content > yt-lockup-view-model > div > a",
".ytLockupMetadataViewModelTitle",
getCreatorId,
"yt-lockup-metadata-view-model",
"a.yt-core-attributed-string__link",
)
// END OLD
addVid(
"ytd-rich-item-renderer",
"#content > yt-lockup-view-model > div > a",
".ytLockupMetadataViewModelTitle",
getCreatorId,
"yt-lockup-metadata-view-model",
":is(.ytAttributedStringHost,.ytContentMetadataViewModelMetadataText,.ytAttributedStringWhiteSpacePreWrap,.ytAttributedStringLinkInheritColor,.ytAttributedStringLink)",
)
} else if (LOC.feed) {
addVid(
"ytd-rich-item-renderer:has(a>.cbCustomTitle):has(a.yt-core-attributed-string__link):has(yt-lockup-metadata-view-model)",
"a:has(> .cbCustomTitle:first-child)",
"a:has(> .cbCustomTitle:first-child)>*:not(.cbCustomTitle)",
getCreatorId,
"yt-lockup-metadata-view-model",
"a.yt-core-attributed-string__link",
)
addVid(
"#dismissible:has(#video-title-link)",
"#video-title-link",
"#video-title-link>yt-formatted-string:not(.cbCustomTitle)",
getCreatorId,
"#byline-container",
"#container>#text-container>yt-formatted-string#text>a",
)
} else if (LOC.userhome || LOC.uservids) {
const CREATOR = getCreatorNameFromUrl(location.href)
const h1Sel =
"#page-header > yt-page-header-renderer > yt-page-header-view-model > div > div.page-header-view-model-wiz__page-header-headline > div > yt-dynamic-text-view-model > h1"
const h1 = a.qs(h1Sel)
const btn = a.qs(`${h1Sel} > #blockbtn`)
if (h1 && !btn) {
h1.appendChild(newBlockBtn(null, CREATOR ?? ""))
} else if (btn) {
const blockInfo = isBlocked(btn.creator, btn.title, btn.url)
btn.textContent =
blockInfo ?
`unblock - ${JSON.stringify(blockInfo)}`
: "block"
}
if (LOC.userhome) {
addVid(
"#dismissible:has(#video-title-link)",
"#video-title-link",
"#video-title-link",
() => CREATOR ?? "",
"#byline-container",
)
addVid(
"#dismissible:has(#video-title-link)",
"#video-title-link",
"#video-title-link",
(viddiv, urlSel) => {
const id = a
.qs(urlSel, viddiv)
?.href?.split("?v=")
.at(-1)
?.split("&")[0]
if (!id) return ""
const video = findAllKeys(
unsafeWindow.ytInitialData,
"lockupMetadataViewModel",
).find((e) => findValue(e, id))
return (
findKey(video, "browseEndpoint")?.browseId ??
(error(video, viddiv), "")
)
},
"#byline-container",
"#container>#text-container>yt-formatted-string#text",
)
}
if (LOC.uservids) {
addVid(
"ytd-rich-item-renderer",
".ytLockupMetadataViewModelTitle",
".ytLockupMetadataViewModelTitle>.ytAttributedStringHost.ytAttributedStringWhiteSpacePreWrap",
() => CREATOR ?? "",
"yt-lockup-metadata-view-model h3.ytLockupMetadataViewModelHeadingReset",
)
}
} else if (LOC.watch) {
if (!a.qs("#playrate") && a.qs(".ytp-right-controls")) {
const rates = [
[0.1],
[0.25],
[1, true],
[1.4],
[1.8],
[2, true],
[2.5],
]
a.qs(".ytp-right-controls").appendChild(
a.newelem(
"div",
{
display: "inline flex",
flexDirection: "row",
id: "playrate",
class:
"ytp-button ytp-settings-button ytp-hd-quality-badge",
width: "fit-content",
},
rates.map(([r, highlight]) =>
a.newelem("button", {
innerHTML: String(r),
...(highlight ? { backgroundColor: "#449" } : {}),
onclick() {
localStorage.setItem("playRate", String(r))
update()
},
}),
),
),
)
}
const watchContents =
unsafeWindow.ytInitialData?.contents
?.twoColumnWatchNextResults?.results?.results?.contents
globalname =
watchContents?.[1]?.videoSecondaryInfoRenderer?.owner?.videoOwnerRenderer?.attributedTitle?.commandRuns?.[0]?.onTap?.innertubeCommand?.showDialogCommand?.panelLoadingStrategy?.inlineContent?.dialogViewModel?.customContent?.listViewModel?.listItems?.map(
(e) =>
e?.listItemViewModel?.title?.commandRuns?.[0]?.onTap
?.innertubeCommand?.browseEndpoint?.browseId,
) ||
watchContents?.[1]?.videoSecondaryInfoRenderer?.owner
?.videoOwnerRenderer?.title?.runs?.[0]?.navigationEndpoint
?.browseEndpoint?.browseId ||
watchContents?.[1]?.videoSecondaryInfoRenderer
?.subscribeButton?.subscribeButtonRenderer?.channelId ||
unsafeWindow.ytInitialData?.header?.pageHeaderRenderer
?.content?.pageHeaderViewModel?.actions
?.flexibleActionsViewModel?.actionsRows?.[0]?.actions?.[0]
?.subscribeButtonViewModel?.subscribeButtonContent
?.onTapCommand?.innertubeCommand?.subscribeEndpoint
?.channelIds ||
watchContents?.[2]?.videoSecondaryInfoRenderer
?.subscribeButton?.subscribeButtonRenderer
?.onUnsubscribeEndpoints?.[0]?.signalServiceEndpoint
?.actions?.[0]?.openPopupAction?.popup
?.confirmDialogRenderer?.confirmButton?.buttonRenderer
?.serviceEndpoint?.unsubscribeEndpoint?.channelIds?.[0] ||
watchContents?.[2]?.videoSecondaryInfoRenderer?.subscribeButton?.subscribeButtonRenderer?.onSubscribeEndpoints?.[0]?.showDialogCommand?.panelLoadingStrategy?.inlineContent?.dialogViewModel?.customContent?.listViewModel?.listItems
?.map(
(e) =>
e?.listItemViewModel?.title?.commandRuns?.[0]?.onTap
?.innertubeCommand?.browseEndpoint?.browseId,
)
?.filter(Boolean)
if (a.qs("ytd-video-owner-renderer") && globalname) {
addVid(
"ytd-watch-metadata",
() => location.href.split("?v=").at(-1).split("&")[0],
"#title > h1 > yt-formatted-string",
() => globalname,
"ytd-video-owner-renderer",
null,
)
a.qs("ytd-watch-metadata").style.display = ""
const btn = a.qs("ytd-video-owner-renderer>#blockbtn")
if (btn) {
vidlock = isBlocked(btn.creator, btn.title, btn.url)
const blockInfo = vidlock
btn.textContent =
blockInfo ?
`unblock - ${JSON.stringify(blockInfo)}`
: "block"
}
}
addVid(
"yt-lockup-view-model",
".ytLockupMetadataViewModelTitle",
".ytLockupMetadataViewModelTitle",
getCreatorId,
".ytLockupMetadataViewModelMetadata",
null,
)
setVidSpeed()
}
} catch (e) {
trace("update", e)
}
}
function findAllKeys(obj, keyToFind) {
const results = []
function recurse(current) {
if (!current || typeof current !== "object") return
for (const key of Object.keys(current)) {
if (key.includes(keyToFind)) results.push(current[key])
recurse(current[key])
}
}
recurse(obj)
return results
}
function findAllValues(obj, valueToFind) {
const results = []
function recurse(current) {
if (!current || typeof current !== "object") return
for (const key of Object.keys(current)) {
const val = current[key]
if (typeof val === "string" && val.includes(valueToFind))
results.push(val)
recurse(val)
}
}
recurse(obj)
return results
}
function findKey(obj, keyToFind) {
let result
function recurse(current) {
if (
result !== undefined ||
!current ||
typeof current !== "object"
)
return
for (const key of Object.keys(current)) {
if (key === keyToFind) {
result = current[key]
return
}
recurse(current[key])
}
}
recurse(obj)
return result
}
function findValue(obj, valueToFind) {
let result
function recurse(current) {
if (
result !== undefined ||
!current ||
typeof current !== "object"
)
return
for (const key of Object.keys(current)) {
const val = current[key]
if (val === valueToFind) {
result = val
return
}
recurse(val)
}
}
recurse(obj)
return result
}
function addVid(
mainDivSel,
urlSel,
titleSel,
creatorIdResolver,
blockBtnParentSel,
creatorNameSel,
) {
let viddiv
try {
function tryCall(thing) {
if (typeof thing === "function") {
return (
thing(viddiv, urlSel, titleSel, creatorIdResolver) ?? ""
)
}
return undefined
}
for (viddiv of a.qsa(mainDivSel)) {
const titleEl =
typeof titleSel === "string" ?
a.qs(titleSel, viddiv)
: viddiv
const creatorEl =
creatorNameSel ? a.qs(creatorNameSel, viddiv) : viddiv
const parentEl = a.qs(blockBtnParentSel, viddiv)
if (!titleEl || !creatorEl || !parentEl) continue
if (
!viddiv.getBoundingClientRect().height &&
viddiv.style.display !== "none"
)
continue
if (viddiv.textContent?.includes("Free with ads")) {
viddiv.style.display = "none"
continue
}
let btn = a.qs(`${blockBtnParentSel}>#blockbtn`, viddiv)
const alreadyProcessed = btn && btn.creator !== ""
if (!btn) btn = parentEl.appendChild(newBlockBtn("", ""))
if (!alreadyProcessed) {
btn.url =
tryCall(urlSel) ??
a
.qs(urlSel, viddiv)
.href.split("?v=")
.at(-1)
.split("&")[0]
btn.title =
tryCall(titleSel) ??
a.qs(titleSel, viddiv).textContent.toLowerCase()
btn.creator =
creatorIdResolver.length === 0 ?
creatorIdResolver()
: creatorIdResolver(viddiv, urlSel)
}
const creatorLabel =
Array.isArray(btn.creator) ?
btn.creator
.map((id) => findCreatorNameById(id) ?? id)
.join(", ")
: (findCreatorNameById(btn.creator) ??
(log("failed to find creator name", btn.creator),
"NO CREATOR NAME FOUND"))
btn.textContent =
isCreatorBlocked(btn.creator) ?
`unblock ${creatorLabel}`
: `block ${creatorLabel}`
const prog =
a.qs(
".ytd-thumbnail-overlay-resume-playback-renderer.style-scope",
viddiv,
) ||
a.qs(
".ytThumbnailOverlayProgressBarHostWatchedProgressBarSegment",
viddiv,
)
const watchedPct = prog ? parseFloat(prog.style.width) : 0
viddiv.style.display =
(
isBlocked(btn.creator, btn.title, btn.url) ||
(HIDE_WATCHED_VID_PROG > 0 &&
watchedPct >= HIDE_WATCHED_VID_PROG)
) ?
"none"
: ""
}
} catch (e) {
trace("addVid", e, titleSel, creatorIdResolver, viddiv)
}
}
unsafeWindow.isBlocked = isBlocked
unsafeWindow.addVid = addVid
const regexCache = new Map()
function getCachedRegex(pattern) {
let re = regexCache.get(pattern)
if (!re) {
re = new RegExp(pattern, "i")
regexCache.set(pattern, re)
}
return re
}
function isBlocked(creator, title, url) {
try {
if (creator === undefined)
return {
type: "invalid creator",
val: { creator, title, url },
}
if (url && ls.blockedUrls.includes(url))
return { type: "blockedUrls", val: url }
if (ls.blockedTitles.includes(title))
return { type: "blockedTitles", val: title }
const creatorInfo = isCreatorBlocked(creator)
if (creatorInfo) return creatorInfo
for (const pattern of ls.blockedTitlesReg) {
if (getCachedRegex(pattern).test(title))
return { type: "blockedTitlesReg", val: pattern }
}
return false
} catch (e) {
trace("isBlocked", e)
}
}
function isCreatorBlocked(creator) {
if (Array.isArray(creator)) {
const blocked = creator.filter((c) =>
ls.blockedCreators.includes(c),
)
if (blocked.length) {
return {
type: "blockedCreators",
val: blocked.map((e) => findCreatorNameById(e) ?? e),
}
}
for (const pattern of ls.blockedCreatorsReg) {
if (creator.some((c) => getCachedRegex(pattern).test(c)))
return { type: "blockedCreatorsReg", val: pattern }
}
return undefined
}
if (ls.blockedCreators.includes(creator))
return {
type: "blockedCreators",
val: findCreatorNameById(creator) ?? creator,
}
for (const pattern of ls.blockedCreatorsReg) {
if (getCachedRegex(pattern).test(creator))
return { type: "blockedCreatorsReg", val: pattern }
}
}
function updateLoc() {
Object.assign(LOC, {
root: /^https?:\/\/(?:www\.)?youtube\.com\/?(?:\?|#|$)/.test(
location.href,
),
watch:
/^https?:\/\/(?:www\.)?youtube\.com\/watch\/?(?:\?|#|$)/.test(
location.href,
),
search:
/^https?:\/\/(?:www\.)?youtube\.com\/results\?search_query=.*(?:#|$)/.test(
location.href,
),
feed: /^https?:\/\/(?:www\.)?youtube\.com\/feed\/subscriptions/.test(
location.href,
),
userhome: /^https?:\/\/(?:www\.)?youtube\.com\/@[^/]+\/?$/.test(
location.href,
),
uservids:
/^https?:\/\/(?:www\.)?youtube\.com\/@[^/]+\/(videos|streams)\/?$/.test(
location.href,
) ||
/^https?:\/\/(?:www\.)?youtube\.com\/(?:channel|user|c)\/[^/]+\/(videos|streams)\/?$/.test(
location.href,
),
})
}
unsafeWindow.getCreatorNameFromUrl = getCreatorNameFromUrl
function getCreatorNameFromUrl(url) {
return url.match(/(?:\/@|\/(?:channel|user|c)\/)([^/]*)/i)?.[1]
}
})()