m3u转换器

配合使用:Chrome扩展(https://www.hlsplayer.org/) 或者egdge扩展 M3u8 Player(https://microsoftedge.microsoft.com/addons/detail/hls-m3u8-player-live-/bmmmdhlnijgodpfbhpgjfkpjiigbpcbk?hl=zh-CN)

// ==UserScript==
// @name        m3u转换器
// @namespace   Violentmonkey Scripts
// @match       *://*/*.txt*
// @match       *://*/*.m3u*
// @match       http://127.0.0.1*/*playerlist*
// @grant       none
// @version     1.1.1
// @author      percygyq
// @description 配合使用:Chrome扩展(https://www.hlsplayer.org/) 或者egdge扩展 M3u8 Player(https://microsoftedge.microsoft.com/addons/detail/hls-m3u8-player-live-/bmmmdhlnijgodpfbhpgjfkpjiigbpcbk?hl=zh-CN)


let Store = {
    get: function (key, default_vaule) {
        let value = localStorage.getItem(key);
        if (value) {
            try {
                return JSON.parse(value);
            } catch (ignore) {
                if (default_vaule !== undefined) {
                    return default_vaule;
                }
                return null;
            }
        }
        if (default_vaule !== undefined) {
            return default_vaule;
        }
        return null;
    },
    contains: function (key) {
        return !is_empty(localStorage.getItem(key));
    },
    set: function (key, value) {
        localStorage.setItem(key, JSON.stringify(value));
    },
    remove: function (key) {
        localStorage.removeItem(key);
    },
};
//region 数组功能扩展
//数组迭代函数
Array.prototype.each = function (fn) {
    fn = fn || Function.K;
    let a = [];
    let args = Array.prototype.slice.call(arguments, 1);
    for (let i = 0; i < this.length; i++) {
        let res = fn.apply(this, [this[i], i].concat(args));
        if (res != null) a.push(res);
    }
    return a;
};
//数组是否包含指定元素
Array.prototype.contains = function (suArr) {
    for (let i = 0; i < this.length; i++) {
        if (this[i] === suArr) {
            return true;
        }
    }
    return false;
};
//不重复元素构成的数组
Array.prototype.uniquelize = function () {
    // let ra = [];
    // for (let i = 0; i < this.length; i++) {
    //     if (!ra.contains(this[i])) {
    //         ra.push(this[i]);
    //     }
    // }
    // return ra;
    return this;
};
//删除某一元素
Array.prototype.remove = function (val) {
    let index = this.indexOf(val);
    if (index > -1) {
        this.splice(index, 1);
        return true;
    }
    return false;
};
Array.prototype.isEmpty = function () {
    return this.length === 0;
};
//两个数组的交集
Array.intersect = function (a, b) {
    return a.uniquelize().each(function (o) {
        return b.contains(o) ? o : null
    });
};
//两个数组的差集
Array.minus = function (a, b) {
    return a.uniquelize().each(function (o) {
        return b.contains(o) ? null : o
    });
};
//两个数组的补集
Array.complement = function (a, b) {
    return Array.minus(Array.union(a, b), Array.intersect(a, b));
};
//两个数组并集
Array.union = function (a, b) {
    return a.concat(b).uniquelize();
};

window.requestIdleCallback = window.requestIdleCallback ||
    function (cb) {
        let start = Date.now();
        return setTimeout(function () {
            cb({
                didTimeout: false,
                timeRemaining: function () {
                    return Math.max(0, 50 - (Date.now() - start));
                }
            });
        }, 1);
    };

window.cancelIdleCallback = window.cancelIdleCallback ||
    function (id) {
        clearTimeout(id);
    };

//endregion

// ==/UserScript==
(function () {
    'use strict';
    // @match       *://*/*

    //region 定义变量
    let summary_page_url = window.location.origin + '/res/playerlist'
    let pageSize = 50;//每页显示的记录条数
    let curPage = 0;//当前页
    let direct = 0;//方向
    let len;//总行数
    let page;//总页数
    let title_dic;
    let match_indexes;
    let show_indexs;
    let has_oper_td = false;

    const repeat_str = window.location.host.indexOf("a.bc") || window.location.host.indexOf("193.112.18.11") ? 'res/playerlist' : null;
    let url = window.location.href;
    url = url.split('?')[0];
    url = url.replace('#', '');
    let fav = 'fav';
    let his = 'his';
    let splitr = '~';
    let fav_prefix = fav + splitr;
    let his_prefix = his + splitr;
    let B;  // 文件夹名(C所在的文件夹)
    let C; // 文件名(m3u文件或者txt文件的文件名)
    let imgElements; // {}
    /**
     * 是否收藏状态
     */
    let fav_status = false;
    let fav_indexes;
    let his_status = false;
    let his_indexes;
    let summary;
    let categories;
    /**
     * select选择框 筛选出的 index
     */
    let select_indexes = null;
    let progress = 0;
    let start;
    let period_start;
    //endregion

    //region 逻辑代码
    if (url.startsWith('file:')) {
        //    local file
        console.log('local file------')
    }

    if (getQueryString("mode") === 'summary') {
        // 汇集模式 (显示所有的收藏和历史记录)
        summary = true;
        let datas = load_localStorage();
        printProgress('summary load_localStorage');
        install_table(datas);
        printProgress('summary install_table localStorage');
        return;
    }

    let is_match = url.endsWith(".txt") || url.endsWith(".m3u");
    if (!is_match) {
        window.addEventListener('click', throttle(listen_click, 500, 1000));
        return;
    }

    printProgress('start init html');
    let body_pre = document.querySelector('body > pre');
    if (is_empty(body_pre)) {
        return;
    }
    let text = body_pre.innerText;
    const datas = extract_datas(text);

    printProgress('extract_data_from_html');
    // write_doc(datas);
    install_table(datas);
    printProgress('install_table from html');
    //endregion

    //-----------------------  以下是函数 --------------------
    //region 以下是函数
    function init_some_name(url) {
        let split = url.split('/');
        split = split.each(x => x.length > 0 ? x : null);
        let len = split.length;
        C = decodeURI(split[len - 1]);
        if (len <= 3) {
            B = 'default';
        } else {
            B = decodeURI(split.slice(2, len - 1).join('/'));
        }
        if (!is_empty(repeat_str)) {
            B = B.replace(repeat_str + '/', '');
            B = B.replace(repeat_str, '');
        }
        if (is_empty(B)) {
            B = 'default';
        }
        console.log('B:' + B + ',C:' + C);
    }

    function listen_click(e) {
        let mhref = e && e.target && e.target.href;
        if (is_empty(mhref)) {
            return;
        }
        console.log('mhref', mhref);
        let b = mhref.split('?')[0].endsWith("m3u");
        let c = mhref.split('?')[0].endsWith("txt");
        if (mhref && (b || c)) {
            if (c) {
                // let new_window = window.open(mhref, '_blank');
                // new_window.focus();
                return;
            }
            printProgress('start click url');
            let text = readContent(mhref);
            printProgress('read data from url');
            const datas = extract_datas(text);
            if (datas.length === 0) {
                return;
            }
            url = mhref;
            e.preventDefault();
            e.stopPropagation();
            console.log("e", mhref);
            printProgress('extract data from url');
            install_table(datas);
            printProgress('install_table from url');
            return;
        }
    }

    function lazyload() { //监听页面滚动事件
        let seeHeight = document.documentElement.clientHeight; //可见区域高度
        let scrollTop = document.documentElement.scrollTop || document.body.scrollTop; //滚动条距离顶部高度
        for (let i of show_indexs) {
            let img = imgElements[i];
            if (img && img.offsetTop < seeHeight + scrollTop) {
                if (is_empty(img.getAttribute("src"))) {
                    // console.log("lazyload:", i);
                    img.src = img.getAttribute("data-src");
                }
            }
        }
    }


    function listen_key_down(event) {
        // console.log(event.code);
        switch (event.keyCode) {
            case 37:
                frontPage();
                break;
            case 39:
                nextPage();
                break;
            case 38:
                if (event.altKey) {
                    window.scrollTo(0, 0);
                }
                break;
            case 40:
                if (event.altKey) {
                    window.scrollTo(0, document.body.scrollHeight);
                }
                break
        }
    }


    function favorite_update(d, i) {
        let fav_page_key = fav_prefix + C;
        let fav_item_key = fav_page_key + splitr + d.title;
        let innerText = this.innerText;
        // let page_favs2 = Store.get(page_key) || [];
        let title_favs2 = Store.get(fav_page_key, []);
        if (innerText.includes('💗')) {
            // 取消收藏
            fav_indexes.remove(i);
            this.innerText = this.innerText.replace('💗', '🤍');
            Store.remove(fav_item_key);
            if (title_favs2.remove(d.title)) {
                if (title_favs2.isEmpty()) {
                    Store.remove(fav_page_key);
                    let dir_key = fav_prefix + B;
                    let dir_favs = Store.get(dir_key, []);
                    if (dir_favs.remove(C)) {
                        if (dir_favs.isEmpty()) {
                            Store.remove(dir_key);
                            let top_list = Store.get(fav, []);
                            if (top_list.remove(B)) {
                                Store.set(fav, top_list);
                            }
                        } else {
                            Store.set(dir_key, dir_favs);
                        }
                    }
                } else {
                    Store.set(fav_page_key, title_favs2);
                }
            }
        } else {
            // 收藏
            if (!fav_indexes.includes(i)) {
                fav_indexes.push(i);
            }
            this.innerText = this.innerText.replace('🤍', '💗');
            d.time = now();
            Store.set(fav_item_key, d);
            if (!title_favs2.includes(d.title)) {
                title_favs2.push(d.title);
                Store.set(fav_page_key, title_favs2);
                let dir_key = fav_prefix + B;
                let dir_favs = Store.get(dir_key, []);
                if (!dir_favs.includes(C)) {
                    dir_favs.push(C);
                    Store.set(dir_key, dir_favs);
                    let top_list = Store.get(fav, []);
                    if (!top_list.includes(B)) {
                        top_list.push(B);
                        Store.set(fav, top_list);
                    }
                }
            }
        }
    }

    function history_update(d) {
        let his_page_key = his_prefix + C;
        let title_hiss = Store.get(his_page_key, []);
        d.time = now();
        Store.set(his_page_key + splitr + d.title, d);
        if (!title_hiss.includes(d.title)) {
            title_hiss.push(d.title);
            Store.set(his_page_key, title_hiss);
            let dir_key = his_prefix + B;
            let dir_hiss = Store.get(dir_key, []);
            if (!dir_hiss.includes(C)) {
                dir_hiss.push(C);
                Store.set(dir_key, dir_hiss);
                let top_list = Store.get(his, []);
                if (!top_list.includes(B)) {
                    top_list.push(B);
                    Store.set(his, top_list);
                }
            }
        }
    }

    function fill_table(tbody, start, end, datas) {
        let fragment = document.createDocumentFragment();
        for (let i = start; i < end; i++) {
            let d = datas[i];
            let tr = document.createElement('tr');
            td_attach_event(tr, i % 2 === 0);
            fragment.appendChild(tr);
            let title = d.title;
            let logo = d.logo;
            let group = d.group;
            let url = d.url;
            if (d.index === undefined) {
                d.index = i;
            }
            title_dic[i] = title;
            let td_size = has_oper_td ? 3 : 2;
            for (let j = 0; j < td_size; j++) {
                let td = document.createElement('td');
                tr.appendChild(td);
                switch (j) {
                    case 0:
                        if (fav_indexes.includes(i)) {
                            td.innerText = i + 1 + '💗';
                        } else {
                            td.innerText = i + 1 + '🤍';
                        }
                        td.onclick = function () {
                            setBC(fav, i);
                            favorite_update.call(this, d, i);
                        };
                        break;
                    case 1:
                        let e_link = document.createElement("a");
                        e_link.setAttribute("href", url);
                        e_link.setAttribute("target", '_blank');
                        if (is_empty(group)) {
                            e_link.setAttribute('data-group', group);
                        }
                        e_link.onclick = function () {
                            // 播放记录
                            console.log(`click url ${title} -> ${url}`);
                            if (!his_indexes.includes(i)) {
                                his_indexes.push(i);
                            }
                            setBC(his, i);
                            history_update(d);
                        };
                        if (is_empty(logo)) {
                            e_link.innerText = title;
                            e_link.setAttribute('title', getTip(i, d));
                            td.appendChild(e_link);
                        } else {
                            pageSize = 10;
                            let e_img = document.createElement('img');
                            e_img.setAttribute('alt', '😎');
                            e_img.setAttribute('data-src', logo);
                            e_img.setAttribute('title', getTip(i, d));
                            e_img.onerror = function () {
                                this.src = "https://tva4.sinaimg.cn/small/87c01ec7gy1fsnqqz0c17j21kw0w07lt.jpg";
                                this.onerror = null;
                            };
                            e_link.appendChild(e_img);
                            let e_p2 = document.createElement('p');
                            e_p2.setAttribute('class', 'title');
                            e_p2.innerText = title;
                            td.appendChild(e_link);
                            td.appendChild(e_p2);
                            td.onmouseover = function (ev) {
                                if (is_empty(e_img.getAttribute("src"))) {
                                    e_img.src = e_img.getAttribute("data-src");
                                }
                            };
                            imgElements[i] = e_img;
                        }
                        break;
                    case 2:
                        td.innerHTML = "<a href='javascript:void()' style='color:#01AAED'>编辑</a>";
                        break;
                }
            }
        }
        tbody.appendChild(fragment);
    }

    function install_table(datas) {
        if (datas.length === 0) {
            alert("没有读取到任何数据!")
            return;
        }

        if (!summary) {
            init_some_name(url);
            init_history();
            init_favorite();
            document.title = C;
            printProgress('init history and favorite')
        } else {
            document.title = '汇集';
        }

        imgElements = {};
        title_dic = {};
        document.body.innerHTML = '<!DOCTYPE html><html lang="zh-CN"><body></body></html>';
        let html_str = '<a id="btn0"></a><input id="pageSize" type="text" size="1" maxlength="3" /><a> 条 </a>' +
            '<a href="#" id="pageSizeSet">设置</a> ' +
            '&nbsp;&nbsp;<input type="file" id="file_upload" style="background-color: #E1FFFF">' +
            '<p id="sjzl"></p> ' +
            '<a href="#" id="btn1">首页</a> ' +
            '<a href="#" id="btn2">上一页</a> ' +
            '<a href="#" id="btn3">下一页</a> ' +
            '<a href="#" id="btn4">尾页</a> ' +
            '<a>转到 </a> ' +
            '<input id="changePage" type="text" size="1" maxlength="4"/> ' +
            '<a>页 </a> ' +
            '<a href="#" id="btn5">跳转</a>' +
            '<br/>' +
            '<input type="text" name="name" id="keyword" style="width:150px;height:30px; ali;text-align:center;"/> ' +
            '<input  id="search_button" type="button" value="search" style="width:50px;height:30px; ali;text-align:center;">' +
            '&nbsp;&nbsp;' +
            '<input  id="fav_button" type="button" value="收藏" style="width:50px;height:30px; ali;text-align:center;">' +
            '&nbsp;&nbsp;' +
            '<input  id="his_button" type="button" value="记录" style="width:50px;height:30px; ali;text-align:center;">' +
            '&nbsp;&nbsp;' +
            '<input  id="summary_button" type="button" value="汇集" style="width:50px;height:30px; ali;text-align:center;">'
        ;
        document.body.innerHTML += html_str;
        if (summary) {
            add_select();
            printProgress('summary add_select')
        }

        let my_div = document.createElement('div');
        my_div.setAttribute('id', 'center');
        document.body.appendChild(my_div);

        printProgress('summary add_select');

        createTab();

        printProgress('create top_tab');
        let tableStyle = document.createElement('style');
        tableStyle.innerHTML = 'td{' +
            'border-left: #d2d2d2 1px solid;' +
            'border-top: #d2d2d2 1px solid;' +
            'width:50px;' +
            'height:50px;' +
            'text-align:center;' +
            'border-collapse:collapse;' +
            'white-space: nowrap;' +
            'background:snow' +
            '}';
        document.head.appendChild(tableStyle);
        let tab = document.createElement("table");
        tab.setAttribute("id", "mytable");
        let tbody = document.createElement('tbody');
        tab.appendChild(tbody);

        let first_load = 1000;
        if (first_load >= datas.length) {
            first_load = datas.length;
        } else {
            requestIdleCallback(function () {
                let tab = document.querySelector("#mytable > tbody");
                printProgress("lazy load start ");
                fill_table(tab, first_load, datas.length, datas);
                datas = [];
                display();
                printProgress("lazy load end ");
            }, {timeout: 50});
        }
        fill_table(tbody, 0, first_load, datas);
        my_div.appendChild(tab);
        printProgress('create data tab');
        attach_event();
        display();
        printProgress('display');
    }

    function add_select() {
        let style = document.createElement('style');
        style.innerHTML = '.select_style {\n' +
            '    width: 200px;\n' +
            '    height: 50px;\n' +
            '    overflow: hidden;\n' +
            '    background: no-repeat 215px;\n' +
            '    border: 1px solid #ccc;\n' +
            '    -moz-border-radius: 5px; /* Gecko browsers */\n' +
            '    -webkit-border-radius: 5px; /* Webkit browsers */\n' +
            '    border-radius: 5px;\n' +
            '    margin: 10px;\n' +
            '    display: inline;\n' +
            '}\n' +
            '.select_style select {\n' +
            '    padding: 5px;\n' +
            '    background: transparent;\n' +
            '    width: 150px;\n' +
            '    font-size: 16px;\n' +
            '    border: none;\n' +
            '    height: 30px;\n' +
            '    -webkit-appearance: none;\n' +
            '}';
        document.head.appendChild(style);

        let dic = {fav: '收藏', his: '播放记录'};

        document.body.innerHTML += '<br/><br/><br/>'
        let div = document.createElement('div');
        div.setAttribute('id', 'search_options');


        div.appendChild(create_select_div('stype', '类别', Object.keys(categories), 'dir', onStypeChange));
        div.appendChild(create_select_div('dir', '文件夹', [], 'm3u', onDirChange));
        div.appendChild(create_select_div('m3u', '列表', [], '', onM3uChange));
        document.body.appendChild(div);


        function create_select_div(id, tip, values, next_id, func) {
            let div = document.createElement('div');
            div.setAttribute('class', 'select_style');
            let select = document.createElement('select');
            select.setAttribute('id', id);
            select.options.add(new Option(`--${tip}--`, ''))
            for (let value of values) {
                let option = new Option(dic[value] || value, value);
                select.options.add(option);
            }
            div.appendChild(select);
            select.onchange = function () {
                let nextOptionVaules = func(this.value);
                // console.log(this.value);
                if (!is_empty(next_id)) {
                    let e = document.getElementById(next_id);
                    e.options.length = 1;
                    let keys = nextOptionVaules || [];
                    for (let key of keys) {
                        let option = new Option(key, key);
                        e.options.add(option);
                    }
                }
            }
            return div;
        }

    }

    function createTab() {
        let top_tab = document.createElement("table");
        let my_div = document.getElementById('center');
        const tab_array = ["序号", "名称", "选择", "导出"];

        let len = tab_array.length;
        let title = document.createElement("h1");
        title.innerHTML = "节目表";

        let br = document.createElement("br");

        for (let i = 0; i < 1; i++) {
            let tr = top_tab.insertRow(i);
            for (let j = 0; j < len; j++) {
                let td = top_tab.rows[i].insertCell(j);
                td.style.cssText = "BORDER:#d2d2d2 1px solid ;width:50px;height:50px; ali;text-align:center;";
                td.innerHTML = tab_array[j];
                // td.style.background = '#f2f2f2';
                switch (j) {
                    case 2:
                        td.onclick = do_select;
                        break;
                    case 3:
                        td.onclick = do_export;
                        break;
                }
            }
        }
        my_div.appendChild(title);
        my_div.appendChild(br);
        my_div.appendChild(top_tab);
    }

    function getTip(i, d) {
        let time = d.time === undefined ? '' : ' ' + d.time;
        if (summary) {
            let tip = '';
            let stype;
            if (fav_indexes.includes(i)) {
                stype = fav;
            } else if (his_indexes.includes(i)) {
                stype = his;
            }
            if (stype) {
                let m = categories[stype].movies_dic[i];
                if (!is_empty(m)) {
                    tip += m + ' ';
                }
                tip += time;
            }
            return tip;
        } else {
            return d.title + time;
        }
    }

    function attach_event() {
        document.getElementById("btn1").onclick = function firstPage() {    // 首页
            curPage = 1;
            direct = 0;
            displayPage();
        };
        document.getElementById("btn4").onclick = function lastPage() {    // 尾页
            curPage = page;
            direct = 0;
            displayPage();
        };
        document.getElementById("btn5").onclick = function changePage() {    // 转页
            curPage = document.getElementById("changePage").value;
            if (!/^[1-9]\d*$/.test(curPage)) {
                alert("请输入正整数");
                return;
            }
            if (curPage > page) {
                alert("超出数据页面");
                return;
            }
            direct = 0;
            displayPage();
        };
        document.getElementById("btn2").onclick = frontPage;
        document.getElementById("btn3").onclick = nextPage;
        document.getElementById("pageSizeSet").onclick = setPageSize;
        document.getElementById("pageSize").onblur = setPageSize;

        document.getElementById("search_button").onclick = do_search_title;
        document.getElementById("keyword").onblur = do_search_title;
        document.getElementById("fav_button").onclick = show_favorite;
        document.getElementById("his_button").onclick = show_history;
        document.getElementById("file_upload").onchange = reload_by_file;
        document.getElementById("summary_button").onclick = function () {
            let new_window = window.open(summary_page_url + '?mode=summary', '_blank');
            new_window.focus();
        };
        // 图片懒加载
        if (imgElements.length > 0) {
            lazyload();
            window.addEventListener('scroll', throttle(lazyload, 500, 1000));
        }
        window.onkeydown = listen_key_down;
        printProgress('attach event');
    }

    function display() {
        search_title();
        printProgress('search_title');
        curPage = 1;    // 设置当前为第一页
        displayPage();//显示第一页
        printProgress('displayPage');
    }

    function frontPage() {    // 上一页
        direct = -1;
        displayPage();
    }

    function nextPage() {    // 下一页
        direct = 1;
        displayPage();
    }

    function setPageSize() {    // 设置每页显示多少条记录
        pageSize = document.getElementById("pageSize").value;    //每页显示的记录条数
        if (!/^[1-9]\d*$/.test(pageSize)) {
            alert("请输入正整数");
            return;
        }
        pageSize = parseInt(pageSize);
        curPage = 1;
        direct = 0;
        displayPage();
    }

    function do_search_title() {
        curPage = 1;
        direct = 0;
        search_title();
        displayPage();
    }

    function show_favorite() {
        curPage = 1;
        direct = 0;
        fav_status = !fav_status;
        document.getElementById("fav_button").style.backgroundColor = fav_status ? '#FFB6C1' : '#F8F8FF';
        displayPage();
    }

    function show_history() {
        curPage = 1;
        direct = 0;
        his_status = !his_status;
        document.getElementById("his_button").style.backgroundColor = his_status ? '#95c1fc' : '#F8F8FF';
        displayPage();
    }

    function reload_by_file() {
        if (this.files.length === 0) {
            console.log('请选择文件!');
            return;
        }
        let file = this.files[0];
        const reader = new FileReader();
        reader.onload = function fileReadCompleted() {
            // 当读取完成时,内容只在`reader.result`中
            // console.log(reader.result);
            let datas = extract_datas(reader.result);
            if (datas.isEmpty()) {
                console.error(`文件 [${name}] 没有读取到数据!`)
            } else {
                url = `${window.location.origin}/local/${file.name}`;
                summary = false;
                printProgress('read from localFile');
                install_table(datas);
                printProgress('install_table from localFile');
            }
        };
        reader.readAsText(file);
    }

    function search_title() {
        let keyword = document.getElementById('keyword').value;
        match_indexes = [];
        let b1 = is_empty(keyword);
        for (let i in title_dic) {
            let t = title_dic[i];
            if (is_empty(t)) {
                continue
            }
            i = parseInt(i);
            if (b1) {
                match_indexes.push(i);
            } else if (t.toLowerCase().includes(keyword.toLowerCase())) {
                match_indexes.push(i);
            }
        }
    }

    function displayPage() {
        if (curPage <= 1 && direct === -1) {
            direct = 0;
            alert("已经是第一页了");
            return;
        } else if (curPage >= page && direct === 1) {
            direct = 0;
            alert("已经是最后一页了");
            return;
        }
        let eligible_indexes = match_indexes;
        if (fav_status) {
            eligible_indexes = Array.intersect(eligible_indexes, fav_indexes);
        }
        if (his_status) {
            eligible_indexes = Array.intersect(eligible_indexes, his_indexes);
        }
        if (summary && select_indexes !== null) {
            eligible_indexes = Array.intersect(eligible_indexes, select_indexes);
        }
        len = eligible_indexes.length;
        page = len % pageSize === 0 ? len / pageSize : Math.floor(len / pageSize) + 1;

        // 修复当len=1时,curPage计算得0的bug
        if (len > pageSize) {
            curPage = ((curPage + direct + len) % len);
        } else {
            curPage = 1;
        }

        printProgress('calc page');

        document.getElementById("btn0").innerHTML = '当前 ' + curPage + '/' + page + ' 页    每页 ';
        document.getElementById("sjzl").innerHTML = '数据总量: ' + len + '';
        document.getElementById("pageSize").value = pageSize;

        show_indexs = eligible_indexes.slice((curPage - 1) * pageSize, curPage * pageSize);

        let all = document.querySelectorAll('#mytable tr');
        all.forEach(function (e, i) {
            if (show_indexs.includes(i)) {
                e.style.setProperty('display', 'block');
            } else {
                e.style.setProperty('display', 'none');
            }
        });
        printProgress('show page');

        if (direct >= 0 && pageSize <= 30) {
            // 预加载下一页的图片
            let load_indexes = eligible_indexes.slice((curPage - 1) * pageSize, (curPage + 1) * pageSize);
            for (let i of load_indexes) {
                let img = imgElements[i];
                if (img && is_empty(img.getAttribute("src"))) {
                    img.src = img.getAttribute("data-src");
                }
            }
        }
        printProgress('preload next page')
    }

    function do_select() {
        if (this.x !== 1) {
            // 点击
            this.x = 1;
        } else {
            // 再次点击
            this.x = 0;
        }
        let f = this.x;
        const t = document.querySelectorAll("#mytable tr");
        for (let i = 0; i < t.length; i++) {
            if (!show_indexs.includes(i)) {
                continue;
            }
            t[i]["x"] = f;
            if (f) {
                t[i].style.backgroundColor = "#bce774";
            } else {
                // 取消选中
                t[i].style.backgroundColor = (t[i].sectionRowIndex % 2 === 0) ? "#f8fbfc" : "#e5f1f4";
            }
        }
    }

    function do_export() {
        const t = document.querySelectorAll("#mytable tr");
        let datas = [];
        for (let i = 0; i < t.length; i++) {
            let f = t[i]["x"];
            if (f) {
                t[i].style.backgroundColor = "#e3cf4a";
                let e = t[i].querySelector('td:nth-child(2)');
                let a = e.querySelector('td>a');

                let title = e.innerText;
                let url = a.href;
                let group = a.dataset.group || '';

                let imageElement = a.querySelector('img');
                let logo = '';
                if (imageElement) {
                    logo = imageElement.dataset.src || '';
                }
                let d = {
                    'url': url,
                    'title': title,
                    'tvg-logo': logo,
                    'group-title': group
                };
                datas.push(d);
                console.log('export:', t[i].x, i, title);
            }
        }
        let s = datas_to_m3u(datas);
        if (is_empty(s)) {
            alert("没有选中数据!");
            return;
        }
        let file_name;
        if (summary) {
            file_name = 'summary-export.m3u';
        } else {
            file_name = C.split(".")[0] + '-export.m3u';
        }
        exportRaw(file_name, s);
    }

    /**
     * 从本地存储初始化播放记录
     */
    function init_history() {
        his_indexes = [];
        let page_key = his_prefix + C;
        let title_hiss = Store.get(page_key, []);
        for (let title of title_hiss) {
            let item_key = page_key + splitr + title;
            let item = Store.get(item_key);
            if (item) {
                let index = item.index;
                if (index !== undefined) {
                    his_indexes.push(index);
                }
            }
        }

    }

    function init_favorite() {
        fav_indexes = [];
        let page_key = fav_prefix + C;
        let title_favs = Store.get(page_key, []);
        for (let title of title_favs) {
            let item_key = page_key + splitr + title;
            let item = Store.get(item_key);
            if (item) {
                let index = item.index;
                if (index !== undefined) {
                    fav_indexes.push(index);
                }
            }
        }
    }

    function load_favorite() {
        let page_key = fav_prefix + C;
        let page_favs1 = Store.get(page_key, []);
        for (let title of page_favs1) {
            let item_key = page_key + splitr + title;
            let item = Store.get(item_key);
            if (item) {
                let index = item.index;
                if (index) {
                    fav_indexes.push(index);
                }
            }
        }
    }

    function load_localStorage() {
        let datas = [];
        fav_indexes = [];
        his_indexes = [];
        let c = -1;
        let duplicate_check = [];
        categories = {};
        categories.fav = load_data(fav);
        categories.his = load_data(his);

        function load_data(stype) {
            console.log('----type----', stype);
            let dirs = Store.get(stype, []);
            let prefix = stype + splitr;
            let dir_dic = {};
            let m3u_dic = {};
            let movies_dic = {};
            for (let dir of dirs) {
                let m3us = Store.get(prefix + dir, []);
                if (!m3us.isEmpty()) {
                    dir_dic[dir] = m3us;
                }
                for (let m3u of m3us) {
                    let movies = Store.get(prefix + m3u, []);
                    let index_arr = [];
                    let k = dir + splitr + m3u;
                    for (let m of movies) {
                        let item = Store.get(prefix + m3u + splitr + m);
                        if (item) {
                            let j = k + splitr + m;
                            if (duplicate_check.includes(j)) {
                                continue;
                            }
                            duplicate_check.push(j);
                            c++;
                            movies_dic[c] = k;
                            datas.push(item);
                            index_arr.push(c);
                            // console.log('m:', item);
                            switch (stype) {
                                case fav:
                                    fav_indexes.push(c);
                                    break;
                                case his:
                                    his_indexes.push(c);
                                    break;
                            }
                        }
                    }
                    if (!index_arr.isEmpty()) {
                        m3u_dic[k] = index_arr;
                    }
                }
            }
            return {
                'dir_dic': dir_dic,
                'm3u_dic': m3u_dic,
                'movies_dic': movies_dic,
            };
        }

        return datas;
    }

    function setBC(stype, i) {
        if (summary) {
            let m = categories[stype].movies_dic[i];
            if (is_empty(m)) {
                return;
            }
            let split = m.split(splitr);
            B = split[0];
            C = split[1];
        }
    }

    function onStypeChange(x) {
        document.getElementById('m3u').options.length = 1;
        select_indexes = null;
        switch (x) {
            case fav:
                his_status = false;
                fav_status = false;
                document.getElementById("his_button").style.backgroundColor = '#F8F8FF';
                show_favorite();
                break;
            case his:
                his_status = false;
                fav_status = false;
                document.getElementById("fav_button").style.backgroundColor = '#F8F8FF';
                show_history();
                break;
            default:
                his_status = false;
                fav_status = false;
                document.getElementById("fav_button").style.backgroundColor = '#F8F8FF';
                document.getElementById("his_button").style.backgroundColor = '#F8F8FF';
                curPage = 1;
                direct = 0;
                displayPage();
                return [];
        }
        return Object.keys(categories[x].dir_dic);
    }

    function onDirChange(x) {
        let pre = document.getElementById('stype');
        let stype = pre.options[pre.selectedIndex].value;
        if (is_empty(stype)) {
            return null;
        }
        if (is_empty(x)) {
            onStypeChange(stype);
            return null;
        }
        select_indexes = [];
        let m3us = categories[stype].dir_dic[x] || [];
        for (let m3u of m3us) {
            let k = x + splitr + m3u;
            let arr = categories[stype].m3u_dic[k] || [];
            for (let i of arr) {
                select_indexes.push(i);
            }
        }
        curPage = 1;
        direct = 0;
        displayPage();
        return categories[stype].dir_dic[x];
    }

    function onM3uChange(x) {
        let pre = document.getElementById('stype');
        let stype = pre.options[pre.selectedIndex].value;
        if (is_empty(stype)) {
            return;
        }
        pre = document.getElementById('dir');
        let dir = pre.options[pre.selectedIndex].value;
        if (is_empty(dir)) {
            return;
        }
        if (is_empty(x)) {
            onDirChange(dir);
            return;
        }
        select_indexes = [];
        let k = dir + splitr + x;
        let arr = categories[stype].m3u_dic[k] || [];
        for (let i of arr) {
            select_indexes.push(i);
        }
        curPage = 1;
        direct = 0;
        displayPage();
        return;
    }

    function printProgress(name) {
        let now = Date.now();
        if (isNaN(start)) {
            start = now;
            period_start = now;
        }
        let cost = now - start;
        let period_cost = now - period_start;
        period_start = now;
        console.log(`${++progress}\t\t${cost}ms\t${name} -> ${period_cost} ms`)
    }

    //endregion
})();

//-----------------------  以下是global函数 --------------------
//region 以下是global函数
function td_attach_event(tr, odd) {
    if (odd) {
        tr.style.backgroundColor = '#f8fbfc';
    } else {
        tr.style.backgroundColor = '#e5f1f4';
    }
    tr.onclick = function () {
        if (this.x !== 1) {
            // 选中
            this.x = 1;
            this.style.backgroundColor = "#bce774";
        } else {
            // 取消选中
            this.x = 0;
            this.style.backgroundColor = (this.sectionRowIndex % 2 === 0) ? "#f8fbfc" : "#e5f1f4";
        }
    };
    tr.onmouseover = function () {
        if (this.x !== 1) this.style.backgroundColor = "#ecfbd4";
    };
    tr.onmouseout = function () {
        if (this.x !== 1) this.style.backgroundColor = (this.sectionRowIndex % 2 === 0) ? "#f8fbfc" : "#e5f1f4";
    };
}

/**
 * 节流函数
 */
function throttle(fun, delay, time) {
    let timeout, startTime = new Date();
    return function () {
        let context = this, args = arguments, curTime = new Date();
        clearTimeout(timeout);
        // 如果达到了规定的触发时间间隔,触发 handler
        if (curTime - startTime >= time) {
            fun.apply(context, args);
            startTime = curTime;
            // 没达到触发间隔,重新设定定时器
        } else {
            timeout = setTimeout(fun, delay);
        }
    };
}

function replace_all(str, old_value, new_value) {//吧f替换成e
    let reg = new RegExp(old_value, "g"); //创建正则RegExp对象
    return str.replace(reg, new_value);
}

function is_empty(str) {
    return str == null || str === 'undefined' || !str || !/[^\s]/.test(str);
}

function readContent(url) {
    let xhr = new XMLHttpRequest(), okStatus = document.location.protocol === "file:" ? 0 : 200;
    xhr.open('GET', url, false);
    xhr.overrideMimeType("text/html;charset=utf-8");//默认为utf-8
    xhr.send(null);
    return xhr.status === okStatus ? xhr.responseText : null;
}

function extract_datas(text) {
    const dic_list = [];

    let split = text.split("\n");
    if (split.length === 1) {
        split = text.split("\r\n");
    }
    if (!split[0].includes('#EXTM3U')) {
        return dic_list;
    }
    let dic = {};
    let f = false;
    for (let s of split) {
        if (is_empty(s)) {
            continue
        }
        if (s.includes('#EXTM3U')) {
            continue
        }
        if (s.includes('#EXTINF')) {
            f = true;
            s = s.replace('#EXTINF:-1', '');
            let title = /((\s[\w,-]+)="([^"]+)")*\s*,\s*(.+)/.exec(s)[4].trim();
            let tmp_dic = {};
            let r = /(\s[\w,-]+)="([^"]+)"/g;
            while (r.test(s)) {
                tmp_dic[RegExp.$1.trim()] = RegExp.$2.trim();
            }
            dic = {};
            dic.title = title;
            dic.logo = tmp_dic['tvg-logo'] || '';
            dic.group = tmp_dic['group-title'] || '';
            dic.tvg_name = tmp_dic['tvg-name'] || '';
            continue
        }
        if (f) {
            if (is_empty(s)) {
                continue
            }
            s = s.trim();
            if (s.startsWith("#") || s.includes("#")) {
                continue;
            }
            dic.url = s;
            dic_list.push(dic)
        }
        // console.log(s)
    }
    console.log("-----------------------");
    return dic_list;
}

function datas_to_m3u(datas) {
    if (datas.length === 0) {
        return "";
    }
    let s = "#EXTM3U\n";
    for (let d of datas) {
        s += "#EXTINF:-1 ";
        if (!is_empty(d['tvg-logo'])) {
            s += `tvg-logo="${d['tvg-logo']}" `;
        }
        if (is_empty(d['group-title'])) {
            s += `group-title="${d['group-title']}" `;
        }
        s += `, ${d.title}\n${d.url}\n`;
    }
    return s;
}

function fakeClick(obj) {
    let ev = document.createEvent("MouseEvents");
    ev.initMouseEvent("click", true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
    obj.dispatchEvent(ev);
}

function exportRaw(name, data) {
    let urlObject = window.URL || window.webkitURL || window;
    let export_blob = new Blob([data]);
    let save_link = document.createElementNS("http://www.w3.org/1999/xhtml", "a");
    save_link.href = urlObject.createObjectURL(export_blob);
    save_link.download = name;
    fakeClick(save_link);
}

function getQueryString(name) {
    let reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
    let r = decodeURI(window.location.search.substr(1)).match(reg);
    if (r != null) return unescape(r[2]);
    return null;
}

function now() {
    return dateFormat("YYYY-mm-dd HH:MM:SS", new Date());
}

function dateFormat(fmt, date) {
    let ret;
    const opt = {
        "Y+": date.getFullYear().toString(),        // 年
        "m+": (date.getMonth() + 1).toString(),     // 月
        "d+": date.getDate().toString(),            // 日
        "H+": date.getHours().toString(),           // 时
        "M+": date.getMinutes().toString(),         // 分
        "S+": date.getSeconds().toString()          // 秒
    };
    for (let k in opt) {
        ret = new RegExp("(" + k + ")").exec(fmt);
        if (ret) {
            fmt = fmt.replace(ret[1], (ret[1].length == 1) ? (opt[k]) : (opt[k].padStart(ret[1].length, "0")))
        }
    }
    return fmt;
}


//endregion

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址