Grab Radio 101.ru

Grab radio stream from 101.ru

// ==UserScript==
// @name         Grab Radio 101.ru
// @namespace    https://github.com/AlekPet/
// @version      0.3.1
// @description  Grab radio stream from 101.ru
// @copyright    2018, AlekPet (https://github.com/AlekPet)
// @author       AlekPet
// @license      MIT; https://opensource.org/licenses/MIT
// @match        http*://101.ru/*
// @icon         data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAEIElEQVRIS+1Vf0zUZRj/PMcdx4FgtWzi8Ec6RNwqlxJII6RftmNm4VotQREtWJZpDDA2Gg3Eu7A1clISP7MthjJhTijdWrr+yLA1QR1Xo4aMtuAQwfR7P7h72vNl73Xc2fyr/urd3n2/977v9/k8n+f5fN4jZmb8i4P+B7hbdf+bErnOX+CBzD0UW1rEq+w7SJu4zY3Wk7iB+Xi7ZyOGrriQk3mdtpbGc7U9ku6WdfB+gIGz5RQ7Byewyp5PAy19rLEZboqGxpF4tmAJdTTf5H5HFKpsRiIiiPiCn/8EGgLghDA4V9bDCdaHMDqk4VeHH9sOrqQTrbf40mAkhIEKXlY7yJevjQLmKTycHMX208cNpS9v4A++qadzZYf5iRWpFMYgybadzu/v5UXPrcbokBtDDh/ybUl0vOVPnUHlASKDwaAz+O7HG5z//rforc/C0oWxeO2TVuQ++Si+uNiN1zOzsSFxfSiAE0m2fLrUdIE1jsJtvwkuWJCVt5BOHnNxv8OM96qg90DKIwBNpxxoqEiF0RiBXfXNyHtqHY71daEoaxMylj82CzDb5LdoHA9gRWMJx2evQcumblzne7CrIx3DjhnkWV36/sHDHjaYPfh5eBoUdQt1nT/R2aMZnPFIAlW0d3PtmTZCzDiKrZu5xlryNwPJSmjLnJmZgcfj0ae6SdS6lEeGKpPRaITJZNKnDL/fD5/PF4gV5gPZlOl2u+H1enUwKYd8KCCyJ0FlTanIYrHogPJbvpOzMvUzwXdRKAM5LAEVOwUiwSIiInRAeVcs5GxwUrraQi87xcDlcumH5bcEUUACIuUILp3ZbA7YQMqqGIcx8Din+Gz2x7j6w029oZa1iVzQvh6x982aS4EpFQnY5LSXX6n6GtWFa5GevIRUeVSP5jAYLGvlP65pSPtsB2AikoDDV6e5psiJ+DX3g+LisDTRh93lE4ToMezbbeE9efF4qbIHF0cu6+rZ+2IaV2/J1c2o90WVyK+5uX9nHWKeTkPk8kU4ktVF81KS+IVDqVye+zttrViGnG2WgAc+PzHJDZ2/oM2+mrfbTpP9zTT44ca7nY3o2luOBXH3zjo+uAfCYGzEhfSmnfjt+1G077+C52tT+MPicSppWIzFD5pR+IYPHb1j9MxGjadmJtFcs5ILar+iA4XrEL9gHoq//BRtRfswPyqGRAhzALwT03zGWhfogTDYbE/hQyVOKjmagGWJBqqsMvJHjWN6OVJS3dxqS+Z8ew/1jQwQosfxzpbHuSrnVZ3pHVWk1CH1Fw+IVOUpUzKSoVQk8pSmKpUpIcgZ+T5MRUpryo0iOZFrsKMlmDKVBBBQpZxgzyhPhfkg1Gyaps0xjyQhmctQjORdklBXhawrsDv+ZSoGUh41g10smSsm8lQllKdaV4n+BTVI+QRe6RsxAAAAAElFTkSuQmCC
// @run-at document-end
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_addStyle
// @require https://code.jquery.com/jquery-3.1.0.min.js
// ==/UserScript==

GM_addStyle(`
#radio_box {
position: fixed;
top: 5%;
left: 50%;
margin-left: -300px;
width: 600px;
background: silver;
border: 1px solid silver;
z-index: 9999;
box-shadow: 4px 4px 8px silver;
}
#names {
background: white;
text-align: center;
padding: 3px;
}
.menu_radios > li {
display: inline-block;
background: #737171;
padding: 0 3px;
border: 1px solid;
margin: 0 2px;
color: white;
font-size: 0.7em;
box-shadow: 2px 2px 5px silver;
cursor: pointer;
user-select: none;
}
.menu_radios > li:hover {
background: #4c1a1a;
}
div#outputs {
padding: 5px;
background: moccasin;
text-align: center;
max-height: 500px;
overflow-y: auto;
}
.selactive{
background: #4c1a1a !important;
}
.main_title, .main_title_small {
padding: 5px;
border: 1px solid;
font-size: 1.3em;
background: #949292;
color: white;
text-align: center;
font-family: cursive;
}
.main_title_small{
font-size: 0.9em;
background: #4a3737;
margin: 15px 0;
}
.box_inner > ul li, #outputs > ul li {
display: inline-block;
padding: 10px;
border: 1px solid;
margin: 10px;
background: #949292;
color: white;
cursor: pointer;
zoom: 0.5;
box-shadow: 8px 8px 20px black;
}
@-moz-document url-prefix() {
.box_inner > ul li, #outputs > ul li {
    width: 170px;
    word-wrap: break-word;
  }
li audio {
    transform: translate(-60px,0) scale(0.6);
}
}
#foot {
text-align: center;
padding: 5px;
background: dimgray;
border-top: 1px solid white;
}
div#box_codes {
position: fixed;
top: 5%;
left: 6%;
background: #d2cccc;
border: 1px solid silver;
text-align: center;
padding: 5px;
opacity: 0.8;
display:none;

}
textarea#texta {
width: 520px;
height: 400px;
}
.box_inner {
display: none;
}
.box_radio {
background: #504f4f;
margin-bottom: 2px;
color: white;
cursor:pointer;
}
.title_ganre {
background: #6f293f;
}
.textarea_info {
background: darkgray;
color: white;
padding: 3px;
}
.textarea_info > div{
float: right;
margin-right: 5px;
color: yellow;
cursor: pointer;
}
`);

(function() {
    'use strict';
    var canavas_img = false,
        radios_count = 0,
        radios_image_load = 0,
        debug = true,
        RadioObj = null

    function loadStorage(){
        let RadioObj_tmp = GM_getValue('RadioObj');

        RadioObj = (RadioObj_tmp) ? JSON.parse(GM_getValue('RadioObj')) : {
            options: {},
            allRadio:{}
        };
        if(debug) console.log(RadioObj)

        return (RadioObj.hasOwnProperty("allRadio") && Object.keys(RadioObj.allRadio).length)?true:false
    }

    function saveToStorage(){
        try{
            var save_data = JSON.stringify(RadioObj);

            if(save_data.length>0 && save_data !== null && save_data !=="" && save_data !== undefined){
                GM_setValue('RadioObj', save_data);
                if(debug) console.log("Сохраненно: ",RadioObj);
            }
        }catch(e){
            console.log(e);
        }
    }

    function ajax(urls){

        return $.ajax(
            {
                url: urls,
                async: false,
                xhrFields: { withCredentials: true},
                success: function(data){
                    return data
                },
                error: function(){
                    return "error"
                }
            }).responseText;
    }

    function getListGenres(data){
        let html = data,
            output ={},
            liEl = $(html).find("div.content__main > ul li"),
            liElLenght = liEl.length

        liEl.each(function(index, el){
            if(index != liElLenght-1) {
                let name = $(this).find("a").text().trim(),
                    link = '//101.ru'+$(this).find("a").attr("href")
                output[name]=({name: name, link: link})
            }
        })
        return output
    }

    function getListRadios(data){
        for(let x in data) {
            let elements = ajax(data[x].link),
                html = $(elements);
            if(!data[x].hasOwnProperty("radio")) data[x].radio = [];
            if(!data[x].hasOwnProperty("clear")) data[x].clear = [];

            $(html).find(".content__main > div.grid.grid_size_m > li").each(function(index,el){
                let name = $(this).find("a.grid__title > span").text(),
                    href = $(this).find("a.grid__title").attr("href"),
                    image = $(this).find("link").attr("href"),

                    json = getJson(href.slice(href.lastIndexOf("/")+1,href.length), true);

                let linksR = [];
                if(!json.includes("Канал не найден")){
                    let jsonP = JSON.parse(json);
                    jsonP = jsonP.result
                    for(let s = 0; s < jsonP.playlist.length; s++){
                        let file = jsonP.playlist[s].file,
                            cut = file.slice(0, file.lastIndexOf("?"));
                        linksR.push(cut)
                    }
                }

                if(json.includes("Канал не найден")){
                    data[x].clear.push({name:name,link:href,image:image, json:json})
                } else {
                    data[x].radio.push({name:name,link:href,image:image, json:linksR})
                    radios_count++
                }
            });
        }
        return data
    }

    function getJson(id, bo){
        let urls = bo ? "//101.ru/api/channel/getServers/"+id+"/channel/AAC/64/?dataFormat=html5" : "//101.ru/api/channel/getServers/"+id+"/channel/MP3/128/",
            otvet = ajax(urls)
        if(otvet.includes("Канал не поддерживает формат AAC")){
            otvet = getJson(id, false);
        }
        return otvet
    }

    function panel_v1(obj){
        let div = $("<div id='radio_box'>"+
                    "<div class='main_title'>Radio List 101.ru</div>"+
                    "<div id='outputs'>Пустой список!</div>"+
                    "<div id='foot'><button onclick=\"$(\'#box_codes\').toggle(\'slow\')\">Получить код</button></div>"+
                    "<div id='box_codes'><textarea id='texta' spellcheck='false'></textarea></div>"+
                    "</div>"),

            outuptCode = ``,

            past = $(div).find("#outputs").empty();

        for(let o in obj){ // Main genres
            let genre = obj[o],
                divA = $("<div class='box_radio'></div>"),
                divB = $("<div class='title_ganre'></div>").text(genre.name).click(function(event){
                    event.stopPropagation();
                    $(this).next(".box_inner").toggle('slow');
                }),
                divC = $("<div class='box_inner'></div>"),
                ul = $("<ul>"),
                text = ""

            for(let i=0;i<genre.radio.length;i++){
                outuptCode += `{\n`

                let radio = genre.radio[i]

                text += '<li><a href="'+radio.link+'" class="noajax" data-tooltip-block="#topchan82">'+
                    '<div class="cover logo" style="background-color: #eeeeee"><img src="'+radio.image+'" alt="'+radio.name+'"></div>'+
                    '<div class="h3 caps htitle">'+radio.name+'</div>'+
                    '</a>';

                outuptCode += `"title":"${radio.name}",\n"artist":"${genre.name} - 101.ru",\n"poster":"${radio.image}",`

                if(canavas_img) outuptCode += `\n"canvas":"${radio.canvasik}",\n`

                for(let s = 0 ; s<radio.json.length;s++){
                    text +='<div>'+radio.json[s]+'</div><audio controls preload="none"><source src="'+radio.json[s]+'" type="audio/mpeg"></source></audio>';
                    outuptCode += `"mp3":"${radio.json[s]}"`
                    if(s < radio.json.length-1) outuptCode += `,\n`
                }
                outuptCode += `\n},\n`

                // if(i < genre.radio.length-1) outuptCode +=  `\n},\n`

                text +='</li>';
            }
            $(ul).html(text)
            divC.append(ul)
            divA.append(divB,divC)
            past.append(divA)
        }
        $("body").append(div)
        $("#texta").val(outuptCode)
    }

    function panelv2_menu(obj){
        let div = $("<div id='radio_box'>"+
                    "<div class='main_title'>Radio List 101.ru</div>"+
                    "<div id='names'><ul class='menu_radios'></ul></div>"+
                    "<div id='outputs'>Пусто, выберите элемент!</div>"+
                    "<div id='foot'><button onclick=\"$(\'#box_codes\').toggle(\'slow\')\">Получить код</button></div>"+
                    "<div id='box_codes'>"+
                    "<div class='textarea_info'><span>None info</span><div onclick=\"$(\'#box_codes\').toggle(\'slow\')\">X</div></div>"+
                    "<textarea id='texta' spellcheck='false'></textarea>"+
                    "</div>"+
                    "</div>"),

            ul_menu = $(div).find(".menu_radios"),

            // All radios
            _liAll = $("<li>").text("Все").attr("title","Все").click(function(){
                $(".menu_radios > li.selactive").removeClass()
                $(this).addClass("selactive")
                $("#outputs").empty();

                let outuptCode = "",
                    counts = 0
                for(let o in obj){
                    let genre = obj[o]
                    counts+= genre.radio.length
                    $("#outputs").append($("<div class='main_title_small'>").text(o))
                    outuptCode = loadRadioSelected(genre, outuptCode)
                }
                $("#texta").val(outuptCode)
                $("body").find(".textarea_info > span").text(`Количество радиостанций: ${counts} (Все)`)
            })

        ul_menu.append(_liAll)

        // Make list ganres radios and ouput 1 ganres
        for(let o in obj){
            let genre = obj[o],
                _li = $("<li>").text(o).attr("title",o).click(function(){
                    $(".menu_radios > li.selactive").removeClass()
                    $(this).addClass("selactive")
                    $("#outputs").empty();

                    let outuptCode = ""
                    outuptCode = loadRadioSelected(genre, outuptCode)
                    $("#texta").val(outuptCode)
                    $("body").find(".textarea_info > span").text(`Количество радиостанций: ${genre.radio.length} (${o})`)
                })

            ul_menu.append(_li)
        }
        $("body").append(div)

        // Load default
        $(".menu_radios > li.selactive").removeClass()
        $(ul_menu).find("li:eq(1)").addClass("selactive")
        $("#outputs").empty();

        let outuptCode = ""
        outuptCode = loadRadioSelected(obj["Лучшие станции"], outuptCode)
        $("#texta").val(outuptCode)
        $("body").find(".textarea_info > span").text(`Количество радиостанций: ${obj["Лучшие станции"].radio.length} (Лучшие станции)`)

    }

    function loadRadioSelected(genre, outuptCode){
        let ul = $("<ul>"),
            text = ""

        for(let i=0;i<genre.radio.length;i++){
            outuptCode += '{\n'

            let radio = genre.radio[i]

            text += '<li><a href="'+radio.link+'" class="noajax" data-tooltip-block="#topchan82">'+
                '<div class="cover logo" style="background-color: #eeeeee"><img src="'+radio.image+'" alt="'+radio.name+'" width="440px"></div>'+
                '<div class="h3 caps htitle">'+radio.name+'</div>'+
                '</a>';

            outuptCode += '"title":"'+radio.name+'",\n"artist":"'+genre.name+' - 101.ru",\n"poster":"'+radio.image+'",\n'

            if(canavas_img) outuptCode += '"canvas":"'+radio.canvasik+'",\n'

            for(let s = 0 ; s<radio.json.length;s++){
                text +='<div>'+radio.json[s]+'</div><audio controls preload="none"><source src="'+radio.json[s]+'" type="audio/mpeg"></source></audio>';
                outuptCode += '"mp3":"'+radio.json[s]+'"'
                if(s < radio.json.length-1) outuptCode += ',\n'
            }
            outuptCode += '\n}'

            if(i<genre.radio.length-1)outuptCode +=',\n'

            text +='</li>';
        }

        $(ul).html(text)
        $("#outputs").append(ul)

        return outuptCode
    }

    function returnCanvas(params){
        let canvas = document.createElement("canvas"),
            ctx = null,
            img = new Image(),
            src = params.src,
            text = params.text,
            color = params.color,
            gradient = params.gradient || [],
            stroke = params.stroke || false,
            shadow = params.shadow || []

        if(text.includes(" ")){
        text = text.split(" ")
        }

        img.crossOrigin = "Anonymous";

        if(src.length && canvas.getContext('2d')){
            ctx = canvas.getContext("2d")

            img.onload = function(){
                if(debug) console.log("Load image: ", src)

                let w = canvas.width = this.width,
                    h = canvas.height = this.height;
                ctx.drawImage(img, 0, 0);

                if(color == null && gradient.length){
                    color = ctx.createLinearGradient(0,0,w,0);
                    for(let col=0;col<gradient.length;col++) color.addColorStop(gradient[col].proc,gradient[col].color);
                }

                ctx.font="20px Verdana";
                ctx.textAlign = 'center';

                if(shadow.length) {
                    ctx.shadowColor = shadow[0];
                    ctx.shadowBlur = shadow[1];
                    ctx.shadowOffsetX = ctx.shadowOffsetY= shadow[2];
                }

                if(stroke){
                    ctx.strokeStyle = color
                    if(Object.prototype.toString.call(text) == "[object Array]"){
                        for(let i=0;i<text.length;i++) ctx.strokeText(text[i], w/2, h/text.length+i*20+text.length)
                    } else {
                    ctx.strokeText(text,w/2,h/2);
                    }
                } else {
                    ctx.fillStyle = color
                    if(Object.prototype.toString.call(text) == "[object Array]"){
                        for(let i=0;i<text.length;i++) ctx.fillText(text[i], w/2, h/text.length+i*20+text.length)
                    } else {
                    ctx.fillText(text,w/2,h/2);
                    }
                }

                if(debug) console.log(canvas.toDataURL("image/png"))

                params.radio.canvasik = canvas.toDataURL("image/png")
                radios_image_load++
                return canvas.toDataURL("image/png")
            }

            img.onerror = function(){
                params.radio.canvasik = src
                return src
            }
            img.src= src
        }
    }

    function convertToCanvas(obj){
        for(let o in obj){ // Main genres
            let genre = obj[o]

            for(let i=0;i<genre.radio.length;i++){
                let radio = genre.radio[i],
                    canvasik

                canvasik = returnCanvas({
                    src: "."+radio.image.slice(radio.image.indexOf("/",7),radio.image.length),
                    text: radio.name,
                    color: null,
                    gradient: [{proc: 0, color: "black"},
                               {proc: 0.5, color: "red"},
                               {proc: 1, color: "black"}],
                    stroke: false,
                    shadow: ["black",10,20],
                    radio: radio
                });
            }
        }
        return obj
    }


    function OBJLengthEmpty(obj){
        return Object.keys(obj).length == 0 ? true : false
    }


    function init(){
        let interval = null,listGanres,listRadios,makePanelObject
        listGanres = getListGenres(ajax("//101.ru/radio-top"))

        if(OBJLengthEmpty(listGanres)){
            throw Error("Список жанров пуст!")
            return false
        }
        if(debug) console.log("Жанры полученны!",listGanres)

        listRadios = getListRadios(listGanres)

        if(OBJLengthEmpty(listRadios)){
            throw Error("Список радиостанций пуст!")
            return false
        }

        makePanelObject = (canavas_img)? convertToCanvas(listRadios): listRadios

        if(canavas_img && OBJLengthEmpty(makePanelObject)){
            throw Error("Список радиостанций, с canvas'ом пуст!")
            return false
        }

        if(debug) console.log("Список радиостанций получен!", "Найдено:", radios_count)

        if(canavas_img){
            interval = setInterval(function(){
                if(radios_image_load == radios_count){
                    if(debug) console.log("Loading images from canvas completed! Count:", radios_image_load)
                    clearInterval(interval)
                    //panel(makePanelObject)
                    if(!RadioObj.hasOwnProperty("allRadio")) RadioObj.allRadio = {};
                    RadioObj.allRadio = makePanelObject
                    saveToStorage()

                    panelv2_menu(makePanelObject)
                } else {
                    if(debug) console.log("Loading images from canvas:", radios_image_load,"/",radios_count)
                }
            }, 2000)
        } else {
            // panel(makePanelObject)
            if(!RadioObj.hasOwnProperty("allRadio")) RadioObj.allRadio = {};
            RadioObj.allRadio = makePanelObject
            saveToStorage()

            panelv2_menu(makePanelObject)
        }

    }

    function grabAgain(){
        if(confirm("Начать грабить 101.ru?")){
            canavas_img = confirm("Внимание:\nПеревести все изображения радиостанций в canvas?\n")
            init()
        }
    }

    if(loadStorage()){
        if(confirm("Загрузить сохраненные радио, с прошлого раза?")){
            if(confirm("Внимание:\nДобавить canvas в коды?\n")){
            if(RadioObj.hasOwnProperty("allRadio") &&
               RadioObj.allRadio.hasOwnProperty("Лучшие станции") &&
               RadioObj.allRadio["Лучшие станции"].hasOwnProperty("radio") &&
               RadioObj.allRadio["Лучшие станции"].radio[0].hasOwnProperty("canvasik")){
                canavas_img = true
            } else {
                alert("Внимание:\nПоле с изображением в canvas не найдено!\nЗагрузка не возможна, начните грабить заново с canvas'ом!")
                canavas_img = false
            }
            }

            panelv2_menu(RadioObj.allRadio)
        } else {
            grabAgain()
        }
    } else {
        grabAgain()
    }
})();