SeenChute

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

安裝腳本?
作者推薦腳本

您可能也會喜歡 SmartChute

安裝腳本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 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()

}) ();