YouTube Auto-Liker

Automatically likes videos of channels you're subscribed to

当前为 2022-10-17 提交的版本,查看 最新版本

  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.14
  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. HIDE_LIKE_NOTIFICATION: {
  59. label: 'Hide like notification',
  60. type: 'checkbox',
  61. default: false
  62. },
  63. LIKE_IF_NOT_SUBSCRIBED: {
  64. label: 'Like if not subscribed',
  65. type: 'checkbox',
  66. default: false,
  67. title: 'Like videos from channels you are not subscribed to'
  68. }
  69. }
  70. })
  71.  
  72. GM_registerMenuCommand('Settings', () => {
  73. GM_config.open()
  74. })
  75.  
  76. function Debugger (name, enabled) {
  77. this.debug = {}
  78. if (!window.console) {
  79. return () => {}
  80. }
  81. Object.getOwnPropertyNames(window.console).forEach(key => {
  82. if (typeof window.console[key] === 'function') {
  83. if (enabled) {
  84. this.debug[key] = window.console[key].bind(window.console, name + ': ')
  85. } else {
  86. this.debug[key] = () => {}
  87. }
  88. }
  89. })
  90. return this.debug
  91. }
  92.  
  93. const DEBUG = new Debugger(GM_info.script.name, GM_config.get('DEBUG_MODE'))
  94.  
  95. const SELECTORS = {
  96. PLAYER: '#movie_player',
  97. SUBSCRIBE_BUTTON: '#subscribe-button > ytd-subscribe-button-renderer > tp-yt-paper-button',
  98. LIKE_BUTTON: '#menu #top-level-buttons-computed > ytd-toggle-button-renderer:nth-child(1), #segmented-like-button button',
  99. DISLIKE_BUTTON: '#menu #top-level-buttons-computed > ytd-toggle-button-renderer:nth-child(2)',
  100. NOTIFICATION: 'ytd-popup-container'
  101. }
  102.  
  103. const autoLikedVideoIds = []
  104.  
  105. setTimeout(wait, GM_config.get('CHECK_FREQUENCY'))
  106.  
  107. function getVideoId () {
  108. const elem = document.querySelector('#page-manager > ytd-watch-flexy')
  109. if (elem && elem.hasAttribute('video-id')) {
  110. return elem.getAttribute('video-id')
  111. } else {
  112. return new URLSearchParams(window.location.search).get('v')
  113. }
  114. }
  115.  
  116. function watchThresholdReached () {
  117. const player = document.querySelector(SELECTORS.PLAYER)
  118. if (player) {
  119. const watched = player.getCurrentTime() / player.getDuration()
  120. const watchedTarget = GM_config.get('WATCH_THRESHOLD') / 100
  121. if (watched < watchedTarget) {
  122. DEBUG.info(`Waiting until watch threshold reached (${watched.toFixed(2)}/${watchedTarget})...`)
  123. return false
  124. }
  125. }
  126. return true
  127. }
  128.  
  129. function isSubscribed () {
  130. DEBUG.info('Checking whether subscribed...')
  131. const subscribeButton = document.querySelector(SELECTORS.SUBSCRIBE_BUTTON)
  132. if (!subscribeButton) {
  133. throw Error('Couldn\'t find sub button')
  134. }
  135. const subscribed = subscribeButton.hasAttribute('subscribed')
  136. DEBUG.info(subscribed ? 'We are subscribed' : 'We are not subscribed')
  137. return subscribed
  138. }
  139.  
  140. function wait () {
  141. if (watchThresholdReached()) {
  142. try {
  143. if (GM_config.get('LIKE_IF_NOT_SUBSCRIBED') || isSubscribed()) {
  144. like()
  145. }
  146. } catch (e) {
  147. DEBUG.info(`Failed to like video: ${e}. Will try again in ${GM_config.get('CHECK_FREQUENCY')} ms...`)
  148. }
  149. }
  150. setTimeout(wait, GM_config.get('CHECK_FREQUENCY'))
  151. }
  152.  
  153. function hideLikeNotification () {
  154. DEBUG.info('Trying to hide notification...')
  155. const notification = document.querySelector(SELECTORS.NOTIFICATION)
  156. if (notification) {
  157. DEBUG.info('Found notification. Hiding it...')
  158. notification.style.display = 'none'
  159. setTimeout(() => {
  160. DEBUG.info('Un-hiding notification')
  161. notification.style.removeProperty('display')
  162. }, 5000)
  163. } else {
  164. DEBUG.info('Couldn\'t find notification')
  165. }
  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 (likeButton.classList.contains('style-default-active') ||
  180. likeButton.getAttribute('aria-pressed') === 'true') {
  181. DEBUG.info('Like button has already been clicked')
  182. autoLikedVideoIds.push(videoId)
  183. } else if (dislikeButton.classList.contains('style-default-active') ||
  184. dislikeButton.getAttribute('aria-pressed') === 'true') {
  185. DEBUG.info('The dislike button has been clicked.')
  186. } else if (autoLikedVideoIds.includes(videoId)) {
  187. DEBUG.info('Video has already been auto-liked. User must ' +
  188. 'have un-liked it, so we won\'t like it again')
  189. } else {
  190. DEBUG.info('Found like button')
  191. if (GM_config.get('HIDE_LIKE_NOTIFICATION')) {
  192. hideLikeNotification()
  193. }
  194. DEBUG.info('It\'s unclicked. Clicking it...')
  195. likeButton.click()
  196. autoLikedVideoIds.push(videoId)
  197. DEBUG.info('Successfully liked video')
  198. }
  199. }
  200. })()