BitChute | 'Add to Playlist' Button Everywhere

Adds a button for BitChute playlist menu to video thumbnails and listings (next to the 'Watch Later' button)

当前为 2021-02-13 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name BitChute | 'Add to Playlist' Button Everywhere
  3. // @namespace de.sidneys.userscripts
  4. // @homepage https://gist.githubusercontent.com/sidneys//raw/
  5. // @version 0.9.0
  6. // @description Adds a button for BitChute playlist menu to video thumbnails and listings (next to the 'Watch Later' button)
  7. // @author sidneys
  8. // @icon https://i.imgur.com/4GUWzW5.png
  9. // @noframes
  10. // @include *://*.bitchute.com/*
  11. // @require https://greasyfork.org/scripts/38888-greasemonkey-color-log/code/Greasemonkey%20%7C%20Color%20Log.js
  12. // @require https://greasyfork.org/scripts/374849-library-onelementready-es6/code/Library%20%7C%20onElementReady%20ES6.js
  13. // @connect bitchute.com
  14. // @grant GM.addStyle
  15. // @grant GM.download
  16. // @grant unsafeWindow
  17. // @run-at document-start
  18. // ==/UserScript==
  19.  
  20. /**
  21. * ESLint
  22. * @global
  23. */
  24. /* global onElementReady */
  25. Debug = false
  26.  
  27.  
  28. /**
  29. * Inject Stylesheet
  30. */
  31. let injectStylesheet = () => {
  32. console.debug('injectStylesheet')
  33.  
  34. GM.addStyle(`
  35. /* ==========================================================================
  36. ELEMENTS
  37. ========================================================================== */
  38.  
  39. /* .show-playlist-modal
  40. ========================================================================== */
  41.  
  42. .action-button.show-playlist-modal
  43. {
  44. top: 30px;
  45. font-size: 24px;
  46. width: 30px;
  47. height: 30px;
  48. display: flex;
  49. align-items: center;
  50. justify-content: center;
  51. }
  52.  
  53. .action-button.show-playlist-modal > svg.action-icon:hover
  54. {
  55. transform: unset;
  56. }
  57.  
  58. .action-button
  59. {
  60. cursor: pointer;
  61. }
  62. `)
  63. }
  64.  
  65.  
  66. /**
  67. * Monkey Patch History.pushState() to emit a 'pushstate' event on <window>.
  68. * @see {@link https://stackoverflow.com/a/4585031/1327892}
  69. * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/History/pushState}
  70. */
  71. const originalPushState = unsafeWindow.history.pushState
  72. unsafeWindow.history.pushState = (state, title = '', url) => {
  73. console.debug('history.pushState()', 'state:', JSON.stringify(state), 'title:', title, 'url:', url)
  74.  
  75. // Create event
  76. const event = new CustomEvent('pushstate', { 'detail': state, 'bubbles': true, 'cancelable': false })
  77.  
  78. // Emit event
  79. unsafeWindow.dispatchEvent(event)
  80.  
  81. // Call original
  82. originalPushState.call(unsafeWindow.history, state, title, url)
  83. }
  84.  
  85.  
  86. /**
  87. * Render Button 'Add to Playlist'
  88. * @param {Element} element - Target Element
  89. */
  90. let renderButtonElement = (element) => {
  91. console.debug('renderButtonElement')
  92.  
  93. // Create Element
  94. const buttonElement = document.createElement('span')
  95. buttonElement.className = 'show-playlist-modal'
  96. buttonElement.innerHTML = `
  97. <i class="action-icon fas fa-list fa-fw"></i>
  98. `
  99. buttonElement.title = 'Add to Playlist'
  100. buttonElement.dataset.toggle = 'tooltip'
  101. buttonElement.dataset.placement = 'bottom'
  102. unsafeWindow.jQuery(buttonElement).data('video', unsafeWindow.jQuery(element).data('video'))
  103.  
  104. if (element.classList.contains('action-button')) {
  105. buttonElement.classList.add('action-button')
  106. }
  107.  
  108. if (element.classList.contains('toolbox-button')) {
  109. buttonElement.classList.add('toolbox-button')
  110. }
  111.  
  112. // Render Element
  113. element.after(buttonElement)
  114.  
  115. // Status
  116. console.debug('Rendered', 'Button Element')
  117. }
  118.  
  119. /**
  120. * Render Modal 'Playlist'
  121. * @param {Element} element - Target Element
  122. */
  123. let renderPlaylistElement = (element) => {
  124. console.debug('renderPlaylistElement')
  125.  
  126. // Abort if exists
  127. if (document.querySelector('.playlist-modal')) { return }
  128.  
  129. // Create Element
  130. const playlistElement = document.createElement('div')
  131. playlistElement.setAttribute('role', 'dialog')
  132. playlistElement.className = 'modal fade playlist-modal'
  133. playlistElement.innerHTML = `
  134. <div class="modal-dialog">
  135. <div class="modal-content">
  136. <div class="modal-header">
  137. <button type="button" class="close" data-dismiss="modal" aria-hidden="true">
  138. <i class="fas fa-times fa-fw"></i>
  139. </button>
  140. <a href="/" class="spa">
  141. <img class="modal-logo logo-full" src="/static/v130/images/logo-full-day.png" alt="BitChute">
  142. </a>
  143. </div>
  144. <div class="modal-body">
  145. <div class="form-group">
  146. <div class="create-playlist-entry-error alert alert-danger hidden"></div>
  147. <label>Add to Playlist</label>
  148. <span class="options"></span>
  149. </div>
  150. <div class="create-playlist-error alert alert-danger hidden"></div>
  151. <div class="form-group">
  152. <form method="post" id="create-playlist-form">
  153. <input type="hidden" name="csrfmiddlewaretoken" value="XA3zoHAEsntOaFTteSV3rhIVboyJf5P5HeYjAioyqn2hKQ8IXcBOcKZCTduYIVc4">
  154. <label>Create a New Playlist</label>
  155. <div class="create-playlist-error-playlist alert alert-danger hidden"></div>
  156. <div class="name">
  157. <input type="text" name="playlist" class="form-control" autocomplete="off" placeholder="My Playlist" maxlength="100" id="id_playlist">
  158. <span class="toolbox-button create" data-video="UkfyMnll0o9O" data-toggle="tooltip" data-placement="bottom" title="Create Playlist">
  159. <span class="fa-layers">
  160. <i class="fal fa-square"></i>
  161. <i class="far fa-plus" data-fa-transform="shrink-7"></i>
  162. </span>
  163. </span>
  164. </div>
  165. <p class="help">This must be unique. Maximum 100 characters.</p>
  166. </form>
  167. </div>
  168. </div>
  169. </div>
  170. </div>
  171. `
  172.  
  173. // Render Element
  174. element.before(playlistElement)
  175.  
  176. // Status
  177. console.debug('Rendered', 'Playlist Element')
  178. }
  179.  
  180.  
  181. /**
  182. * Register Event Handlers
  183. */
  184. let registerEventHandlers = () => {
  185. console.debug('registerEventHandlers')
  186.  
  187. // Add BitChute's internal event handlers
  188. unsafeWindow.playlistAttachEvents()
  189. unsafeWindow.playlistAttachModalEvents()
  190. unsafeWindow.spaAttachEvents()
  191. unsafeWindow.scrollerAttachEvents()
  192.  
  193. // Log message after Playlist shown
  194. unsafeWindow.jQuery('.playlist-modal')
  195. .off('shown.bs.modal')
  196. .on('shown.bs.modal', (event) => {
  197. console.debug('.playlist-modal#shown.bs.modal')
  198.  
  199. console.info('Shown', 'Playlist')
  200. })
  201.  
  202. // Log message after Playlist hidden
  203. unsafeWindow.jQuery('.playlist-modal')
  204. .off('hidden.bs.modal')
  205. .on('hidden.bs.modal', (event) => {
  206. console.debug('.playlist-modal#hidden.bs.modal')
  207.  
  208. console.info('Hidden', 'Playlist')
  209. })
  210. }
  211.  
  212.  
  213. /**
  214. * Init
  215. */
  216. let init = () => {
  217. console.info('init')
  218.  
  219. // Add Stylesheet
  220. injectStylesheet()
  221.  
  222. // Add Playlist
  223. onElementReady('.container', true, (element) => {
  224. renderPlaylistElement(element)
  225. })
  226.  
  227. // Add Buttons
  228. onElementReady('.playlist-watch-later', true, (element) => {
  229. renderButtonElement(element)
  230. })
  231.  
  232. // Add Events
  233. registerEventHandlers()
  234. }
  235.  
  236.  
  237. /**
  238. * @listens document:Event#readystatechange
  239. */
  240. document.addEventListener('readystatechange', () => {
  241. console.debug('document#readystatechange', document.readyState)
  242.  
  243. if (document.readyState !== 'interactive') { return }
  244.  
  245. /**
  246. * @listens window:Event#jQuery(document).ready
  247. */
  248. unsafeWindow.jQuery(document).ready(() => {
  249. console.debug('window#jQuery(document).ready')
  250.  
  251. init()
  252. })
  253. })
  254.  
  255. /**
  256. * Handle in-page navigation on BitChute
  257. * @listens window:Event#pushstate
  258. */
  259. unsafeWindow.addEventListener('pushstate', (event) => {
  260. console.debug('window#pushstate', 'event.detail:', JSON.stringify(event.detail))
  261.  
  262. init()
  263. })