YouTube Auto-Liker

Automatically likes videos of channels you're subscribed to

Install this script?
Author's suggested script

You may also like Google Translate: Filter & Flags.

Install this script
  1. // ==UserScript==
  2. // @name YouTube Auto-Liker
  3. // @name:zh YouTube自動點讚
  4. // @name:ja YouTubeのような自動
  5. // @namespace https://github.com/HatScripts/youtube-auto-liker
  6. // @version 1.3.25
  7. // @description Automatically likes videos of channels you're subscribed to
  8. // @description:zh 對您訂閲的頻道視頻自動點讚
  9. // @description:ja 購読しているチャンネルの動画が自動的に好きです
  10. // @description:ru Автоматически нравится видео каналов, на которые вы подписаны
  11. // @description:es Le gustan automáticamente los videos de los canales a los que está suscrito
  12. // @description:pt Gosta automaticamente de vídeos de canais nos quais você está inscrito
  13. // @author HatScripts
  14. // @license MIT
  15. // @icon https://raw.githubusercontent.com/HatScripts/youtube-auto-liker/master/logo.svg
  16. // @downloadurl https://github.com/HatScripts/youtube-auto-liker/raw/master/youtube-auto-liker.user.js
  17. // @updateurl https://github.com/HatScripts/youtube-auto-liker/raw/master/youtube-auto-liker.user.js
  18. // @match http://*.youtube.com/*
  19. // @match https://*.youtube.com/*
  20. // @require https://openuserjs.org/src/libs/sizzle/GM_config.js
  21. // @grant GM_getValue
  22. // @grant GM_setValue
  23. // @grant GM_registerMenuCommand
  24. // @run-at document-idle
  25. // @noframes
  26. // ==/UserScript==
  27.  
  28. /* global GM_config, GM_info, GM_registerMenuCommand */
  29.  
  30. (() => {
  31. 'use strict'
  32.  
  33. GM_config.init({
  34. id: 'ytal_config',
  35. title: GM_info.script.name + ' Settings',
  36. fields: {
  37. DEBUG_MODE: {
  38. label: 'Debug mode',
  39. type: 'checkbox',
  40. default: false,
  41. title: 'Log debug messages to the console'
  42. },
  43. CHECK_FREQUENCY: {
  44. label: 'Check frequency (ms)',
  45. type: 'number',
  46. min: 1,
  47. default: 5000,
  48. title: 'The number of milliseconds to wait between checking if video should be liked'
  49. },
  50. WATCH_THRESHOLD: {
  51. label: 'Watch threshold %',
  52. type: 'number',
  53. min: 0,
  54. max: 100,
  55. default: 50,
  56. title: 'The percentage watched to like the video at'
  57. },
  58. LIKE_IF_NOT_SUBSCRIBED: {
  59. label: 'Like if not subscribed',
  60. type: 'checkbox',
  61. default: false,
  62. title: 'Like videos from channels you are not subscribed to'
  63. },
  64. AUTO_LIKE_LIVE_STREAMS: {
  65. label: 'Auto-like live streams',
  66. type: 'checkbox',
  67. default: false,
  68. title: 'Automatically like live streams'
  69. }
  70. },
  71. events: {
  72. init: onInit
  73. }
  74. })
  75.  
  76. GM_registerMenuCommand('Settings', () => {
  77. GM_config.open()
  78. })
  79.  
  80. class Debugger {
  81. constructor (name, enabled) {
  82. this.debug = {}
  83. if (!window.console) {
  84. return () => { }
  85. }
  86. Object.getOwnPropertyNames(window.console).forEach(key => {
  87. if (typeof window.console[key] === 'function') {
  88. if (enabled) {
  89. this.debug[key] = window.console[key].bind(window.console, name + ': ')
  90. } else {
  91. this.debug[key] = () => { }
  92. }
  93. }
  94. })
  95. return this.debug
  96. }
  97. }
  98.  
  99. var DEBUG
  100.  
  101. const SELECTORS = {
  102. PLAYER: '#movie_player',
  103. SUBSCRIBE_BUTTON: '#subscribe-button > ytd-subscribe-button-renderer',
  104. LIKE_BUTTON: '#menu #top-level-buttons-computed > ytd-toggle-button-renderer:nth-child(1), #segmented-like-button button',
  105. DISLIKE_BUTTON: '#menu #top-level-buttons-computed > ytd-toggle-button-renderer:nth-child(2), #segmented-dislike-button button'
  106. }
  107.  
  108. const autoLikedVideoIds = []
  109.  
  110. function onInit() {
  111. DEBUG = new Debugger(GM_info.script.name, GM_config.get('DEBUG_MODE'))
  112. setInterval(wait, GM_config.get('CHECK_FREQUENCY'))
  113. }
  114.  
  115. function getVideoId () {
  116. const elem = document.querySelector('#page-manager > ytd-watch-flexy')
  117. if (elem && elem.hasAttribute('video-id')) {
  118. return elem.getAttribute('video-id')
  119. } else {
  120. return new URLSearchParams(window.location.search).get('v')
  121. }
  122. }
  123.  
  124. function watchThresholdReached () {
  125. const player = document.querySelector(SELECTORS.PLAYER)
  126. if (player) {
  127. const watched = player.getCurrentTime() / player.getDuration()
  128. const watchedTarget = GM_config.get('WATCH_THRESHOLD') / 100
  129. if (watched < watchedTarget) {
  130. DEBUG.info(`Waiting until watch threshold reached (${watched.toFixed(2)}/${watchedTarget})...`)
  131. return false
  132. }
  133. }
  134. return true
  135. }
  136.  
  137. function isSubscribed () {
  138. DEBUG.info('Checking whether subscribed...')
  139. const subscribeButton = document.querySelector(SELECTORS.SUBSCRIBE_BUTTON)
  140. if (!subscribeButton) {
  141. throw Error('Couldn\'t find sub button')
  142. }
  143. const subscribed = subscribeButton.hasAttribute('subscribe-button-invisible')
  144. DEBUG.info(subscribed ? 'We are subscribed' : 'We are not subscribed')
  145. return subscribed
  146. }
  147.  
  148. function wait () {
  149. if (watchThresholdReached()) {
  150. try {
  151. if (GM_config.get('LIKE_IF_NOT_SUBSCRIBED') || isSubscribed()) {
  152. if (GM_config.get('AUTO_LIKE_LIVE_STREAMS') ||
  153. window.getComputedStyle(document.querySelector('.ytp-live-badge')).display === 'none') {
  154. like()
  155. }
  156. }
  157. } catch (e) {
  158. DEBUG.info(`Failed to like video: ${e}. Will try again in ${GM_config.get('CHECK_FREQUENCY')} ms...`)
  159. }
  160. }
  161. }
  162.  
  163. function isButtonPressed (button) {
  164. return button.classList.contains('style-default-active') ||
  165. button.getAttribute('aria-pressed') === 'true'
  166. }
  167.  
  168. function like () {
  169. DEBUG.info('Trying to like video...')
  170. const likeButton = document.querySelector(SELECTORS.LIKE_BUTTON)
  171. const dislikeButton = document.querySelector(SELECTORS.DISLIKE_BUTTON)
  172. if (!likeButton) {
  173. throw Error('Couldn\'t find like button')
  174. }
  175. if (!dislikeButton) {
  176. throw Error('Couldn\'t find dislike button')
  177. }
  178. const videoId = getVideoId()
  179. if (isButtonPressed(likeButton)) {
  180. DEBUG.info('Like button has already been clicked')
  181. autoLikedVideoIds.push(videoId)
  182. } else if (isButtonPressed(dislikeButton)) {
  183. DEBUG.info('Dislike button has already been clicked')
  184. } else if (autoLikedVideoIds.includes(videoId)) {
  185. DEBUG.info('Video has already been auto-liked. User must ' +
  186. 'have un-liked it, so we won\'t like it again')
  187. } else {
  188. DEBUG.info('Found like button. It\'s unclicked. Clicking it...')
  189. likeButton.click()
  190. if (isButtonPressed(likeButton)) {
  191. autoLikedVideoIds.push(videoId)
  192. DEBUG.info('Successfully liked video')
  193. } else {
  194. DEBUG.info('Failed to like video')
  195. }
  196. }
  197. }
  198. })()