get_iplayer helper

Adds bright pink buttons to every program in BBC's iPlayer website (https://www.bbc.co.uk/iplayer) that allows quick programming of open source downloader get_iplayer Web PVR Manager (available at https://github.com/get-iplayer/get_iplayer/wiki/installation)

// ==UserScript==
// @name         get_iplayer helper
// @namespace    http://tampermonkey.net/
// @include      http://www.bbc.co.uk/*
// @include      https://www.bbc.co.uk/*
// @version      0.50
// @description  Adds bright pink buttons to every program in BBC's iPlayer website (https://www.bbc.co.uk/iplayer) that allows quick programming of open source downloader get_iplayer Web PVR Manager (available at https://github.com/get-iplayer/get_iplayer/wiki/installation)
// @author       Jake Lewis ( no relation to Phil ).
// @supportURL   [email protected]
// @connect     bbc.co.uk
// @connect     localhost
// @license     GPL version 3 or any later version; http://www.gnu.org/copyleft/gpl.html
// @require     http://code.jquery.com/jquery-latest.min.js
// @grant       GM.setValue
// @grant       GM.getValue
// @grant       GM.listValues
// @grant       GM.deleteValue
// ==/UserScript==


//There are three types of pages this script is designed to work on:
//  catagory pages such as https://www.bbc.co.uk/iplayer/categories/arts/featured
//  episodes pages such as https://www.bbc.co.uk/iplayer/episodes/b006m86d
//  episode  pages such as https://www.bbc.co.uk/iplayer/episode/b0c0bt44/eastenders-15012019


//(function() {
//    'use strict';

    //variables that can be passed to get_iplayer's Web PVR Manager - they are set on our banner in the bbc page

    var recordDirectoryLabel = "recordDirectory";// user should fill in their own value on screen
    var PVRLabel = "PVR";//http://localhost:1935"//the default.
    var fps25Label = "fps25"; //0 ;//the default.
    var forceLabel = "force";// = 0;//the default.
    var modesLabel = "modes";// = best
    var subtitlesLabel ="subtitles";// = 0;// the default.
    var thumbLabel = "thumb";// = 0;// the default.
    var metadataLabel = "metadata";// = 0;// the default.




    var appendCatLabel = "appendCat";// = 0
    var appendNameLabel = "appendName";// = 1
    var appendEpisodeLabel = "appendEpisode";//=1




    var Get_iPlayerHelper_Category = "Get_iPlayerHelper_Category";

    $(document).ready(function() {
        console.log("get_ihelper: A helper script to be used with get_iplayer: https://github.com/get-iplayer/get_iplayer/wiki/installation ");
        console.log("get_ihelper: Bugs to [email protected]. Be sure to include: OS, Browser type, the bbc URL, and a good description of the issue. ");

        (async () => {

            await GM.deleteValue("localhostPort");

            var keys = await GM.listValues();
            for (var i = 0; i < keys.length; i++){
                //console.log("get_ihelper: "+keys[i]+" "+ await GM.getValue(keys[i]));
            }

            parsePage();
        })();//async
    });

function createBaseControl(prettyName, label, inputNode){
    var controlSpan = document.createElement("span");
    var labelNode = document.createElement("label");
    labelNode.style="white-space: nowrap";
    labelNode.for = label;
    labelNode.innerText = prettyName;
    controlSpan.appendChild(labelNode);
    if(inputNode!=null){
        labelNode.appendChild(inputNode);}
    return controlSpan;
}

function createTextControl(prettyName, label, placeholding, defaultString){


        var inputNode = document.createElement('input');
        inputNode.type = 'text';
        inputNode.id = inputNode.name = label;
        (async () => { inputNode.value = await GM.getValue(label, defaultString); })();
        inputNode.placeholder = placeholding;
        inputNode.onchange = function(){
            (async () => { await GM.setValue(label, inputNode.value); })();
        }

      //return createBaseControl(prettyName, label, inputNode);
        return inputNode;
}

    function createCheckControl(prettyName, label, defaultString){

        var inputNode = document.createElement('input');
        inputNode.type = 'checkbox';
        inputNode.id = inputNode.name = label;
        (async () => { var v = await GM.getValue(label, defaultString);
                       inputNode.checked = v=="1";
        })();
        inputNode.onchange = function(){
            (async () => { var v = "0";
                          if(inputNode.checked){v = "1" }
                          await GM.setValue(label, v ); })();
        }
       return createBaseControl(prettyName, label, inputNode);

}

function parsePage(){

    var orbModules = document.querySelector('#orb-modules');
    if(orbModules != null){
        var controlsDiv = document.createElement("div");
        controlsDiv.style.background = "#F54997";
        controlsDiv.style.color = "white";
        controlsDiv.style.padding = "5px 5px";
        //controlsDiv.style.margin = "5px 5px";
        orbModules.parentNode.insertBefore(controlsDiv, orbModules);


        var tbl = document.createElement('table');
        //tbl.style.width = '100%';
        //tbl.setAttribute('border', '1');
        var tbdy = document.createElement('tbody');
            var row1 = document.createElement('tr');
                var reclabel = document.createElement('td'); row1.appendChild(reclabel);
                    reclabel.appendChild( createBaseControl("  Recording Directory: ", recordDirectoryLabel));
                var rec = document.createElement('td'); row1.appendChild(rec)
                    rec.appendChild( createTextControl("  Recording Directory: ", recordDirectoryLabel, " Enter file directory "),"" );
                var dir = document.createElement('td'); row1.appendChild(dir)
                    dir.appendChild( createCheckControl(" prepend Category Dir ", appendCatLabel, "0"));
                    dir.appendChild( createCheckControl(" prepend Name Dir ",appendNameLabel, "1"));
                    dir.appendChild( createCheckControl(" prepend Episode Dir ",appendEpisodeLabel, "1"));
             tbdy.appendChild(row1);

             var row2 = document.createElement('tr');
                var modeslabel = document.createElement('td'); row2.appendChild(modeslabel);
                    modeslabel.appendChild( createBaseControl("Recording Modes: ", modesLabel));
                var modes = document.createElement('td'); row2.appendChild(modes)
                    modes.appendChild( createTextControl("  modes: ", modesLabel, "",'best') );
                var checks = document.createElement('td'); row2.appendChild(checks)
                    checks.appendChild( createCheckControl("  fps25 ", fps25Label, "0") );
                    checks.appendChild( createCheckControl("  force ", forceLabel, "0") );
                    checks.appendChild( createCheckControl("  subtitles ", subtitlesLabel, "0") );
                    checks.appendChild( createCheckControl("  thumb ", thumbLabel,"0"));
                    checks.appendChild( createCheckControl("  metadata ", metadataLabel, "0"));
             tbdy.appendChild(row2);

             var row3 = document.createElement('tr');
                var portlabel = document.createElement('td'); row3.appendChild(portlabel);
                    portlabel.appendChild( createBaseControl("PVR: ", PVRLabel));
                var port = document.createElement('td'); row3.appendChild(port)
                    port.appendChild( createTextControl("PVR: ", PVRLabel, "", "http://localhost:1935") );
             tbdy.appendChild(row3);


        tbl.appendChild(tbdy);
        controlsDiv.appendChild(tbl);

    }



        var categoryString = "";
        var nameString = "";
        var episodeString = "";
        var isCategoriesPage = true;


        //on an individual programme/episode page
        if(window.location.href.includes("www.bbc.co.uk/iplayer/episode/")){
            var myLocation = document.createElement('a');// just a dummy
            myLocation.href = window.location.href;
            var urlParams = new URLSearchParams(window.location.search);
            if(urlParams.has(Get_iPlayerHelper_Category)){
                categoryString = decodeURIComponent(urlParams.get(Get_iPlayerHelper_Category));
                urlParams.delete(Get_iPlayerHelper_Category);
                myLocation.search = urlParams.toString();
            }
            var nameElement = document.querySelector('span.typo');
            if(nameElement != null) nameString = nameElement.innerText;
           // var singleElement = document.querySelector('div.playback-content');
            var singleElement = document.querySelector('div.hero-meta__content');
            //console.log("get_ihelper: "+"singleElement.length:"+singleElement.length);
            if(singleElement!=null ){
                singleElement.insertBefore(createButtons(myLocation, categoryString, nameString, episodeString, nameString), singleElement.children[0]);
            }
            isCategoriesPage = false;

        }

        //on an episodes page
        var headerElement = document.querySelector('h1.hero-header__title');
        if(headerElement!=null){
            if(window.location.href.includes("www.bbc.co.uk/iplayer/episodes/")){
                isCategoriesPage = false;
                nameString = headerElement.innerText;
                var catElement = document.querySelector('div.hero-header__label');
                if(catElement != null){
                    categoryString = catElement.innerText;}


            }
        }



    //on all pages




        var targetElements = document.querySelectorAll('div.content-item');
        console.log("get_ihelper"+"targetElements.length:"+targetElements.length);


        for (var i = 0; i < targetElements.length; i++) {

            var targetElement = targetElements[i];
            console.log("get_ihelper"+i+" "+ targetElement);
            var content_item__link = targetElement.querySelector('a.content-item__link');
            if(content_item__link != null){
                var contentItemLabels = content_item__link.querySelector('div.content-item__labels');
                if(contentItemLabels!=null){
                    var catElement2 = contentItemLabels.querySelector('span.typo');
                    if(catElement2 != null){
                        if(isCategoriesPage){
                            categoryString = catElement2.innerText;}
                    }
                }
                var titleElement = content_item__link.querySelector('div.content-item__title');
                if( titleElement != null ){
                    if(isCategoriesPage){
                        nameString = titleElement.innerText;
                        if(nameString.endsWith("...") && titleElement.querySelector('span.tvip-hide')!=null){ //bbc uses this to hide the end of long titles
                            nameString = nameString.substring(0, nameString.length-3);
                        }
                    }
                }
                targetElement.insertBefore(createButtons(content_item__link, categoryString, nameString, episodeString, episodeString, nameString), content_item__link);
            }else{
                // a 'This Episode' element in episodes page
                content_item__link = targetElement.querySelector('span.content-item__link');
                if(content_item__link!=null){
                    if(content_item__link.innerText.includes('This Episode')){
                       targetElement.insertBefore(createButtons(location,categoryString, nameString, episodeString, nameString), content_item__link);
                    }
                }
            }

           if(isCategoriesPage){
               categoryString = "";
               nameString = "";
           }




        }//targetElements



        var targetEls = document.querySelectorAll('li.grid__item');
        console.log("get_ihelper li.grid__item targetElements.length:"+targetEls.length);
        for (var it = 0; it < targetEls.length; it++) {
            var targetEl = targetEls[it];
            var content_item = targetEl.querySelector('a');
            if(content_item != null){
                var contentItemLab = content_item.querySelector('div.content-item-root__meta');
                if(contentItemLab!=null){
                    if(isCategoriesPage){
                        nameString = contentItemLab.innerText;
                    }else{
                        episodeString = contentItemLab.innerText;
                    }
                    // console.log("get_ihelper episodeString:"+episodeString);
                }
                targetEl.prepend(createButtons(content_item, categoryString, nameString, episodeString, nameString));//, content_item);
            }
        }


}

function createButtons(videoElement, catDirectory, nameDirectory, episodeDirectory, searchTerm){
    console.log("get_ihelper createButtons("+catDirectory+"/"+nameDirectory+"/"+episodeDirectory+")");
    var videoURL = videoElement.href;
    if(catDirectory.length>0){
        var urlParams = new URLSearchParams(videoElement.search);
        console.log("urlParams:"+urlParams.toString());
        if( urlParams.has(Get_iPlayerHelper_Category) == false){
           urlParams.append(Get_iPlayerHelper_Category, catDirectory);
           videoElement.search = urlParams.toString();} //this has to be passed in the url as it's not avail in the episode page
    }

    var buttonSpan = null;
    if(videoElement.parentNode !=null) buttonSpan = videoElement.parentNode.querySelector("span#helper_buttons");
    if(buttonSpan == null){
        buttonSpan = document.createElement("span");
        buttonSpan.id = "helper_buttons";
    }
    var safeCatDir = makeDirectorySafeName(catDirectory);
    var safeNameDir = makeDirectorySafeName(nameDirectory);
    var safeEpisodeDir = makeDirectorySafeName(episodeDirectory);
    buttonSpan.appendChild( createButton(videoURL, safeCatDir, safeNameDir, safeEpisodeDir, "", "Add QuickURL to Queue", 'pvr_queue') );
   // buttonSpan.appendChild( createButton('', safeCatDir, safeNameDir, safeEpisodeDir, searchTerm, "Search Get_iPlayer ", 'search_progs'));
    //console.log("get_ihelper: "+safeCatDir+"/"+safeNameDir+" search:"+searchTerm+" "+videoURL);
    return buttonSpan;
}


function createButton(videoURL, catDirectory, nameDirectory, episodeDirectory, searchTerm, label, nextPage){

            var addButton = document.createElement("button");
            addButton.appendChild(document.createTextNode(label));
            //addButton.style.width = "170px";
            //addButton.style.left = "15%";
            addButton.style.backgroundColor = "#F54997";
            addButton.style.color = "white";
            addButton.style.textAlign = "center";
            addButton.style.padding = "2px 5px";
            addButton.style.margin = "0px 5px";
            addButton.style.fontSize = "9px";
            addButton.style.border = "3px";
            addButton.style.cursor = "pointer";
            addButton.style.borderRadius = "2px";
            //addButton.style.fontFamily = "Roboto, Arial, sans-serif";
            addButton.style.textDecoration = "none";

            //our data
            addButton.videoURL = videoURL;
            addButton.catDirectory = catDirectory;
            addButton.nameDirectory = nameDirectory;
            addButton.episodeDirectory = episodeDirectory;





            addButton.onclick = function buttonClick(evt){

                (async () => {
                      console.log("get_ihelper: "+evt.target.videoURL);

                    var directorySeperator = "\\"; //    "\\"  for windows, "/" for linux, mac
                    var directoryName = "";
                    if( await GM.getValue(appendCatLabel, "1")=="1" && evt.target.catDirectory.length > 0) directoryName += directorySeperator+evt.target.catDirectory;
                    if( await GM.getValue(appendNameLabel, "1")=="1"&& evt.target.nameDirectory.length >0) directoryName += directorySeperator+evt.target.nameDirectory;
                    if( await GM.getValue(appendEpisodeLabel, "1")=="1"&& evt.target.episodeDirectory.length >0) directoryName += directorySeperator+evt.target.episodeDirectory;
                    var metadataString = await GM.getValue(metadataLabel, "1")=="1" ? 'generic' : '';


                      post( await GM.getValue(PVRLabel,"http://localhost:1935")+'/', {
                           'SEARCH':searchTerm,
                           'SEARCHFIELDS':'name',
                           'PROGTYPES': 'tv',
                           'URL': evt.target.videoURL,
                           'NEXTPAGE':nextPage,
                           'OUTPUT': await GM.getValue(recordDirectoryLabel, "") + directoryName,
                           'FORCE':await GM.getValue(forceLabel, "0"),
                           'FPS25': await GM.getValue(fps25Label, "0"),
                           'MODES': await GM.getValue(modesLabel, "best"),
                           'SUBTITLES':await GM.getValue(subtitlesLabel,"0"),
                           'THUMB':    await GM.getValue(thumbLabel,"0"),
                           'METADATA': metadataString
                           }
                      );
                 })();

            };
    return addButton;
}


function post(path, params, method) {
    method = method || "post"; // Set method to post by default if not specified.
    var form = document.createElement("form");
    form.setAttribute("method", method);
    form.setAttribute("action", path);
    form.setAttribute("enctype", "multipart/form-data");
    form.setAttribute("target", "_blank");//disable this for easier debugging

    for(var key in params) {
        if(params.hasOwnProperty(key)) {
            var hiddenField = document.createElement("input");
            hiddenField.setAttribute("type", "hidden");
            hiddenField.setAttribute("name", key);
            hiddenField.setAttribute("value", params[key]);


            form.appendChild(hiddenField);
        }
    }

    document.body.appendChild(form);
    form.submit();
}

var safeCharRegex = /[<>/\|?*.]/g;

function makeDirectorySafeName(inString){

    //get_iplayer can't write to directories with this character in the name!
    inString = inString.replace("’","'"); //at least looks similar.

    //remove Windows illegal directory characters
    inString = inString.replace(":", "-");
    inString = inString.replace('"', "'");//at least looks similar.

    return inString.replace(safeCharRegex, '_');
}



//})();