Keylol Chinaplay 2Game Table

在购买心得 Chinaplay 和 2Game 板块的帖子一楼开始位置添加折扣表格,折后价和史低可排序。

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Keylol Chinaplay 2Game Table
// @namespace    https://greasyfork.org/users/34380
// @version      20240918
// @description  在购买心得 Chinaplay 和 2Game 板块的帖子一楼开始位置添加折扣表格,折后价和史低可排序。
// @supportURL   https://keylol.com/t920709-1-1
// @match        https://keylol.com/t*
// @match        https://keylol.com/forum.php?mod=viewthread&tid=*
// @connect      gitee.com
// @connect      raw.githubusercontent.com
// @grant        GM_xmlhttpRequest
// ==/UserScript==

(function () {
    'use strict';

    if (document.querySelector('.subforum_left_title_left_up').innerText.match(/Chinaplay/)) {
        document.querySelector('head').insertAdjacentHTML('beforeend', `<style> .td-wide-80 { width:80px; } .td-wide-50 { width:50px; } .hidden1,.hidden2,.hidden3,.hidden4 { display:none; }</style>`);

        const html_table = `<br><div class="sff_collapse"><div class="sff_collapse_b" onclick="var s = this.parentNode; while (!s.classList.contains('sff_collapse')) {s = s.parentNode;} if (s.classList.contains('sff_collapsed')) {s.classList.remove('sff_collapsed');} else {s.classList.add('sff_collapsed');}"><span class="sff_collapse_t">&gt;</span> 折扣表格</div><div class="sff_collapse_d">
            <span id="checkbox-rows"><label><input id="cb-tr-higher" type="checkbox" value="tr-higher"></input>高于史低</label><label><input id="cb-tr-old" type="checkbox" value="tr-old"></input>高于史低CP</label><label><input id="cb-tr-not-wish" type="checkbox" value="tr-not-wish"></input>非愿望单</label><label><input id="cb-tr-own" type="checkbox" value="tr-own"></input>已拥有</label></span>
            <table id="table-chinaplay" class="t_table"><thead><tr><td data-col="index" data-reverse="1">游戏名</td><td class="td-wide-80" data-col="coupon" data-reverse="-1">折后价</td><td class="td-wide-80" data-col="hist-cp" data-reverse="-1">史低CP</td><td class="td-wide-80" data-col="hist" data-reverse="-1">史低</td><td>区域</td><td class="td-wide-50">截止</td><td>折扣码</td><td class="td-wide-50">加购</td></tr></thead><tbody></tbody></table>
            <div><a href="javascript:;" onclick="var s = this.parentNode; while (!s.classList.contains('sff_collapse')) {s = s.parentNode;} s.scrollIntoView(true); if (s.classList.contains('sff_collapsed')) {s.classList.remove('sff_collapsed');} else {s.classList.add('sff_collapsed');}">点击隐藏</a></div></div></div>`;

        const floor1 = document.querySelector('.t_f');
        const anchor = floor1.querySelector('.t_f .pstatus') || floor1.querySelector('.original_text_style1');
        if (anchor) {
            anchor.insertAdjacentHTML('afterend', '' + html_table);
        } else {
            floor1.insertAdjacentHTML('afterbegin', html_table);
        }
        const cb_rows = floor1.querySelector('#checkbox-rows');
        cb_rows.querySelectorAll('input').forEach((cb) => {
            cb.checked = true;
        });

        const tbody = floor1.querySelector('#table-chinaplay > tbody');

        // table col > name discount hist region expire code cart steam
        let name;
        let discount = 0;
        let hist = 0;
        let region;
        let expire;
        let code = '';
        let steam;
        let is_code4down

        let trs = [];
        let i_start = 0;
        let i_end = 0;
        let is_hist_fill = true;
        const vnode = document.createElement('div');
        vnode.innerHTML = floor1.innerHTML.replace(/<\/?span[^>]*>/g, '').replace(/<\/?strong[^>]*>/g, '');
        const nodes = vnode.childNodes;
        nodes.forEach((node) => {
            readNode(node);
        });
        GM_xmlhttpRequest({
            method: "GET",
            url: 'https://gitee.com/mouse040429/keylol-chinaplay-table/raw/main/datacp.json',
            timeout: 9100,
            onload: (res) => {
				try {
                	const data = JSON.parse(res.responseText);
                	newTable(data);
				} catch (e) {
					console.log('可能是 Gitee 屏蔽关键字导致出错,正在连接 Github。');
					GM_xmlhttpRequest({
						method: "GET",
						url: 'https://raw.githubusercontent.com/mouse040429/Keylol-Chinaplay-Table/refs/heads/main/datacp.json',
						timeout: 9100,
						onload: (res) => {
							const data = JSON.parse(res.responseText);
							console.log('成功连接到 Github。');
							newTable(data);
						}
					});
				}
            }
        });

        setTimeout(() => {
            tbody.querySelectorAll('tr > td:nth-child(1) > a').forEach((node) => {
                if (!node.classList.contains('steam-info-wish')) {
                    if (node.classList.contains('steam-info-own')) {
                        node.parentNode.parentNode.classList.add('tr-own', 'tr-not-wish');
                    } else {
                        node.parentNode.parentNode.classList.add('tr-not-wish');
                    }
                }
            });
        }, 5000);

        function newTable(data) {
            let html = '';
            trs.forEach((tds, i) => {
                // tds [name, parseFloat(discount), parseFloat(hist), region, expire, code, cart, steam];
                const is_lower = tds[1] <= tds[2];
                const cur = data[tds[6]];
                let td_hist;
                let is_newlow = false;
                if (cur) {
                    const _low = cur.low;
                    is_newlow = tds[1] <= _low[0];
                    let tit = padEnsp(_low[0]) + _low[1] + '&#13;';
                    cur.hist.forEach((item) => {
                        tit = tit + '&#13;' + padEnsp(item[0]) + item[1];
                    });
                    td_hist = `<td data-hist-cp="${_low[0]}" title="${tit}">${is_newlow ? '<span style="background-color:LightBlue">' + _low[0] + '</span>' : _low[0]}</td>`;
                } else {
                    td_hist = `<td data-hist-cp="0">0</td>`;
                }
                html = html + `<tr class="${is_lower ? '' : 'tr-higher '}${is_newlow ? '' : 'tr-old'}"><td data-index="${i}"><a href="https://store.steampowered.com/${tds[7]}/" target="_blank">${tds[0]}</a></td><td data-coupon="${tds[1]}">${is_lower ? '<span style="background-color:Wheat">' + tds[1] + '</span>' : tds[1]}</td>${td_hist}<td data-hist="${tds[2]}">${tds[2]}</td><td>${tds[3]}</td><td>${tds[4]}</td><td>${tds[5]}</td><td><a href="https://chinaplay.store/detail/${tds[6]}/" target="_blank">加购</a></td></tr>`;
            });
            tbody.innerHTML = html;

            function padEnsp(para) {

                for (let i = para.toString().length; i < 8; i++) {
                    para += '&ensp;';
                }
                return para;
            }
        }

        function readNode(node) {
            if (node.nodeName == 'H1') {
                expire = '';
                region = '-';
                const matched1 = node.innerText.match(/((\d+\.\d+)?)(.*)((.*))/);
                if (matched1) {
                    expire = matched1[1];
                    region = matched1[4];
                    i_end = trs.length;
                    for (; i_start < i_end; i_start++) {
                        trs[i_start][5] = code;
                    }
                    const matched2 = matched1[3].match(/.*《(.*)》/);
                    if (matched2) {
                        name = matched2[1];
                    }
                    if (!is_hist_fill) {
                        trs[i_end - 1][2] = hist;
                    }
                }
            } else if (node.nodeName == 'A') {
                if (node.href.match(/https:\/\/store\.steampowered\.com\/(app|sub|bundle)\/\d+\//)) {
                    name = node.innerText;
                    steam = node.href.match(/https:\/\/store\.steampowered\.com\/((app|sub|bundle)\/\d+)\//)[1];
                } else if (node.href.match(/https:\/\/chinaplay\.store\/detail\/[\w-]+\/?/)) {
                    const cart = node.href.match(/https:\/\/chinaplay\.store\/detail\/([\w-]+)\/?/)[1];
                    if (!name) { name = cart.replace(/-/g, ' ').replace('   ', ' - '); }
                    trs.push([name, parseFloat(discount), parseFloat(hist), region, expire, code, cart, steam]);
                    if (is_code4down) { i_start++; }
                    name = null;
                    hist = 0;
                    is_hist_fill = true;
                }
            } else if (node.nodeName == '#text') {
                const content = node.textContent;
                if (content.match(/史低:?(\d+(\.\d+)?)/)) {
                    hist = content.match(/史低:?(\d+(\.\d+)?)/)[1];
                    is_hist_fill = false;
                } else if (content.match(/(\d+(\.\d+)?)元/)) {
                    discount = content.match(/(\d+(\.\d+)?)元/)[1];
                } else if (content.match(/适用折扣码:/)) {
                    code = content.match(/折扣码:(\S+)/)[1];
                    is_code4down = true;
                } else if (content.match(/对应折扣码:/)) {
                    code = content.match(/折扣码:(\S+)/)[1];
                }
            }
        }

        cb_rows.addEventListener('click', (event) => {
            if (event.target.nodeName == 'INPUT') {
                const hidden = {
                    'tr-higher': 'hidden1',
                    'tr-old': 'hidden2',
                    'tr-not-wish': 'hidden3',
                    'tr-own': 'hidden4'
                }
                const value = event.target.value;
                tbody.querySelectorAll('.' + value).forEach((node) => { node.classList.toggle(hidden[value]); });
            }
        });

        floor1.querySelector('#table-chinaplay > thead > tr').addEventListener('click', function (event) {
            const target = event.target;
            if (target.nodeName == 'TD' && target.hasAttribute('data-reverse')) {
                const col = target.getAttribute('data-col');
                let reverse = target.getAttribute('data-reverse');
                let sorted;
                if (reverse == 0) {
                    sorted = Array.from(tbody.querySelectorAll('tr')).sort((a, b) => {
                        return b.querySelector('td[data-' + col + ']').getAttribute('data-' + col) - a.querySelector('td[data-' + col + ']').getAttribute('data-' + col);
                    });
                    const siblings = this.querySelectorAll('[data-reverse="1"]');
                    target.setAttribute('data-reverse', '1');
                    siblings.forEach((node) => { node.setAttribute('data-reverse', '0'); });
                } else {
                    sorted = Array.from(tbody.querySelectorAll('tr')).sort((a, b) => { return a.querySelector('td[data-' + col + ']').getAttribute('data-' + col) - b.querySelector('td[data-' + col + ']').getAttribute('data-' + col); });
                    const siblings = this.querySelectorAll('[data-reverse="0"]');
                    target.setAttribute('data-reverse', '0');
                    siblings.forEach((node) => { node.setAttribute('data-reverse', '1'); });
                }
                sorted.forEach((node) => {
                    tbody.insertAdjacentElement('beforeend', node);
                });
            }
        });

    } else if (document.querySelector('.subforum_left_title_left_up').innerText.match(/2Game/)) {
        document.querySelector('head').insertAdjacentHTML('beforeend', `<style> .td-wide-80 { width:80px; } .td-wide-50 { width:50px; } .hidden1,.hidden2,.hidden3,.hidden4 { display:none; }</style>`);

        const html_table = `<br><div class="sff_collapse"><div class="sff_collapse_b" onclick="var s = this.parentNode; while (!s.classList.contains('sff_collapse')) {s = s.parentNode;} if (s.classList.contains('sff_collapsed')) {s.classList.remove('sff_collapsed');} else {s.classList.add('sff_collapsed');}"><span class="sff_collapse_t">&gt;</span> 折扣表格</div><div class="sff_collapse_d">
            <span id="checkbox-rows"><label><input id="cb-tr-old" type="checkbox" value="tr-old"></input>高于史低2G</label><label><input id="cb-tr-not-wish" type="checkbox" value="tr-not-wish"></input>非愿望单</label><label><input id="cb-tr-own" type="checkbox" value="tr-own"></input>已拥有</label></span>
            <table id="table-chinaplay" class="t_table" style="width:80%"><thead><tr><td data-col="index" data-reverse="1">游戏名</td><td class="td-wide-80" data-col="coupon" data-reverse="-1">折后价</td><td class="td-wide-80" data-col="hist-cp" data-reverse="-1">史低2G</td><td class="td-wide-50">加购</td></tr></thead><tbody></tbody></table>
            <div><a href="javascript:;" onclick="var s = this.parentNode; while (!s.classList.contains('sff_collapse')) {s = s.parentNode;} s.scrollIntoView(true); if (s.classList.contains('sff_collapsed')) {s.classList.remove('sff_collapsed');} else {s.classList.add('sff_collapsed');}">点击隐藏</a></div></div></div>`;

        const floor1 = document.querySelector('.t_f');
        const anchor = floor1.querySelector('.t_f .pstatus') || floor1.querySelector('.original_text_style1');
        if (anchor) {
            anchor.insertAdjacentHTML('afterend', '' + html_table);
        } else {
            floor1.insertAdjacentHTML('afterbegin', html_table);
        }
        const cb_rows = floor1.querySelector('#checkbox-rows');
        cb_rows.querySelectorAll('input').forEach((cb) => {
            cb.checked = true;
        });

        const tbody = floor1.querySelector('#table-chinaplay > tbody');

        // table col > name discount hist region expire code cart steam
        let name;
        let version;
        let discount = 0;
        let hist = 0;
        let region;
        let expire;
        let code = '';
        let steam;

        let trs = [];
        let i_start = 0;
        let i_end = 0;
        let is_hist_fill = true;
        const vnode = document.createElement('div');
        vnode.innerHTML = floor1.innerHTML.replace(/<\/?span[^>]*>/g, '').replace(/<\/?strong[^>]*>/g, '');
        const nodes = vnode.childNodes;
        nodes.forEach((node) => {
            readNode(node);
        });
        GM_xmlhttpRequest({
            method: "GET",
            url: 'https://gitee.com/mouse040429/keylol-chinaplay-table/raw/main/data2g.json',
            timeout: 9100,
            onload: (res) => {
				try {
                	const data = JSON.parse(res.responseText);
                	newTable(data);
				} catch (e) {
					console.log('可能是 Gitee 屏蔽关键字导致出错,正在连接 Github。');
					GM_xmlhttpRequest({
						method: "GET",
						url: 'https://raw.githubusercontent.com/mouse040429/Keylol-Chinaplay-Table/refs/heads/main/data2g.json',
						timeout: 9100,
						onload: (res) => {
							const data = JSON.parse(res.responseText);
							console.log('成功连接到 Github。');
							newTable(data);
						}
					});
				}
            }
        });

        setTimeout(() => {
            tbody.querySelectorAll('tr > td:nth-child(1) > a').forEach((node) => {
                if (!node.classList.contains('steam-info-wish')) {
                    if (node.classList.contains('steam-info-own')) {
                        node.parentNode.parentNode.classList.add('tr-own', 'tr-not-wish');
                    } else {
                        node.parentNode.parentNode.classList.add('tr-not-wish');
                    }
                }
            });
        }, 5000);

        function newTable(data) {
            let html = '';
            trs.forEach((tds, i) => {
                // tds [name, parseFloat(discount), parseFloat(hist), region, expire, code, cart, steam];
                const cur = data[tds[2]];
                let td_hist;
                let is_newlow = false;
                if (cur) {
                    const _low = cur.low;
                    is_newlow = tds[1] <= _low[0];
                    let tit = _low[0] + '   ' + _low[1] + '&#13;';
                    cur.hist.forEach((item) => {
                        tit = tit + '&#13;' + item[0] + '   ' + item[1];
                    });
                    td_hist = `<td data-hist-2g="${_low[0]}" title="${tit}">${is_newlow ? '<span style="background-color:LightBlue">' + _low[0] + '</span>' : _low[0]}</td>`;
                } else {
                    td_hist = `<td data-hist-2g="0">0</td>`;
                }
                html = html + `<tr class="${is_newlow ? '' : 'tr-old'}"><td data-index="${i}"><a href="https://store.steampowered.com/${tds[3]}/" target="_blank">${tds[0]}</a></td><td data-coupon="${tds[1]}">${tds[1]}</td>${td_hist}<td><a href="https://chinaplay.store/detail/${tds[2]}/" target="_blank">加购</a></td></tr>`;
            });
            tbody.innerHTML = html;
        }

        function readNode(node) {
            if (node.nodeName == 'A') {
					console.log('a');
                if (node.href.match(/https?:\/\/store\.steampowered\.com\/(app|sub|bundle)\/\d+\//)) {
                    name = node.innerText;
                    steam = node.href.match(/https?:\/\/store\.steampowered\.com\/((app|sub|bundle)\/\d+)\//)[1];
                } else if (node.href.match(/https:\/\/2game\.hk\/cn\/[\w-]+\/?/)) {
                    const cart = node.href.match(/https:\/\/2game\.hk\/cn\/([\w-]+)/)[1];
                    trs.push([name + version, parseFloat(discount), cart, steam]);
                    hist = 0;
                    is_hist_fill = true;
                }
            } else if (node.nodeName == '#text') {
                const content = node.textContent;
                if (content.match(/价格:\d+(\.\d+)?/)) {
                    version = content.match(/(.*)叠加折扣码价格:/)[1];
                    discount = content.match(/价格:(\d+(\.\d+)?)/)[1];
                }

            } else if (node.nodeName == 'DIV') {
				node.childNodes.forEach((_node) => {
					readNode(_node);
				});
            }
        }

        cb_rows.addEventListener('click', (event) => {
            if (event.target.nodeName == 'INPUT') {
                const hidden = {
                    'tr-higher': 'hidden1',
                    'tr-old': 'hidden2',
                    'tr-not-wish': 'hidden3',
                    'tr-own': 'hidden4'
                }
                const value = event.target.value;
                tbody.querySelectorAll('.' + value).forEach((node) => { node.classList.toggle(hidden[value]); });
            }
        });

        floor1.querySelector('#table-chinaplay > thead > tr').addEventListener('click', function (event) {
            const target = event.target;
            if (target.nodeName == 'TD' && target.hasAttribute('data-reverse')) {
                const col = target.getAttribute('data-col');
                let reverse = target.getAttribute('data-reverse');
                let sorted;
                if (reverse == 0) {
                    sorted = Array.from(tbody.querySelectorAll('tr')).sort((a, b) => {
                        return b.querySelector('td[data-' + col + ']').getAttribute('data-' + col) - a.querySelector('td[data-' + col + ']').getAttribute('data-' + col);
                    });
                    const siblings = this.querySelectorAll('[data-reverse="1"]');
                    target.setAttribute('data-reverse', '1');
                    siblings.forEach((node) => { node.setAttribute('data-reverse', '0'); });
                } else {
                    sorted = Array.from(tbody.querySelectorAll('tr')).sort((a, b) => { return a.querySelector('td[data-' + col + ']').getAttribute('data-' + col) - b.querySelector('td[data-' + col + ']').getAttribute('data-' + col); });
                    const siblings = this.querySelectorAll('[data-reverse="0"]');
                    target.setAttribute('data-reverse', '0');
                    siblings.forEach((node) => { node.setAttribute('data-reverse', '1'); });
                }
                sorted.forEach((node) => {
                    tbody.insertAdjacentElement('beforeend', node);
                });
            }
        });
    }
})();