SeenChute

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

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey, Greasemonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Userscripts.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een gebruikersscriptbeheerder nodig.

(Ik heb al een user script manager, laat me het downloaden!)

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

(Ik heb al een beheerder - laat me doorgaan met de installatie!)

// ==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()

}) ();