Youtube/vtstats, VTuber Viewer Graph

This script display graph of VTuber's viewer count into control bar

// ==UserScript==
// @name         Youtube/vtstats, VTuber Viewer Graph
// @name:ja      Youtube/vtstats, VTuber視聴者数グラフ表示
// @namespace    http://tampermonkey.net/
// @version      0.2.2
// @description  This script display graph of VTuber's viewer count into control bar
// @description:ja  VTuberの放送の視聴者数をグラフで表示する
// @author       You
// @match        https://www.youtube.com/*
// @icon         https://www.google.com/s2/favicons?domain=youtube.com
// @grant        GM.xmlHttpRequest
// @grant        GM.addStyle
// @run-at       document-end
// ==/UserScript==

(function() {
    'use strict';
    var APPNAME = "YHVG", VERSION = "0.2.2";
    var Channels = ["UCUERxnZSjCq-G7OfYL7Nukg","UC4l9gz3q65lTBFfFtW5LLeA","UC69URn8iP4u8D_zUp-IJ1sg","UCS9uQI-jC3DE0L4IpXyvr6w","UC6TfqY40Xt1Y0J-N18c85qQ","UCTvHWSfBZgtxE4sILOaurIQ","UCiA-trSZfB0i92V_-dyDqBw","UChAnqc_AY5_I3Px5dig3X1Q","UCKeAhJvy8zgXWbh9duVjIaQ","UCRqBKoKuX30ruKAq05pCeRQ","UCUP8TmlO7NNra88AMqGU_vQ","UC2hx0xVkMoHGWijwr_lA01w","UCZLZ8Jjx_RN2CXloOmgTHVg","UCQRfUPNla7dB-kdcWD92lJg","UCKu59gTZ_rdEmerdx5rV4Yg","UCeShTCVgZyq2lsBW9QwIJcw","UCwokZsOK_uEre70XayaFnzA","UCkieJGn3pgJikVW8gmMXE2w","UCIM92Ok_spNKLVB5TsgwseQ","UC4mTp4T_DkmnFuhAlqNBnHA","UCLHmLrj4pHHg3-iBJn_CqxA","UCSc_KzY_9WYAx9LghggjVRA","UCgA2jKRkqpY_8eysPUs8sjw","UCsb-1aJgiJXJH2feV-zlZRw","UCdpUojq0KWZCN9bxXnZwz5w","UCle1cz6rcyH0a-xoMYwLlAg","UC6wvdADTJ88OfIbJYIpAaDA","UCMqGG8BRAiI1lJfKOpETM_w","UC3n5uGu18FoCy23ggWWp8tA","UCMGfV7TVTmHhEErVJg1oHBQ","UCQYADFw7xEJ9oZSM5ZbqyBw","UCV5ZZlLjk5MKGg3L0n0vbzw","UCWRPqA0ehhWV4Hnp27PJCkQ","UCo2N7C-Z91waaR6lF3LL_jw","UCb5JxV6vKlYVknoJB8TnyYg","UCTIE7LM5X15NVugV7Krp9Hw","UCebT4Aq-3XWb5je1S1FvR_A","UCevD0wKzJFpfIkvHOiQsfLQ","UC7fk0CB07ly8oSl0aqKkqFg","UCcDDxnoQcezyTUzHg5uHaKg","UCjLEmnpCNeisMxy134KPwWw","UCGhqxhovNfaPBpxfCruy9EA","UCl1oLKcAq93p-pwKfDGhiYQ","UC3VN9h8fokwB2XURWHNcdWw","UC_a1ZYZ8ZTXpjg9xUY9sj8w","UCC7rRD6P7RQcx0hKv9RQP4w","UCvInZx9h3jC2JzsIzoOebWg","UCRfz2yOn8C7Jg5zq4Xi67-Q","UCveZ9Ic1VtcXbsyaBgxPMvg","UCdXAk5MpyLD8594lm_OvtGQ","UCUKD-uaobj9jiqB-VXt71mA","UCAWSyEs_Io8MtpY3m-zqILA","UCmbs8T6MWqUHP1tIQvSgKrg","UCo7TRj3cS-f_1D9ZDmuTsjw","UC727SQYUvx5pDDGQpTICNWg","UCGw7lrT-rVZCWHfdG9Frcgg","UCRWOdwLRsenx2jLaiCAIU4A","UCmovZ2th3Sqpd00F5RdeigQ","UC1iA6_NT4mtAcIII6ygrvCw","UCKMYISTJAQ8xTplUPHiABlA","UCsUj0dszADCGbF3gNrQEuSQ","UCGNI4MENvnsymYjKiZwv9eg","UCoM_XmK45j504hfUWvN06Qg","UCDRWSO281bIHYVi-OV3iFYA","UCXRlIK3Cw_TJIQC5kSJJQMg","UCe_p3YEuYJb8Np0Ip9dk-FQ","UChSvpZYRPh0FvG4SJGSga3g","UCHK5wkevfaGrPr7j3g56Jmw","UC1suqwovbL1kzsoaZgFZLKg","UCP0BspO_AMEe3aQqqpo89Dg","UCP4nMSTdwU1KqYWu3UH5DHQ","UCoztvTULBYd3WmStqYeoHcA","UC0g1AE0DOjBYnLhkgoRWN1w","UC1CfXB_kRs3C-zaeTG3oGyg","UCnRQYHTnRLSF0cLJwMnedCg","UCqm3BQLlJfvkTsX_hvm0UmA","UCL34fAoFim9oHLbVzMKFavQ","UCgRqGV1gBf2Esxh0Tz1vxzw","UCtAvQ5U0aXyKwm2i4GqFgJg","UCXTpFs_3PqI41qX2d9tL2Rw","UCl_gCybOJRIgOXw6Qb4qJzQ","UC4OeUf_KfYRrwksschtRYow","UC_sFNM0z0MWm9A6WlKPuMMg","UCANDOlYTJT7N5jlRC3zfzVA","UCRvkXFtB70ZADg4L6A8L3wQ","UC1QgXt46-GEvtNjEC1paHnw","UC8C1LLhBhf_E2IBPLSDJXlQ","UCkngxfPbmGyGl_RIq4FA3MQ","UCgIfLpQvelloDi8I0Ycbwpg","UCsg-YqdqQ-KFF0LNk23BY4A","UCp-5t9SrOQwXMU7iIjQfARg","UCZlDXzGoo7d44bwdNObFacg","UCO_aKKYxn4tvrqPjcTzZ6EQ","UCfipDDn7wY-C-SoUChgxCQQ","UC4WvIIAo89_AzGUh1AZ6Dkg","UCufQu4q65z63IgE4cfKs1BQ","UCG0rzBZV_QMP4MtWg6IjhEA","UCvmppcdYf4HOv-tFQhHHJMA","UC1opHUrw8rvnsadT-iGp7Cg","UCtpB6Bvhs1Um93ziEDACQ8g","UCambvP8yxNDot4FzQc9cgiw","UCUc8GZfFxtmk7ZwSO7ccQ0g","UCBwH091x3pYLbGYwRtPbLlA","UCzKkwB84Y0ql0EvyOWRSkEw","UCivwPlOp0ojnMPZj5pNOPPA","UCerkculBD7YLc_vOGrF7tKg","UCvXsXmpMKthJuX8XHbsnOjQ","UCsk_mKQyv_xdhahUBqoVhcg","UCWQtYtq9EOB4-I5P-3fh8lA","UC6oDys1BGgBsIC3WhG1BovQ","UC_vMYWcDjmfdpH6r4TTn1MQ","UCAoy6rzhSf4ydcYjJw3WoVg","UC10wVt6hoQiwySRhz7RdOUA","UCL_qhgtOy0dy1Agp8vkySQg","UCFKOVgVbGmX65RxO3EtH3iw","UCAQDFeCTVdx90GtwohwjHzQ","UC4yNIKGvy-YUrwYupVdLDXA","UCgnfPPb9JI3e9A4cXHnWbyg","UCmhtmUBjkXOAetnaDq-XJ1g","UCu-J8uIXuLZh16gG-cT1naw","UCBURM8S4LH7cRZ0Clea9RDA","UC5dJFf4m-mEcoyJRfhBljoA","UC9p_lqQ0FEDz327Vgf5JwqA","UCy8P3o5XlMpJGQY4WugzdNA","UCjlmCrq4TP1I4xguOtJ-31w","UCVLsPBwDtv7n7FZLiOsvhSQ","UCwaS8_S7kMiKA3izlTWHbQg","UCdfMHxjcCc2HSd9qFvfJgjg","UCuep1JCrMvSxOGgGhBfJuYw","UCtNeQ8cUwvAAhBbVbwfGWpg","UCD-miitqNY3nyukJ4Fnf4_A","UC9V3Y3_uzU5e-usObb6IE1w","UCCVwhI5trmaSxfcze_Ovzfw","UC_82HBGtvwN1hcGeOGHzUBQ","UCvzVB-EYuHFXHZrObB8a_Og","UCHP4f7G2dWD4qib7BMatGAw","UCNW1Ex0r6HsWRD4LCtPwvoQ","UCPvGypSgfDkVe7JG2KygK7A","UC3lNFeJiTq6L3UWoz4g1e-A","UCJFZiqLMntJufDCHc6bQixg","UCYz_5n-uDuChHtLo7My1HnQ","UCah4_WVjmr8XA7i5aigwV-Q","UCu2DMOGLeR_DSStCyeQpi5Q","UC1uv2Oq6kNxgATlCiez59hw","UCtyWhCj3AqKh2dXctLkDtng","UC1yoRdFoFJaCY-AGfD9W0wQ","UCYKP16oMX9KKPbrNgo_Kgag","UCbfv8uuUXt3RSJGEwxny5Rw","UCgmFrRcyH7d1zR9sIVQhFow","UC9EjSJ8pvxtvPdxLOElv73w","UC6eWCld0KwmyHFbAqK3V-Rw","UCK9V2B22uJYu3N7eR_BT9QA","UCuuAb_72QzK0M1USPMEl1yw","UCpzxZW5kghGnO5TmAFJQAVw","UCqXxS-9x9Ha_UiH6hG4kh5Q","UC2L0MeiLsfJf4rH_xTVteDw","UC6t3-_N8A6ME1JShZHHqOMw","UCkT1u65YS49ca_LsFwcTakw","UC7Gb7Uawe20QyFibhLl1lzA","UCt0clH12Xk1-Ej5PXKGfdPA","UCllKI7VjyANuS1RXatizfLQ","UCIG9rDtgR45VCZmYnd-4DUw","UC53UDnhAAYwvNO7j_2Ju1cQ","UCt5-0i4AVHXaWJrL8Wql3mw","UC-6rZgmxZSIbq786j3RD5ow","UCQ0UDLQCjY0rmuxCDE38FGg","UC7MMNHR-kf9EN1rXiesMTMw","UChJ5FTsHOu72_5OVx0rvsvQ","UCFTLzh12_nrtzqBPsTCqenA","UC1zFJrfEKvCixhsjNSb1toQ","UCgZ0pH7j6c9z-pkOG3PYw1Q","UCz89MGFBrAqwJ5xMr5weSuA","UCXW4MqCQn-jCaxlX-nn-BYg","UC5CwaMl1eIgY8h02uZw7u8A","UCC0i9nECi4Gz7TU63xZwodg","UCt9H_RpQzhxzlyBxFqrdHqA","UCotXwY6s8pWmuWd_snKYjhg","UC2OacIzd2UxGHRGhdHl1Rhw","UCENwRMx5Yh42zWpzURebzTw","UC7XCjKxBEct0uAukpQXNFPw","UC0TXe_LYZ4scaW2XMyi5_kw","UCtHY-tP0dyykhTRMmnfPs_g","UCc88OV45ICgHbn3ZqLLb52w","UCDqI2jOz0weumE8s7paEk6g","UCS-XXTgVkotkbkDnGEprXpg","UCZgOv3YDEs-ZnZWDYVwJdmA","UC6WFKwYptsxVue56Lx218vg","UCHVXbQzkl3rDfsXWo8xi2qw","UCwL7dgTxKo8Y4RFIKWaf8gA","UCb6ObE-XGCctO3WrjRZC-cw","UCckdfYDGrjojJM28n5SHYrA","UChdY64fJb14Nfnbs8EGdQig","UCp6993wxpyDPHUpavwDFqgg","UCRm6lqtdxs_Qo6HeL-SRQ-w","UCNVEsYbiZjH5QLmGeSgTSzg","UCyl1z3jo3XHR1riLFKG5UAg","UCspv01oxUFf_MTSipURRhkA","UC_GCs6GARLxEHxy1w40d6VQ","UCHsx4Hqa-1ORjQTh9TYDhww","UCV1xUwfM2v2oBtT3JNvic3w","UCIBY1ollUsauvVi4hW4cumw","UC060r4zABV18vcahAWR1n7w","UC7gxU6NXjKF1LrgOddPzgTw","UC5gs8EbpwbfQvrhizJx60TA","UCkIimWZ9gBJRamKF0rmPU8w","UCgmPnx-EEeOrZSg5Tiw7ZRQ","UCCzUftO8KOVkV4wQG1vkUvg","UCJubINhCcFXlsBwnHp0wl_g","UCdyqAaZDKHXg4Ahi7VENThQ","UCWz0CSYCxf4MhRKPDm220AQ","UCIytNcoz4pWzXfLda0DoULQ","UC22BVlBsZc6ta3Dqz75NU6Q","UC_4tXjqecqox5Uc05ncxpxg","UCN68LoM3khS4gdBMiWJO8WA","UC9ruVYPv7yJmV0Rh0NKA-Lw","UCdn5BQ06XqgXoAxIhbqw5Rg","UCOyYb1c43VlX9rc_lT6NKQw","UCfrWoRGlawPQDQxxeIDRP0Q","UChUJbHiTVeGrSkTdBzVfNCQ","UCa9Y57gfeY0Zro_noHRVrnw","UCL_O_HXgLJx3Auteer0n0pA","UClrQ7xhRBxS_v_-WuudGKmA","UCwrjITPwG4q71HzihV2C7Nw","UCryOPk2GZ1meIDt53tL30Tw","UChgTyjG-pdNvxxhdsXfHQ5Q","UCqjTqdVlvIipZXIKeCkHKUA","UCVhRMzNWYcg8XDTgB9NFRTw","UC47rNmkDcNgbOcM-2BwzJTQ","UCmeyo5pRj_6PXG-CsGUuWWg","UCwcyyxn6h9ex4sMXGtpQE_g","UCCXME7oZmXB2VFHJbz5496A","UCoSrY_IQQVpmIRZ9Xf-y93g","UCJCzy0Fyrm0UhIrGQ7tHpjg","UCuvk5PilcvDECU7dDZhQiEw","UCpnvhOIJ6BN-vPkYU9ls-Eg","UCfQVs_KuXeNAlGa3fb8rlnQ","UC3ql_EU4JnaE3Rh3cqbHu6g","UChgPVLjqugDQpRLWvC7zzig","UCtnO2N4kPTXmyvedjGWdx3Q","UCsFn_ueskBkMCEyzCEqAOvg","UCr9p1ZjLKgfaoqNorY7PiWQ","UC8NZiqKx6fsDT3AVcMiVFyA","UCFgXWZOUZA2oYHNr6qDmsTQ","UCe22Bcwd_GCpTjLxn83zl7A","UCRV9d6YCYIMUszK-83TwxVA","UC0WwEfE-jOM2rzjpdfhTzZA","UCvaTdHTWBGv3MKj3KVqJVCw","UCSFCh5NL4qXrAy9u-u2lX3g","UCp3tgHXw_HI0QMk1K8qh3gQ","UC9mf_ZVpouoILRY9NUIaK-w","UCpNH2Zk2gw3JBjWAKSyZcQQ","UC1vawzfbCnRpHT9SJ5pHlHw","UCvzGlP9oQwU--Y0r9id_jnA","UCHX7YpFG8rVwhsHCx34xt7w","UC48jH1ul-6HOrcSSfoR02fQ","UCtLfA_qUqCJtjXJM2ZR_keg","UCbc8fwhdUNlqi-J99ISYu4A","UCrb7RCZ6scsSrCGoGehe-fw","UCg63a3lk6PNeWhVvMRM_mrQ","UC-hM6YJuNYVAmUWxeIr9FeA","UCmZ1Rbthn-6Jm_qOGjYsh5A","UCD8HOxPs4Xvsm8H0ZxXGiBw","UCs9_O1tRPMQTHQ-N_L6FU2g","UCQ1U65-CQdIoZ2_NA4Z4F7A","UC8rcEBzJSleTkf_-agPM20g","UCiSRx1a2k-0tOg-fs6gAolQ","UCeK9HFcRZoTrvqcUCtccMoQ","UCR6qhsLpn62WVxCBK1dkLow","UCHBhnG2G-qN0JrrWmMO2FTA","UCRcLAVTbmx2-iNcXSsupdNA","UCIeSUTOTkF9Hs7q3SGcO-Ow","UCBLGjbYv6-xxju1i44RjnnA","UCQ1zGxHrfEmmW4CPpBx9-qw","UCLO9QDxVL4bnvRRsz6K4bsQ","UCu-rV2gPtJ-CsGxe71z_BrQ","UC1DCedRgGHBdm81E1llLhOQ","UCyxtGMdWlURZ30WSnEjDOQw","UCZ1xuCK1kNmn5RzPYIZop3w","UC-o-E6I3IC2q8sAoAuM6Umg","UCt30jJgChL8qeT9VPadidSw","UCMwGHR0BTZuLsmjY_NT5Pwg","UCmUjjW5zF1MMOhYUwwwQv9Q","UCGYAYLDE7TZiiC8U6teciDQ"];
    // CSS
    GM.addStyle(`
    .vvg-container {
        position: absolute;
        left: 0;
        right: 0;
        top: 0;
        bottom: 0;
        display: flex;
        align-items: flex-end;
        opacity: 0.3;
        z-index: -1;
    }
    .vvg-container .segment {
        background: red;
        width: 100%;
        vertical-align: bottom;
        height: calc(var(--viewers) / var(--max-viewers) * 100);
        animation: vvg-segment-animation 1s 1;
    }
    @keyframes vvg-segment-animation {
        0% {
            transform: translate(0, 100%);
        }
        10% {
            transform: translate(0, 100%);
        }
        100% {
            transform: translate(0%);
        }
    }
    .vvg-count-popup {
        position: absolute;
        top: -3rem;
        left: 0;
        background: var(--yt-spec-themed-overlay-background);
        color: var(--yt-spec-text-primary);
        padding: 4px;
        line-height: 1.5;
        font-size: 15px;
    }
    .vvg-powered {
        margin-left: 24px;
    }
        `);

    // ytplayer.config.args.raw_player_response.heartbeatParams.intervalMilliseconds
    // ytplayer.config.args.raw_player_response.playabilityStatus.liveStreamability.liveStreamabilityRenderer.pollDelayMs
    var recordInterval = 15000;
    var videoId, maxViewers = 0, viewerSpans = [];

    var popup, graph, powered;

    // 動画を監視して、更新されたら読み込み開始
    var recordTimer = setInterval(recordLive, recordInterval);
    document.addEventListener("yt-navigate-finish", updatedVideo);
    document.addEventListener("yt-play", updatedVideo);
    setTimeout(updatedVideo, 2000);

    // ----------------------------------------------------------
    // チャンネル確認
    // ID 確認
    // --履歴取得
    // --履歴記録

    async function updatedVideo() {
        // console.log("updatedVideo()");

        // 許可されたチャンネルかどうか
        if (checkAllowed()) {
            var id = getVideoId();
            if (id && videoId != id) {
                enable(id);
                await statsHistory(id);
            }
        } else {
            disable();
        }
    }

    function getVideoId() {
        if (location.pathname != "/watch") return null;
        for (const match of location.search.matchAll(/v=([0-9a-zA-Z\-\+\=_]+)&?/gm)) {
            return match[1];
        }
        return null;
    }

    function checkAllowed() {
        if (typeof ytplayer == "undefined" || typeof ytplayer.config == "undefined") return false;

        var id = ytplayer.config.args.raw_player_response.videoDetails.channelId;
        if (Channels.includes(id)) return true;

        var url = ytplayer.config.args.raw_player_response.microformat.playerMicroformatRenderer.ownerProfileUrl;
        var m = url.match(/\/([^\/]+)$/);
        if (m && Channels.includes(m[1])) return true;

        return false;
    }

    function statsAsync(videoId) {
        console.log(`loading viewers analytics from VTStats : ${videoId}`);
        var agent = { "user-agent": `${APPNAME}/${VERSION}` };

        return new Promise((resolve, reject) => {
            GM.xmlHttpRequest({
                method: "GET",
                url: `https://vt.poi.cat/api/v4/streams?platform=YOUTUBE&platformId=${videoId}`,
                headers: agent,
                onload: (xhr) => {
                    var stream = JSON.parse(xhr.responseText);
                    if (!stream) {
                        reject("This stream is not archived in vtstats");
                        return;
                    }
                    var streamId = stream.streamId;
                    GM.xmlHttpRequest({
                        method: "GET",
                        url: `https://vt.poi.cat/api/v4/stream-stats/viewer?streamId=${streamId}`,
                        headers: agent,
                        onload: (xhr) => {
                            try {
                                resolve(JSON.parse(xhr.responseText));
                            } catch (ex) {
                                console.log("parse error");
                                reject();
                            }
                        },
                        onerror: reject,
                        onabort: reject,
                        ontimeout: reject,
                    });
                },
                onerror: reject,
                onabort: reject,
                ontimeout: reject,
            });
        });
    }

    function isLive() {
        if (!(window.ytplayer && window.ytplayer.config && window.ytplayer.config.args)) return false;

        return window.ytplayer.config.args.raw_player_response.videoDetails.isLive;
    }

    function isLiveContent() {
        if (!(window.ytplayer && window.ytplayer.config && window.ytplayer.config.args)) return false;

        return window.ytplayer.config.args.raw_player_response.videoDetails.isLiveContent;
    }

    // 現在の視聴者数を記録する
    // 一定時間ごとに呼び出す
    function recordLive() {
        // console.log("recordLive()");
        if (!isLive()) return;

        var viewer = document.querySelector("#info-container span").innerText.replace(",", "").match(/\d+/)[0];
        drawSpan(parseInt(viewer));
    }

    function enable(id) {
        console.log(`initilize viewers graph : ${id}`);

        maxViewers = 0;
        videoId = id;
        viewerSpans = [];

        var controls = document.querySelector(".ytp-chrome-controls");
        if (!controls) return;

        popup = controls.querySelector(".vvg-count-popup");
        if (popup == null) {
            controls.insertAdjacentHTML("beforeend", `<div class="vvg-count-popup"></div>`);
            popup = controls.querySelector(".vvg-count-popup");
        }

        graph = controls.querySelector(".vvg-container");
        if (graph == null) {
            controls.insertAdjacentHTML("beforeend", `<div class="vvg-container" style="--max-viewers: 1;"></div>`);
            graph = controls.querySelector(".vvg-container");

            controls.addEventListener("mousemove", (ev) => {
                var rect = graph.getBoundingClientRect();
                var row = viewerSpans[Math.floor(ev.clientX / rect.width * viewerSpans.length)];
                if (row) {
                    popup.innerText = row;
                    popup.style.opacity = 1;
                    popup.style.left = (ev.clientX - popup.clientWidth / 2) + "px";
                }
            }, false);
            controls.addEventListener("mouseleave", ev => {
                popup.style.opacity = 0;
            });
        } else {
            graph.innerHTML = "";
        }

        powered = controls.querySelector(".vvg-powered");
        if (powered == null) {
            var controlsLeft = controls.querySelector(".ytp-left-controls");
            controlsLeft.insertAdjacentHTML("beforeend", `<a href="https://vt.poi.cat/" class="vvg-powered"></a>`);
            powered = controlsLeft.querySelector(".vvg-powered");
        } else {
            powered.innerHTML = "";
        }
    }

    function disable() {
        if (graph) {
            graph.innerHTML = "";
        }
        if (powered) {
            powered.innerHTML = "";
        }
    }

    async function statsHistory(id) {
        try {
            var reports = await statsAsync(id);
            for (var i = 0; i < reports.length; i++) {
                var row = reports[i];
                drawSpan(row[1]);
            }
            powered.innerHTML = reports.length == 0 ? "This stream is not archived in vtstats" : "Viewer Count from vtstats";
        } catch (error) {
            if (typeof error == "string") {
                powered.innerHTML = error;
            } else {
                powered.innerHTML = "VVG have any error";
            }
        }
    }

    function drawSpan(count) {
        if (maxViewers < count) {
            maxViewers = count;
            graph.style.setProperty("--max-viewers", `${count}`);
        }
        viewerSpans.push(count);
        graph.insertAdjacentHTML("beforeend", `<div class="segment" style="--viewers: ${count}%;" viewers="${count}" target="_blank"></div>`);
    }

    function sleepAsync(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }

/*
var clist = [];
for (var i in catalog.channels.length; i++) {
    var c = catalog.channels[i];
    if (c.pratform != "YOUTUBE") continue;
    clist.push(c.platformId);
}
JSON.stringify(clist);
*/
})();