SeenChute

BitChute.com. Adds a "watched" bar to top of video cards.

安装此脚本
作者推荐脚本

您可能也喜欢SmartChute

安装此脚本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name            SeenChute
// @version         19.12.7
// @description     BitChute.com. Adds a "watched" bar to top of video cards.
// @license         MIT
// @author          S-Marty
// @compatible      firefox
// @compatible      chrome
// @compatible      opera
// @namespace       https://github.com/s-marty/SeenChute
// @homepageURL     https://github.com/s-marty/SeenChute
// @icon            https://raw.githubusercontent.com/s-marty/SeenChute/master/images/seenChute.png
// @contributionURL https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=QHFFSLZ7ENUQN&source=url
// @include         /^https?://www\.bitchute\.com/.*$/
// @run-at          document-end
// @grant           GM.getValue
// @grant           GM.setValue
// @noframes
// ==/UserScript==

/* greasyfork.org jshint syntax checking hacks */
/* jshint asi: true */
/* jshint boss: true */
/* jshint esversion: 6 */
/* jshint multistr: true */

/** **********************   Features   **********************
*** Adds a red metered bar over your watched videos by percent.
*** Videos which are watched not more than 1 % will be ignored.
*** Database size is auto-manageable.  Defaults to 2000 videos.
***     Set "var limit_database_To = " to any whole integer.
***     The oldest database records will be truncated first.
*** Bar colors may be edited as well.
*** See "Editable options" in source code below.
*** No extra @require files (jquery et.al.)

***  ***  Does not & will not work well with IE and IEdge  ***/


/* Editable options */
var limit_database_To = 2000;         /* 0 for unlimited. i.e. 5000 to save only the latest 5000 videos */
var bar_top_color     = "#CC3333";    /* A hexadecimal color specified as: #RRGGBB, where the RR (red), GG (green) and BB (blue)*/
var bar_middle_color  = "#F05555";    /* Hex integers specify the components of the color. Values must be between 00 and FF. */
var bar_bottom_color  = "#000000";    /* Edits made here will be lost during userscript updates. Database data survives updates */
/* End Editable options */


(function() {
    "use strict";

    var BC = {};
    var d = document;
    var videoId = '';
    var updater = null;
    var unloader = null;
    var videoViewedMax = 0;
    var listingsAllHeight = 0;
    var listingsPopHeight = 0;
    var channelVideoHeight = 0;

    function iHaveSeen(e) {

        BC.url          = window.location.href;
        BC.host         = window.location.hostname;
        BC.path         = window.location.pathname;
        BC.searchpage   = BC.url.indexOf('/search') !=-1;
        BC.watchpage    = BC.path.indexOf('/video') !=-1;
        BC.profilepage  = BC.path.indexOf('/profile/') !=-1;
        BC.channelpage  = BC.path.indexOf('/channel/') !=-1;
        BC.hashtagpage  = BC.path.indexOf('/hashtag/') !=-1;
        BC.categorypage = BC.path.indexOf('/category/') !=-1;
        BC.playlistpage = BC.path.indexOf('/playlist/') !=-1;
        BC.homepage     = BC.url == location.protocol +"//"+ BC.host +"/";

        if (!BC.loaded) {
            if (!BC.loader) {
                if (BC.loader = qs("#loader-container")) {
                    addListener(BC.loader, function(e) {
                        if (e.target.style.display == 'none') iHaveSeen(e);
                    },{ attributes: true, attributeFilter: ['style'] });
                }
            }

            let style = d.createElement("style");
            style.type = "text/css";
            style.innerText = '\
                    div.video-seen {height: 3px; margin: 0px; padding: 0px; background-color: '+ bar_middle_color +'; border: 0px; \
                    border-top: 1px solid '+ bar_top_color +'; border-bottom: 1px solid '+ bar_bottom_color +'; overflow: hidden;}';
            d.documentElement.appendChild(style);
            BC.loaded = 1;
        }
        else {
            if (BC.page == 'watchpage') {
                watchedlistAdd();
            }
        }

        if (BC.watchpage) {
            BC.page = 'watchpage';
            videoViewedMax = 0;
            videoId = BC.path.match( /video\/([a-z0-9_-]+)\//i )[1];

            if (! BC.api || ! updater) {
                apiUpdater()
            }
            if (! unloader) {
                window.addEventListener('beforeunload', function(e){ watchedlistAdd(e); }, false);
                unloader = true;
            }
            applySeenBars(3000);
            window.setTimeout(function() { showMoreListen(); }, 5000);
        }
        else if (BC.profilepage || BC.hashtagpage || BC.playlistpage) {
            BC.page = 'profilepage';
            applySeenBars();
        }
        else if (BC.channelpage) {
            BC.page = 'channelpage';
            let channelTabs = qs('#channel-tabs.seeing');
            let listingsChannel = qs('.channel-videos-list');
            if (!channelTabs) {
                addListener(listingsChannel, function(e) {
                    let newlistings = qs('.channel-videos-list');
                    let newlistingsHeight = Math.round(newlistings.getBoundingClientRect().height);
                    if (channelVideoHeight != newlistingsHeight) {
                        channelVideoHeight = newlistingsHeight;
                        applySeenBars();
                    }
                },{ childList: true });

                qs('#channel-tabs').classList.add('seeing');
            }

            applySeenBars();
        }
        else if (BC.homepage || BC.categorypage) {
            BC.page = 'homepage';
            let listingTabs = qs('#listing-tabs.seeing');
            let listingsAll = qs('#listing-all > div.row');
            let listingsPopular = qs('#listing-popular > div.row');

            if (!listingTabs) {
                qs("ul.nav-tabs-list li a[href='#listing-all']")
                  .addEventListener('click', function(e){ applySeenBars() }, false);
                qs("ul.nav-tabs-list li a[href='#listing-popular']")
                  .addEventListener('click', function(e){ applySeenBars() }, false);
                qs("ul.nav-tabs-list li a[href='#listing-subscribed']")
                  .addEventListener('click', function(e){ applySeenBars() }, false);
                qs("ul.nav-tabs-list li a[href='#listing-trending']")
                  .addEventListener('click', function(e){ applySeenBars(); trendingTabs() }, false);

                addListener(listingsAll, function(e) {
                    let newlistings = qs('#listing-all > div.row');
                    let newlistingsHeight = Math.round(newlistings.getBoundingClientRect().height);
                    if (listingsAllHeight != newlistingsHeight) {
                        listingsAllHeight = newlistingsHeight;
                        applySeenBars();
                    }
                },{ childList: true });

                addListener(listingsPopular, function(e) {
                    let newlistings = qs('#listing-popular > div.row');
                    let newlistingsHeight = Math.round(newlistings.getBoundingClientRect().height);
                    if (listingsPopHeight != newlistingsHeight) {
                        listingsPopHeight = newlistingsHeight;
                        applySeenBars();
                    }
                },{ childList: true });

                qs('#listing-tabs').classList.add('seeing');
            }
            listingsAllHeight = Math.round(listingsAll.getBoundingClientRect().height);
            listingsPopHeight = Math.round(listingsPopular.getBoundingClientRect().height);
            applySeenBars();
        }
        else return;
    }

    function trendingTabs(e) {
        qs("ul.nav.nav-tabs li a[href='#trending-day']")
          .addEventListener('click', function(e){ applySeenBars() }, false);
        qs("ul.nav.nav-tabs li a[href='#trending-week']")
          .addEventListener('click', function(e){ applySeenBars() }, false);
        qs("ul.nav.nav-tabs li a[href='#trending-month']")
          .addEventListener('click', function(e){ applySeenBars() }, false);
    }

    function apiUpdater() {
        if (BC.api = qs('video#player')) {
            if (! updater) {
                BC.api.addEventListener('timeupdate', function(e){ onPlayProgress(e); }, false);
                updater = true;
            }
        }
        else window.setTimeout(apiUpdater, 1000)
    }

    function onPlayProgress(e) {
        if (! BC.api) return;
        let active, liveBar, current, i;
        let duration = parseFloat(BC.api.duration);
        let valuenow = parseFloat(BC.api.currentTime);
        let completed = Math.ceil(valuenow / duration * 100);
        if (completed > videoViewedMax && completed <= 100) {
            videoViewedMax = completed;
            active = qsa('.video-card.active');
            if (active.length) {
                for (i = 0; i < active.length; i++) {
                    if (liveBar = active[i].querySelector('.video-seen')) {
                        current = parseInt(liveBar.style.width);
                        if (videoViewedMax > current) {
                            liveBar.title = videoViewedMax +'% Watched';
                            liveBar.style.width = videoViewedMax +'%';
                        }
                    }
                    else {
                        let card = active[i].querySelector('.video-card-image');
                        let bar = d.createElement("div");
                        bar.innerText = "&nbsp;";
                        bar.className = "video-seen";
                        bar.title = videoViewedMax +'% Watched';
                        bar.style.width = videoViewedMax +'%';
                        card.insertBefore(bar, card.firstChild);
                    }
                }
            }
        }
        if (completed == 100) {
            watchedlistAdd();
        }
    }

    function applySeenBars(ms = 2000) {
        window.setTimeout(_applySeenBars, ms);
    }

    function _applySeenBars(e) {
        let i, n,
            cards = [],
            selector = '',
            selectors = [
                '.video-card', 
                '.video-trending-image-container', 
                '.channel-videos-container', 
                '.image-container'
            ];

        selectors.some(function(item) {
            if (qs(item) !== null) {
                selector = item.split(',').join(':not([seen]), ') + ':not([seen])';
                if (cards.length) {
                    cards = cards.concat(Array.prototype.slice.call(qsa(selector)));
                }
                else {
                    cards = Array.prototype.slice.call(qsa(selector));
                }
            }
        });

        if (cards.length) {
            try {
                for (i = 0; i < cards.length; i++) {
                    let link = cards[i].querySelector('a');
                    let card = cards[i].querySelector('.video-card-image, .video-trending-image, .channel-videos-image, .image');
                    if (card) {
                        let href = link.getAttribute("href");
                        let video = href.match( /\/video\/([a-z0-9_-]+)\//i );
                        if (video) {
                            if (BC.watched.has(video[1])) {
                                let bar = d.createElement("div");
                                bar.innerText = "&nbsp;";
                                bar.className = "video-seen";
                                bar.title = BC.watched.get(video[1]) +'% Watched';
                                bar.style.width = BC.watched.get(video[1]) +'%';
                                card.insertBefore(bar, card.firstChild);
                            }
                        }
                    }
                    cards[i].setAttribute('seen', 'true')
                }
            } catch (e) {console.error('SeenChute: applyWatchedlist: '+ e);}
        }
    }

    function showMoreListen() {
        let showMore = qs('.show-more');
        if (showMore) {
            showMore.addEventListener('click', function(e) {
                setTimeout(function() {
                    applySeenBars();
                    showMoreListen();
            }, 2000)}, false)
        }
    }

    function watchedlistAdd(e) {
        if (BC.page != 'watchpage') return false;
        let n, update = false;
        if (videoId && videoViewedMax > 1) {
            if (BC.watched.has(videoId)) {
                if (BC.watched.get(videoId) < videoViewedMax) {
                    BC.watched.set(videoId, videoViewedMax)
                }
                update = true;
            }
            if (! update) {
                BC.watched.set(videoId, videoViewedMax);
                let limit = limit_database_To ? parseInt(limit_database_To) : 0;
                if (limit && BC.watched.size > limit) {
                    do {
                        BC.watched.delete(BC.watched.keys().next().value)
                    } while (BC.watched.size > limit)
                }
            }
            GM.setValue('watched', JSON.stringify(Array.from(BC.watched)))
        }

        videoId = '';
        BC.api = null;
        updater = null;
        videoViewedMax = 0;

        return false;
    }

    function qs(selector) { return document.querySelector(selector) }

    function qsa(selector) { return document.querySelectorAll(selector) }

    function addListener(target, fn, config) {
        // jshint ignore:start
        var cfg = {...{attributes:!1, childList:!1, characterData:!1, subtree:!1}, ...config};
        // jshint ignore:end
        var observer = new MutationObserver(function(mutations) {
          mutations.forEach(function(mutation) { fn(mutation) })});
        observer.observe(target, cfg);
        return observer
    }

    function init(e) {
        GM.getValue('watched', "[]").then(function (value) {
            BC.watched = [];
            BC.page = '';
            BC.api = null;
            BC.url = null;
            BC.host = null;
            BC.path = null;
            BC.loaded = !1;
            BC.loader = null;

            if (value && value != '[]') {
                BC.watched = new Map(JSON.parse(value));
            }
            else {
                    /* Install Database */
                GM.setValue('watched', '[ ]');
                window.location.replace(window.location.href);
            }
        }).catch (error => {
            console.error('SeenChute: Error in promise loading watched list: '+ error)
        })
        window.setTimeout(iHaveSeen, 5000)
    }

      /* Not in Frames */
    if (window.self == window.top) init()

}) ();