BitChute | 'Add to Playlist' Button Everywhere

Adds a button for the BitChute playlist menu to video thumbnails and listings, right next to the 'Watch Later' button.

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

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name            BitChute | 'Add to Playlist' Button Everywhere
// @namespace       de.sidneys.userscripts
// @homepage        https://gist.githubusercontent.com/sidneys/23018bf607466ebeb5b11c7889774665/raw/
// @version         0.9.2
// @description     Adds a button for the BitChute playlist menu to video thumbnails and listings, right next to the 'Watch Later' button.
// @author          sidneys
// @icon            https://i.imgur.com/4GUWzW5.png
// @noframes
// @include         *://*.bitchute.com/*
// @require         https://greasyfork.org/scripts/38888-greasemonkey-color-log/code/Greasemonkey%20%7C%20Color%20Log.js
// @require         https://greasyfork.org/scripts/374849-library-onelementready-es6/code/Library%20%7C%20onElementReady%20ES6.js
// @connect         bitchute.com
// @grant           GM.addStyle
// @grant           GM.download
// @grant           unsafeWindow
// @run-at          document-start
// ==/UserScript==

/**
 * ESLint
 * @global
 */
/* global onElementReady */
Debug = false


/**
 * Inject Stylesheet
 */
let injectStylesheet = () => {
    console.debug('injectStylesheet')

    GM.addStyle(`
        /* ==========================================================================
           ELEMENTS
           ========================================================================== */

        /* .show-playlist-modal
           ========================================================================== */

        .action-button.show-playlist-modal
        {
            top: 30px;
            font-size: 24px;
            width: 30px;
            height: 30px;
            display: flex;
            align-items: center;
            justify-content: center;
        }

        .action-button.show-playlist-modal > svg.action-icon:hover
        {
            transform: unset;
        }

        .action-button
        {
            cursor: pointer;
        }
    `)
}


/**
 * Monkey Patch History.pushState() to emit a 'pushstate' event on <window>.
 * @see {@link https://stackoverflow.com/a/4585031/1327892}
 * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/History/pushState}
 */
const originalPushState = unsafeWindow.history.pushState
unsafeWindow.history.pushState = (state, title = '', url) => {
    console.debug('history.pushState()', 'state:', JSON.stringify(state), 'title:', title, 'url:', url)

    // Create event
    const event = new CustomEvent('pushstate', { 'detail': state, 'bubbles': true, 'cancelable': false })

    // Emit event
    unsafeWindow.dispatchEvent(event)

    // Call original
    originalPushState.call(unsafeWindow.history, state, title, url)
}


/**
 * Render Button 'Add to Playlist'
 * @param {Element} element - Target Element
 */
let renderButtonElement = (element) => {
    console.debug('renderButtonElement')

    // Create Element
    const buttonElement = document.createElement('span')
    buttonElement.className = 'show-playlist-modal'
    buttonElement.innerHTML = `
        <i class="action-icon fas fa-list fa-fw"></i>
    `
    buttonElement.title = 'Add to Playlist'
    buttonElement.dataset.toggle = 'tooltip'
    buttonElement.dataset.placement = 'bottom'
    unsafeWindow.jQuery(buttonElement).data('video', unsafeWindow.jQuery(element).data('video'))

    if (element.classList.contains('action-button')) {
        buttonElement.classList.add('action-button')
    }

    if (element.classList.contains('toolbox-button')) {
        buttonElement.classList.add('toolbox-button')
    }

    // Render Element
    element.after(buttonElement)

    // Status
    console.debug('Rendered', 'Button Element')
}

/**
 * Render Modal 'Playlist'
 * @param {Element} element - Target Element
 */
let renderPlaylistElement = (element) => {
    console.debug('renderPlaylistElement')

    // Abort if exists
    if (document.querySelector('.playlist-modal')) { return }

    // Create Element
    const playlistElement = document.createElement('div')
    playlistElement.setAttribute('role', 'dialog')
    playlistElement.className = 'modal fade playlist-modal'
    playlistElement.innerHTML = `
    <div class="modal-dialog">
        <div class="modal-content">
            <div class="modal-header">
                <button type="button" class="close" data-dismiss="modal" aria-hidden="true">
                    <i class="fas fa-times fa-fw"></i>
                </button>
                <a href="/" class="spa">
                    <img class="modal-logo logo-full" src="/static/v130/images/logo-full-day.png" alt="BitChute">
                </a>
            </div>
            <div class="modal-body">
                <div class="form-group">
                    <div class="create-playlist-entry-error alert alert-danger hidden"></div>
                    <label>Add to Playlist</label>
                    <span class="options"></span>
                </div>
                <div class="create-playlist-error alert alert-danger hidden"></div>
                <div class="form-group">
                    <form method="post" id="create-playlist-form">
                        <input type="hidden" name="csrfmiddlewaretoken" value="XA3zoHAEsntOaFTteSV3rhIVboyJf5P5HeYjAioyqn2hKQ8IXcBOcKZCTduYIVc4">
                        <label>Create a New Playlist</label>
                        <div class="create-playlist-error-playlist alert alert-danger hidden"></div>
                        <div class="name">
                            <input type="text" name="playlist" class="form-control" autocomplete="off" placeholder="My Playlist" maxlength="100" id="id_playlist">
                            <span class="toolbox-button create" data-video="UkfyMnll0o9O" data-toggle="tooltip" data-placement="bottom" title="Create Playlist">
                                <span class="fa-layers">
                                    <i class="fal fa-square"></i>
                                    <i class="far fa-plus" data-fa-transform="shrink-7"></i>
                                </span>
                            </span>
                        </div>
                        <p class="help">This must be unique. Maximum 100 characters.</p>
                    </form>
                </div>
            </div>
        </div>
    </div>
    `

    // Render Element
    element.before(playlistElement)

    // Status
    console.debug('Rendered', 'Playlist Element')
}


/**
 * Register Event Handlers
 */
let registerEventHandlers = () => {
    console.debug('registerEventHandlers')

    // Add BitChute's internal event handlers
    unsafeWindow.playlistAttachEvents()
    unsafeWindow.playlistAttachModalEvents()
    unsafeWindow.spaAttachEvents()
    unsafeWindow.scrollerAttachEvents()

    // Log message after Playlist shown
    unsafeWindow.jQuery('.playlist-modal')
        .off('shown.bs.modal')
        .on('shown.bs.modal', (event) => {
            console.debug('.playlist-modal#shown.bs.modal')

            console.info('Shown', 'Playlist')
        })

    // Log message after Playlist hidden
    unsafeWindow.jQuery('.playlist-modal')
        .off('hidden.bs.modal')
        .on('hidden.bs.modal', (event) => {
            console.debug('.playlist-modal#hidden.bs.modal')

            console.info('Hidden', 'Playlist')
        })
}


/**
 * Init
 */
let init = () => {
    console.info('init')

    // Add Stylesheet
    injectStylesheet()

    // Add Playlist
    onElementReady('.container', true, (element) => {
        renderPlaylistElement(element)
    })

    // Add Buttons
    onElementReady('.playlist-watch-later', true, (element) => {
        renderButtonElement(element)
    })

    // Add Events
    registerEventHandlers()
}


/**
 * @listens document:Event#readystatechange
 */
document.addEventListener('readystatechange', () => {
    console.debug('document#readystatechange', document.readyState)

    if (document.readyState !== 'interactive') { return }

    /**
     * @listens window:Event#jQuery(document).ready
     */
    unsafeWindow.jQuery(document).ready(() => {
        console.debug('window#jQuery(document).ready')

        init()
    })
})

/**
 * Handle in-page navigation on BitChute
 * @listens window:Event#pushstate
 */
unsafeWindow.addEventListener('pushstate', (event) => {
    console.debug('window#pushstate', 'event.detail:', JSON.stringify(event.detail))

    init()
})