|
1 | 1 | // ==UserScript== |
2 | 2 | // @name YouTube™ Classic 📺 — (Remove rounded design + Return YouTube dislikes) |
3 | | -// @version 2026.1.21.13 |
| 3 | +// @version 2026.1.21.14 |
4 | 4 | // @author Adam Lui, Magma_Craft, Fuim & hoothin |
5 | 5 | // @namespace https://github.com/adamlui |
6 | 6 | // @description Reverts YouTube to its classic design (before all the rounded corners & hidden dislikes) + redirects YouTube Shorts |
|
33 | 33 | (() => { |
34 | 34 | 'use strict' |
35 | 35 |
|
36 | | - const app = { symbol: '📺', configKeyPrefix: 'ytClassic' } |
37 | | - const env = { |
| 36 | + window.app = { symbol: '📺', configKeyPrefix: 'ytClassic', config: {} } |
| 37 | + window.env = { |
38 | 38 | scriptManager: { |
39 | 39 | name: (() => { try { return GM_info.scriptHandler } catch (err) { return 'unknown' }})(), |
40 | 40 | version: (() => { try { return GM_info.version } catch (err) { return 'unknown' }})() |
|
46 | 46 | } |
47 | 47 | env.scriptManager.supportsTooltips = env.scriptManager.name == 'Tampermonkey' |
48 | 48 | && parseInt(env.scriptManager.version.split('.')[0]) >= 5 |
49 | | - const config = {} |
50 | 49 | const settings = { |
51 | 50 |
|
52 | 51 | controls: { // displays top-to-bottom in toolbar menu |
53 | | - disableShorts: { type: 'toggle', label: 'Redirect Shorts', defaultVal: true, |
54 | | - helptip: 'Redirect Shorts to classic wide player' }, |
55 | | - shortsBlock: { type: 'toggle', label: 'Hide Shorts', defaultVal: true, |
56 | | - helptip: 'Hide Shorts from appearing in home page + results' }, |
57 | | - playablesBlock: { type: 'toggle', label: 'Hide Playables', defaultVal: true, |
58 | | - helptip: 'Hide Playables from appearing in home page' }, |
59 | | - adBlock: { type: 'toggle', label: 'Block Ads', defaultVal: false, |
60 | | - helptip: 'Hide ad thumbnails from homepage layouts' }, |
61 | | - aiBlock: { type: 'toggle', label: 'Block AI Summaries', defaultVal: true, |
62 | | - helptip: 'Hide AI summaries from video pages' }, |
63 | | - notifDisabled: { type: 'toggle', label: 'Mode Notifications', defaultVal: false, |
64 | | - helptip: 'Show notifications when toggling mode/settings' } |
| 52 | + disableShorts: { |
| 53 | + type: 'toggle', label: 'Redirect Shorts', defaultVal: true, |
| 54 | + helptip: 'Redirect Shorts to classic wide player' |
| 55 | + }, |
| 56 | + shortsBlock: { |
| 57 | + type: 'toggle', label: 'Hide Shorts', defaultVal: true, |
| 58 | + helptip: 'Hide Shorts from appearing in home page + results' |
| 59 | + }, |
| 60 | + playablesBlock: { |
| 61 | + type: 'toggle', label: 'Hide Playables', defaultVal: true, |
| 62 | + helptip: 'Hide Playables from appearing in home page' |
| 63 | + }, |
| 64 | + adBlock: { |
| 65 | + type: 'toggle', label: 'Block Ads', defaultVal: false, |
| 66 | + helptip: 'Hide ad thumbnails from homepage layouts' |
| 67 | + }, |
| 68 | + aiBlock: { |
| 69 | + type: 'toggle', label: 'Block AI Summaries', defaultVal: true, |
| 70 | + helptip: 'Hide AI summaries from video pages' |
| 71 | + }, |
| 72 | + notifDisabled: { |
| 73 | + type: 'toggle', label: 'Mode Notifications', defaultVal: false, |
| 74 | + helptip: 'Show notifications when toggling mode/settings' |
| 75 | + } |
65 | 76 | }, |
66 | 77 |
|
67 | 78 | load(...keys) { |
68 | 79 | keys.flat().forEach(key => |
69 | | - config[key] = processKey(key, GM_getValue(`${app.configKeyPrefix}_${key}`, undefined))) |
| 80 | + app.config[key] = processKey(key, GM_getValue(`${app.configKeyPrefix}_${key}`, undefined))) |
70 | 81 | function processKey(key, val) { |
71 | 82 | const ctrl = settings.controls?.[key] |
72 | 83 | if (val != undefined && ( // validate stored val |
|
77 | 88 | } |
78 | 89 | }, |
79 | 90 |
|
80 | | - save(key, val) { GM_setValue(`${app.configKeyPrefix}_${key}`, val) ; config[key] = val }, |
| 91 | + save(key, val) { GM_setValue(`${app.configKeyPrefix}_${key}`, val) ; app.config[key] = val }, |
81 | 92 |
|
82 | 93 | typeIsEnabled(key) { // for menu labels + notifs to return ON/OFF |
83 | 94 | const reInvertSuffixes = /disabled|hidden/i |
84 | 95 | return reInvertSuffixes.test(key) // flag in control key name |
85 | 96 | && !reInvertSuffixes.test(this.controls[key]?.label || '') // but not in label msg key name |
86 | | - ? !config[key] : config[key] // so invert since flag reps opposite type state, else don't |
| 97 | + ? !app.config[key] : app.config[key] // so invert since flag reps opposite type state, else don't |
87 | 98 | } |
88 | 99 | } |
89 | 100 | settings.load(Object.keys(settings.controls)) |
90 | 101 |
|
91 | | - const domSelectors = { |
| 102 | + app.selectors = { site: { |
92 | 103 | ad: { masthead: 'div#masthead-ad' }, // https://imgur.com/a/kOWzh3O |
93 | 104 | ai: { |
94 | 105 | askBtn: 'button:has(path[d*=M480-80q0-83])', |
|
102 | 113 | results: 'grid-shelf-view-model:has(a[href*="/shorts/"])' // https://imgur.com/a/vVzoEfH |
103 | 114 | } |
104 | 115 | } |
105 | | - } |
| 116 | + }} |
106 | 117 |
|
107 | 118 | const CONFIGS = { BUTTON_REWORK: false } |
108 | 119 |
|
|
308 | 319 | const menuLabel = `${ |
309 | 320 | ctrl.symbol || this.state.symbols[+settings.typeIsEnabled(key)] } ${ctrl.label} ${ |
310 | 321 | ctrl.type == 'toggle' ? this.state.separator + this.state.words[+settings.typeIsEnabled(key)] |
311 | | - : ctrl.type == 'slider' ? ': ' + config[key] + ctrl.labelSuffix || '' |
| 322 | + : ctrl.type == 'slider' ? ': ' + app.config[key] + ctrl.labelSuffix || '' |
312 | 323 | : ctrl.status ? ` — ${ctrl.status}` : '' }` |
313 | 324 | return GM_registerMenuCommand(menuLabel, () => { |
314 | | - settings.save(key, !config[key]) ; syncConfigToUI({ updatedKey: key }) |
| 325 | + settings.save(key, !app.config[key]) ; syncConfigToUI({ updatedKey: key }) |
315 | 326 | notify(`${ctrl.label}: ${this.state.words[+settings.typeIsEnabled(key)]}`) |
316 | 327 | }, env.scriptManager.supportsTooltips ? { title: ctrl.helptip || ' ' } : undefined) |
317 | 328 | }) |
318 | 329 | } |
319 | 330 | } |
320 | 331 |
|
321 | 332 | function notify(msg, pos = '', notifDuration = '', shadow = '') { // requires dom.js |
322 | | - if (config.notifDisabled && !msg.includes(settings.controls.notifDisabled.label)) return |
| 333 | + if (app.config.notifDisabled && !msg.includes(settings.controls.notifDisabled.label)) return |
323 | 334 |
|
324 | 335 | // Strip state word to append colored one later |
325 | 336 | const foundState = toolbarMenu.state.words.find(word => msg.includes(word)) |
|
343 | 354 |
|
344 | 355 | function syncConfigToUI(options) { |
345 | 356 | if (options?.updatedKey == 'disableShorts') { |
346 | | - if (config.disableShorts && !checkShortsToRedir.id) |
| 357 | + if (app.config.disableShorts && !checkShortsToRedir.id) |
347 | 358 | checkShortsToRedir() |
348 | | - else if (!config.disableShorts && checkShortsToRedir.id) { |
| 359 | + else if (!app.config.disableShorts && checkShortsToRedir.id) { |
349 | 360 | cancelAnimationFrame(checkShortsToRedir.id) ; checkShortsToRedir.id = null } |
350 | 361 | } else if (options?.updatedKey.includes('Block')) |
351 | | - window.configStyle.textContent = Object.entries(domSelectors) |
352 | | - .map(([key, selectors]) => !config[`${key}Block`] ? '' |
| 362 | + app.styles.config.textContent = Object.entries(app.selectors.site) |
| 363 | + .map(([key, selectors]) => !app.config[`${key}Block`] ? '' |
353 | 364 | : `${css.selectors.extract(selectors).join(',')} { display: none }` |
354 | 365 | ).join('') |
355 | 366 | toolbarMenu.refresh() // prefixes/suffixes |
|
462 | 473 | document.querySelector('#items > ytd-mini-guide-entry-renderer:nth-child(2)').data = trendingData |
463 | 474 | } |
464 | 475 |
|
465 | | - // Restore classic comments UI |
466 | | - let hl |
467 | | - const cfconfig = { unicodeEmojis: false } |
468 | | - const cfi18n = { en: { |
469 | | - viewSingular: 'View reply', |
470 | | - viewMulti: 'View %s replies', |
471 | | - viewSingularOwner: 'View reply from %s', |
472 | | - viewMultiOwner: 'View %s replies from %s and others', |
473 | | - hideSingular: 'Hide reply', |
474 | | - hideMulti: 'Hide replies', |
475 | | - replyCntIsolator: /( REPLIES)|( REPLY)/ |
476 | | - }} |
477 | | - function getString(string, hl = 'en', ...args) { |
478 | | - if (!string) return |
479 | | - let str |
480 | | - if (cfi18n[hl]) { |
481 | | - if (cfi18n[hl][string]) str = cfi18n[hl][string] |
482 | | - else if (cfi18n.en[string]) str = cfi18n.en[string] |
483 | | - else return |
484 | | - } else if (cfi18n.en[string]) str = cfi18n.en[string] |
485 | | - for (const arg of args) str = str.replace(/%s/, arg) |
486 | | - return str |
487 | | - } |
488 | | - function getSimpleString(object) { |
489 | | - if (object.simpleText) return object.simpleText |
490 | | - let str = '' |
491 | | - for (const run of object.runs) str += run.text |
492 | | - return str |
493 | | - } |
494 | | - function formatComment(comment) { |
495 | | - if (cfconfig.unicodeEmojis) { |
496 | | - try { for (const run of comment.contentText.runs) { delete run.emoji ; delete run.loggingDirectives }} |
497 | | - catch (err) {} |
498 | | - } |
499 | | - return comment |
500 | | - } |
501 | | - async function formatCommentThread(thread) { |
502 | | - if (thread.comment.commentRenderer) |
503 | | - thread.comment.commentRenderer = formatComment(thread.comment.commentRenderer) |
504 | | - let replies |
505 | | - try { |
506 | | - replies = thread.replies.commentRepliesRenderer |
507 | | - if (replies.viewRepliesIcon) { |
508 | | - replies.viewReplies.buttonRenderer.icon = replies.viewRepliesIcon.buttonRenderer.icon |
509 | | - delete replies.viewRepliesIcon |
510 | | - } |
511 | | - if (replies.hideRepliesIcon) { |
512 | | - replies.hideReplies.buttonRenderer.icon = replies.hideRepliesIcon.buttonRenderer.icon |
513 | | - delete replies.hideRepliesIcon |
514 | | - } |
515 | | - let creatorName |
516 | | - try { |
517 | | - creatorName = replies.viewRepliesCreatorThumbnail.accessibility.accessibilityData.label |
518 | | - delete replies.viewRepliesCreatorThumbnail |
519 | | - } catch (err) {} |
520 | | - let replyCnt = getSimpleString(replies.viewReplies.buttonRenderer.text) |
521 | | - replyCnt = +replyCnt.replace(getString('replyCntIsolator', hl), '') |
522 | | - const viewMultiStr = creatorName ? 'viewMultiOwner' : 'viewMulti', |
523 | | - viewSingleStr = creatorName ? 'viewSingularOwner' : 'viewSingular' |
524 | | - replies.viewReplies.buttonRenderer.text = { |
525 | | - runs: [{ |
526 | | - text: (replyCnt > 1) ? getString(viewMultiStr, hl, replyCnt, creatorName) |
527 | | - : getString(viewSingleStr, hl, creatorName) |
528 | | - }] |
529 | | - } |
530 | | - replies.hideReplies.buttonRenderer.text = { |
531 | | - runs: [{ text: (replyCnt > 1) ? getString('hideMulti', hl) : getString('hideSingular', hl) }] |
532 | | - } |
533 | | - } catch (err) {} |
534 | | - return thread |
535 | | - } |
536 | | - function refreshData(elem) { |
537 | | - const clone = elem.cloneNode() |
538 | | - clone.data = elem.data ; clone.data.fixedByCF = true |
539 | | - for (const i in elem.properties) clone[i] = elem[i] |
540 | | - elem.insertAdjacentElement('afterend', clone) ; elem.remove() |
541 | | - } |
542 | | - const commentObserver = new MutationObserver(mutations => mutations.forEach(async mutation => { |
543 | | - if (mutation.addedNodes) for (const elem of mutation.addedNodes) if (elem.classList && !elem.data?.fixedByCF) |
544 | | - if (elem.tagName == 'YTD-COMMENT-THREAD-RENDERER') { |
545 | | - elem.data = await formatCommentThread(elem.data) |
546 | | - refreshData(elem) |
547 | | - } else if (elem.tagName == 'YTD-COMMENT-RENDERER' |
548 | | - && !elem.classList.contains('ytd-comment-thread-renderer') |
549 | | - ) { elem.data = formatComment(elem.data) ; refreshData(elem) } |
550 | | - })) |
551 | | - document.addEventListener('yt-page-data-updated', async () => |
552 | | - commentObserver.observe(document.querySelector('ytd-app'), { childList: true, subtree: true })) |
553 | | - |
554 | 476 | // CSS adjustments and UI fixes |
555 | 477 | app.styles = { fixes: dom.create.style(` |
556 | 478 | yt-thumbnail-view-model { border-radius: 0 !important } /* square homepage thumbs */ |
|
1122 | 1044 | const obsConfig = { childList: true, subtree: true } |
1123 | 1045 |
|
1124 | 1046 | // Redirect Shorts to classic player |
1125 | | - if (config.disableShorts) checkShortsToRedir() |
| 1047 | + if (app.config.disableShorts) checkShortsToRedir() |
1126 | 1048 | function checkShortsToRedir() { |
1127 | 1049 | if (location.pathname.startsWith('/shorts/')) |
1128 | 1050 | return location.replace(`https://www.youtube.com/watch?v=${location.pathname.split('/')[2]}`) |
|
1135 | 1057 | locationPath = location.pathname ; homeObserver.disconnect() |
1136 | 1058 | dom.get.loadedElem('html').then(() => homeObserver.observe(document.documentElement, obsConfig)) |
1137 | 1059 | } else if (locationPath == '/') { // remove regenerating homepage stuff |
1138 | | - if (config.adBlock) // remove ads |
| 1060 | + if (app.config.adBlock) // remove ads |
1139 | 1061 | document.querySelector('ytd-ad-slot-renderer')?.closest('[rendered-from-rich-grid]')?.remove() |
1140 | | - if (config.shortsBlock || config.playablesBlock) // remove shelves |
| 1062 | + if (app.config.shortsBlock || app.config.playablesBlock) // remove shelves |
1141 | 1063 | document.querySelector( |
1142 | | - `ytd-rich-section-renderer${ !config.shortsBlock ? ':not(:has(a[href*="/shorts/"]))' : '' }${ |
1143 | | - !config.playablesBlock ? ':not(:has(a[href*="/playables/"]))' : '' }` |
| 1064 | + `ytd-rich-section-renderer${ !app.config.shortsBlock ? ':not(:has(a[href*="/shorts/"]))' : '' }${ |
| 1065 | + !app.config.playablesBlock ? ':not(:has(a[href*="/playables/"]))' : '' }` |
1144 | 1066 | )?.remove() |
1145 | 1067 | } |
1146 | 1068 | }) |
1147 | 1069 | dom.get.loadedElem('html').then(() => homeObserver.observe(document.documentElement, obsConfig)) |
1148 | 1070 |
|
1149 | 1071 | // Block stuff |
1150 | | - document.head.append(window.configStyle ??= dom.create.style()) |
1151 | | - window.configStyle.textContent = Object.entries(domSelectors).map(([key, selectors]) => |
1152 | | - !config[`${key}Block`] ? '' : `${css.selectors.extract(selectors).join(',')} { display: none }`).join('') |
| 1072 | + document.head.append(app.styles.config ??= dom.create.style()) |
| 1073 | + app.styles.config.textContent = Object.entries(app.selectors.site).map(([key, selectors]) => |
| 1074 | + !app.config[`${key}Block`] ? '' : `${css.selectors.extract(selectors).join(',')} { display: none }`).join('') |
1153 | 1075 | })() |
0 commit comments