// ==UserScript== // @name YouTube — Like & next (A) // @namespace https://github.com/chirag127/userscripts // @version 0.1.0 // @description Press A to like the current video AND immediately skip to the next one. Key is remappable via the Tampermonkey menu. // @author chirag127 // @match https://www.youtube.com/* // @match https://m.youtube.com/* // @run-at document-end // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // @license MIT // @homepageURL https://github.com/chirag127/userscripts/tree/main/youtube-like-and-next-shortcut // @supportURL https://github.com/chirag127/userscripts/issues // @updateURL https://raw.githubusercontent.com/chirag127/userscripts/main/youtube-like-and-next-shortcut/youtube-like-and-next-shortcut.user.js // @downloadURL https://raw.githubusercontent.com/chirag127/userscripts/main/youtube-like-and-next-shortcut/youtube-like-and-next-shortcut.user.js // ==/UserScript== (() => { 'use strict' const DEFAULT_KEY = 'a' const STORAGE_KEY = 'yt-like-and-next-key' // Delay between clicking Like and clicking Next. YouTube's like mutation // needs a tick to land on the page state; jumping next instantly sometimes // loses the like. const DELAY_MS = 150 const hasGM = typeof GM_getValue === 'function' && typeof GM_setValue === 'function' function loadKey() { if (!hasGM) return DEFAULT_KEY const v = GM_getValue(STORAGE_KEY, DEFAULT_KEY) return (typeof v === 'string' && v.length === 1) ? v.toLowerCase() : DEFAULT_KEY } function saveKey(value) { if (!hasGM) return GM_setValue(STORAGE_KEY, value.toLowerCase()) } let key = loadKey() function promptForKey(current) { // eslint-disable-next-line no-alert const input = prompt(`Set the "Like & next" key.\nCurrent: ${current}\nType a single letter (a-z) or digit:`, current) if (input == null) return null const k = input.trim().toLowerCase() if (!/^[a-z0-9]$/.test(k)) { // eslint-disable-next-line no-alert alert(`Invalid key: "${input}". Must be a single letter (a-z) or digit. Keeping current: ${current}.`) return null } return k } if (typeof GM_registerMenuCommand === 'function') { GM_registerMenuCommand('Set "Like & next" key (default A)', () => { const fresh = promptForKey(key) if (fresh == null) return key = fresh saveKey(fresh) // eslint-disable-next-line no-alert alert(`"Like & next" key set to: ${fresh}`) }) GM_registerMenuCommand('Reset key to default (A)', () => { key = DEFAULT_KEY saveKey(DEFAULT_KEY) // eslint-disable-next-line no-alert alert('Reset: Like & next = A') }) } function isTyping(target) { if (!target) return false const tag = target.tagName if (tag === 'INPUT' || tag === 'TEXTAREA') return true if (target.isContentEditable) return true return false } const LIKE_SELECTORS = [ 'like-button-view-model button', 'ytd-toggle-button-renderer #segmented-like-button button', 'ytd-segmented-like-dislike-button-renderer #like-button button', 'button[aria-label^="like" i]', 'button[aria-label^="I like this" i]', ] function clickLike() { for (const sel of LIKE_SELECTORS) { const btn = document.querySelector(sel) if (btn) { // Skip if already pressed — don't toggle OFF an existing like. // Modern button exposes state via aria-pressed. const pressed = btn.getAttribute('aria-pressed') if (pressed === 'true') { // Already liked — don't undo. Treat as success for the chain. return true } btn.click() return true } } return false } function clickNext() { const btn = document.querySelector('.ytp-next-button') if (btn && !btn.disabled && btn.getAttribute('aria-disabled') !== 'true') { btn.click() return true } return false } function likeAndAdvance() { const liked = clickLike() // Chain after a short delay so YouTube's mutation reflects the like // before we navigate away. If the like button isn't found, still try // to advance — the user pressed the key, give them the navigation. setTimeout(() => { const advanced = clickNext() if (!liked && !advanced) { // Both failed — surface so the user knows nothing happened. console.warn('[yt-like-and-next] no like or next button found on this page') } }, DELAY_MS) } document.addEventListener('keydown', (e) => { if (e.ctrlKey || e.metaKey || e.altKey || e.shiftKey) return if (isTyping(e.target)) return if (e.key.toLowerCase() !== key) return e.preventDefault() e.stopPropagation() likeAndAdvance() }, true) })()