Greasy Fork is available in English.

京东评论合并工具

try to take over the world!

// ==UserScript==
// @name         京东评论合并工具
// @namespace    https://github.com/ClericPy/somethings
// @version      2.2
// @description  try to take over the world!
// @author       Clericpy
// @match        https://item.jd.com/*
// @grant        GM_setClipboard
// ==/UserScript==



(function () {
    'use strict';
    var pages = {}


    window.auto_pager_running = false
    var css_to_hidden = {}
    window.backup_mc_innerHTML = ''
    var custom_css_to_hidden = ''
    var button_style = `font: inherit; margin: 3px; overflow: visible; text-transform: none; -webkit-appearance: button; letter-spacing: 0.01em; zoom: 1; line-height: normal; white-space: nowrap; vertical-align: middle; text-align: center; cursor: pointer; -webkit-user-drag: none; user-select: none; box-sizing: border-box; font-size: 100%; padding: .5em 1em; color: rgba(0,0,0,.8); border: none transparent; background-color: #e6e6e6; text-decoration: none; border-radius: 2px; font-family: inherit;`

    var new_style = document.createElement("style");
    new_style.setAttribute('type', 'text/css')
    new_style.setAttribute('id', 'new_style')
    document.getElementsByTagName('body')[0].appendChild(new_style)

    function alert_doc() {
        var doc = `
主要用途:
1. 京东的评论每次点翻页太麻烦了, 需要找关键词的时候又不能搜索, 只好全复制出来了
2. 偶尔需要语料

使用方法:
1. 加载网页以后, 在商品介绍 tab 位置会出现 [收集评论] 按钮, 点击
2. 可以手动点 [手动翻页], 一页页收集, 也可以配置好间隔秒数(默认2秒)点右边 checkbox 自动翻页
3. 采集结束(或自行停止)后, 点击 [展示全部] 按钮, 则评论会在原来位置对多页合并
4. 自行去噪过滤(可以通过选项, 也可以自己指定 css), 然后点击 [复制 TEXT] 即可复制到剪贴板
5. emoji [文本] 点击可以打开 Ubuntu 的 pastebin 来粘贴刚才复制了的文本    
        `
        alert(doc)
    }

    function check_missing_pages() {
        var exist_page_nums = Object.keys(pages).sort()
        var max_num = exist_page_nums[exist_page_nums.length - 1]
        var missing_page_nums = []
        var i = 1
        while (i < max_num) {
            if (!(i in exist_page_nums)) {
                missing_page_nums.push(i)
            }
            i++;
        }
        if (missing_page_nums.length > 0) {
            var span = document.getElementById('commenter_state')
            span.innerText = '页码缺失: ' + missing_page_nums
        } else {
            var span = document.getElementById('commenter_state')
            span.innerText = ''
        }

    }

    function add_class_for_filter() {
        document.querySelectorAll('#comment [style="display: block;"][data-tab="item"] a.comment-plus-icon').forEach(item => {
            item.parentElement.parentElement.parentElement.classList.add('commenter_plus_vip_item')
        });
        document.querySelectorAll('.comment-op').forEach(item => {
            if (item && !/\s*举报\s*\d+\s*0\s*/.test(item.innerText)) {
                item.parentElement.parentElement.parentElement.classList.add('commenter_with_reply')
            }
            if (item && !/\s*举报\s*0\s*\d+\s*/.test(item.innerText)) {
                item.parentElement.parentElement.parentElement.classList.add('commenter_with_like')
            }
        });

    }


    function collect_dom_to_pages() {
        var curr_page = document.querySelector('#comment [style="display: block;"][data-tab="item"] [class="ui-page-curr"]')
        let commenter_crawl_button = document.getElementById('commenter_crawl_button')
        if (!curr_page) {
            return
        }
        if (!commenter_crawl_button) {
            return
        }
        var page = curr_page.innerText
        console.log(Object.keys(pages).length + ' pages: ' + Object.keys(pages))

        commenter_crawl_button.innerText = '手动翻页 ' + page
        var node = document.querySelectorAll('#comment [style="display: block;"][data-tab="item"]>div.comment-item')
        if (node.length > 0) {
            let num = 0
            let page_num = pages.length
            for (const items of Object.values(pages)) {
                num += items.length
            }
            document.getElementById('commenter_status_bar').innerText = ' 已采集 ' + Object.keys(pages).length + ' 页 ' + num
            pages[page] = node
        }
        if (Object.keys(pages).length == 1) {
            let show_button = document.getElementById('commenter_show_button')
            show_button.disabled = false
            show_button.style.color = 'black'
        }
        check_missing_pages()
        // 反爬导致崩溃, 放上备用页面
        if (document.querySelector('#comment [style="display: block;"][data-tab="item"]')) {
            window.backup_mc_innerHTML = document.querySelector('#comment .mc').innerHTML
        } else {
            document.getElementById('commenter_status_bar').innerText = '没有评论了, 点击[展示全部]进行复制'
        }
        add_class_for_filter()
    }

    function filt_by_text() {
        let commenter_text_filter = document.getElementById('commenter_text_filter')
        let text_filters = []
        if (commenter_text_filter) {
            text_filters = commenter_text_filter.value.split(' ')
            if (text_filters == ['']) {
                text_filters = []
            }
        }
        for (const item of document.querySelectorAll('#comment [style="display: block;"][data-tab="item"]>.comment-item')) {
            var item_html = item.innerHTML
            if (!text_filters) {
                item.classList.remove('commenter_filt_by_text')
            }
            for (const text of text_filters) {
                if (text[0] == '-') {
                    if (item_html.includes(text.slice(1, text.length))) {
                        item.classList.add('commenter_filt_by_text')
                        break
                    }
                } else if (!item_html.includes(text)) {
                    item.classList.add('commenter_filt_by_text')
                    break
                } else {
                    item.classList.remove('commenter_filt_by_text')
                }
            }
        }
    }

    function show_pages() {
        var container = document.querySelector('#comment [style="display: block;"][data-tab="item"]')
        if (!container) {
            // 备用 container
            var mc_node = document.querySelector('#comment .mc')
            if (window.backup_mc_innerHTML) {
                mc_node.innerHTML = window.backup_mc_innerHTML
            }
        }
        var backup_np_node = document.getElementsByClassName('com-table-footer')[0]
        container.innerHTML = ''
        Object.keys(pages).sort().forEach(function (key) {
            let items = pages[key]
            items.forEach(item => {
                container.appendChild(item)
            });
        });
        document.getElementById('commenter_copy_button').style.display = 'inline-block'
        document.getElementById('commenter_copy_text_button').style.display = 'inline-block'
        if (backup_np_node) {
            container.appendChild(backup_np_node)
        }
        add_class_for_filter()
        filt_by_text()
    }

    function copy_html() {
        let items = document.querySelectorAll('#comment [style="display: block;"][data-tab="item"]>.comment-item')
        let text = ''
        let filt_plus = document.getElementById('commenter_non_plus_vip').checked
        let filt_reply = document.getElementById('commenter_non_reply').checked
        let filt_like = document.getElementById('commenter_non_like').checked
        items.forEach(item => {
            if (filt_plus && !item.classList.contains('commenter_plus_vip_item')) {
                return
            }
            if (filt_reply && !item.classList.contains('commenter_with_reply')) {
                return
            }
            if (filt_like && !item.classList.contains('commenter_with_like')) {
                return
            }
            if (item.classList.contains('commenter_filt_by_text')) {
                return
            }
            text += item.outerHTML
        });
        GM_setClipboard(text, 'text')
    }

    function copy_text() {
        let items = document.querySelectorAll('#comment [style="display: block;"][data-tab="item"]>.comment-item')
        let text = ''
        let filt_plus = document.getElementById('commenter_non_plus_vip').checked
        let filt_reply = document.getElementById('commenter_non_reply').checked
        let filt_like = document.getElementById('commenter_non_like').checked
        items.forEach(item => {
            if (filt_plus && !item.classList.contains('commenter_plus_vip_item')) {
                return
            }
            if (filt_reply && !item.classList.contains('commenter_with_reply')) {
                return
            }
            if (filt_like && !item.classList.contains('commenter_with_like')) {
                return
            }
            if (item.classList.contains('commenter_filt_by_text')) {
                return
            }
            text += item.innerText.replace('\n', ' ') + '\n'
        });
        GM_setClipboard(text, 'text')
    }

    function auto_pager_with_interval() {
        collect_next_page()
    }

    function shutdown_auto_pager() {
        if (window.auto_pager_running) {
            window.clearInterval(window.current_autopager)
            window.auto_pager_running = false
        }
    }

    function auto_next_page() {
        shutdown_auto_pager()
        var commenter_auto_np_node = document.getElementById('commenter_auto_np')
        if (commenter_auto_np_node.checked) {
            window.auto_pager_running = true
            var interval = document.getElementById('commenter_auto_np_interval').value
            window.current_autopager = setInterval(() => {
                auto_pager_with_interval()
            }, interval * 1000);

        } else {
            shutdown_auto_pager()
            return
        }
    }

    function update_new_style() {
        let hidden_list = ['.commenter_filt_by_text']
        Object.keys(css_to_hidden).forEach(key => {
            let value = css_to_hidden[key]
            if (value) {
                hidden_list.push(key)
            }
        });
        if (custom_css_to_hidden) {
            hidden_list.push(custom_css_to_hidden)
        }
        document.getElementById('new_style').innerHTML = hidden_list.join(',') + '{display: none;}\n'
    }

    function commenter_clear(checked, css_value) {
        css_to_hidden[css_value] = checked
        update_new_style()
    }

    function commenter_collect_layouts() {

        var head = document.getElementById('comment')
        var mc_node = document.querySelector('#comment .mc')

        var hr = document.createElement("hr");
        head.insertBefore(hr, mc_node)


        var show_button = document.createElement("button");
        show_button.innerText = '展示全部'
        show_button.setAttribute('id', 'commenter_show_button')
        show_button.setAttribute('style', button_style)
        show_button.disabled = true
        show_button.style.color = 'grey'
        show_button.addEventListener('click', show_pages)
        head.insertBefore(show_button, mc_node)

        var copy_button = document.createElement("button");
        copy_button.innerText = '复制 HTML'
        copy_button.setAttribute('id', 'commenter_copy_button')
        copy_button.setAttribute('style', button_style)
        copy_button.style.display = 'none'
        copy_button.addEventListener('click', copy_html)
        head.insertBefore(copy_button, mc_node)

        var copy_text_button = document.createElement("button");
        copy_text_button.innerText = '复制 TEXT'
        copy_text_button.setAttribute('id', 'commenter_copy_text_button')
        copy_text_button.setAttribute('style', button_style)
        copy_text_button.style.display = 'none'
        copy_text_button.addEventListener('click', copy_text)
        head.insertBefore(copy_text_button, mc_node)


        var pastebin = document.createElement("a");
        pastebin.innerText = '📋'
        pastebin.href = 'https://paste.ubuntu.com/'
        pastebin.target = '_blank'
        pastebin.style.margin = '2px'
        head.insertBefore(pastebin, mc_node)

        var commenter_state = document.createElement("span");
        commenter_state.setAttribute('id', 'commenter_state')
        commenter_state.setAttribute('style', 'padding: 0.5em;')
        head.insertBefore(commenter_state, mc_node)

        var commenter_auto_np_interval = document.createElement("input");
        commenter_auto_np_interval.setAttribute('id', 'commenter_auto_np_interval')
        commenter_auto_np_interval.setAttribute('value', '2')
        commenter_auto_np_interval.setAttribute('size', 1)
        commenter_auto_np_interval.setAttribute('style', 'text-align: center;')
        head.insertBefore(commenter_auto_np_interval, mc_node)

        var filter_node = document.createElement("div")
        filter_node.setAttribute('id', 'commenter_filter')
        filter_node.style.display = 'inline'
        filter_node.innerHTML = `
<label for="commenter_auto_np">
    <span style="weight: 1000; margin: 3px;">(s) <b>自动翻页</b>:</span>
    <input type="checkbox" id="commenter_auto_np">
</label>
<button id="commenter_crawl_button" disabled style="font-style: inherit; font-variant: inherit; font-weight: inherit; font-stretch: inherit; margin: 3px; overflow: visible; text-transform: none; -webkit-appearance: button; letter-spacing: 0.01em; zoom: 1; line-height: normal; white-space: nowrap; vertical-align: middle; text-align: center; cursor: pointer; -webkit-user-drag: none; user-select: none; box-sizing: border-box; font-size: 100%; padding: 0.5em 1em; color: grey; border: none transparent; background-color: rgb(230, 230, 230); text-decoration: none; border-radius: 2px; font-family: inherit; weight: 1000">手动翻页</button> | 
<button id="commenter_get_help">帮助</button>
<span id="commenter_status_bar"></span>
<hr><span><b>去噪:</b></span>
<label for="commenter_user"><input type="checkbox" checked name=".user-info" id="commenter_user">用户</label>
<label for="commenter_production"><input type="checkbox" checked name=".order-info>span:first-child" id="commenter_production">产品</label>
<label for="commenter_time"><input type="checkbox" checked name=".order-info>span:last-child" id="commenter_time">时间</label>
<label for="commenter_append"><input type="checkbox" checked name=".append-comment" id="commenter_append">追评</label>
<label for="commenter_append_time"><input type="checkbox" checked name=".append-comment>.append-time" id="commenter_append_time">追评时间</label>
<label for="commenter_other"><input type="checkbox" checked name=".comment-op,.user-level,.comment-star,.comment-message" id="commenter_other">冗余</label>
<label for="commenter_comment"><input type="checkbox" name=".comment-con" id="commenter_comment">文本</label>
<label for="commenter_comment_pics"><input type="checkbox" checked name=".pic-list" id="commenter_comment_pics">图片</label>
<label for="commenter_comment_video"><input type="checkbox" checked name=".J-video-view-wrap" id="commenter_comment_video">视频</label>
<label for="commenter_comment_tags"><input type="checkbox" checked name=".comment-info" id="commenter_comment_tags">标签</label>
<label for="commenter_seller_comment"><input type="checkbox" checked name=".recomment-con" id="commenter_seller_comment">卖家回复</label>
<label for="commenter_non_plus_vip"><input type="checkbox" name=".comment-item:not(.commenter_plus_vip_item)" title="如果选中, 则只显示 Plus 会员的评论" id="commenter_non_plus_vip">非 PLUS 会员</label>
<br>
<label for="commenter_non_reply"><input type="checkbox" name=".comment-item:not(.commenter_with_reply)" title="如果选中, 则只显示有回复的评论" id="commenter_non_reply">无回复</label>
<label for="commenter_non_like"><input type="checkbox" name=".comment-item:not(.commenter_with_like)" title="如果选中, 则只显示有点赞的评论" id="commenter_non_like">无赞</label>
<label for="commenter_comment_custom"><input type="text" style="width:10em;" value="" placeholder="自定义 CSS 过滤" id="commenter_comment_custom"></label>
<label for="commenter_text_filter"><input type="text" style="width:15em;" value="" placeholder="过滤词, 如'手机 -三星'" title="过滤词, 如'手机 -三星'" id="commenter_text_filter"></label>


<hr>
        `
        head.insertBefore(filter_node, mc_node)

        document.getElementById('commenter_get_help').addEventListener('click', alert_doc)
        document.getElementById('commenter_text_filter').addEventListener('input', filt_by_text)
        document.getElementById('commenter_crawl_button').addEventListener('click', collect_next_page)
        document.getElementById('commenter_auto_np').addEventListener('change', function () {
            auto_next_page()
        }, false)

        var filter_ids = ['commenter_user', 'commenter_production', 'commenter_time', 'commenter_append', 'commenter_append_time', 'commenter_other', 'commenter_comment', 'commenter_comment_pics', 'commenter_comment_video', 'commenter_comment_tags', 'commenter_seller_comment', 'commenter_non_plus_vip', 'commenter_non_reply', 'commenter_non_like']
        filter_ids.forEach(eid => {
            let node = document.getElementById(eid)
            css_to_hidden[node.name] = node.checked
            update_new_style()
            node.addEventListener('change', function () {
                commenter_clear(this.checked, this.name)
            })
        });
        let commenter_comment_custom = document.getElementById('commenter_comment_custom')

        commenter_comment_custom.addEventListener('input', function () {
            custom_css_to_hidden = this.value
            this.title = this.value
            update_new_style()
        })
        var tries = 0
        var checkExist = setInterval(function () {
            tries += 1
            if (document.getElementById('commenter_crawl_button')) {
                document.querySelectorAll('ul.filter-list>li').forEach(element => {
                    element.addEventListener('click', function () {
                        pages = {}
                    })
                });
                commenter_crawl_button.disabled = false
                commenter_crawl_button.style.color = 'black'
                collect_dom_to_pages()
                clearInterval(checkExist);
                return
            }
            if (tries > 20) {
                clearInterval(checkExist);
                return
            }
        }, 200);

    }

    function collect_next_page() {
        var np = document.querySelector('#comment [style="display: block;"][data-tab="item"] .ui-pager-next')
        if (!np) {
            shutdown_auto_pager()
            var commenter_auto_np_node = document.getElementById('commenter_auto_np')
            commenter_auto_np_node.checked = false
            document.getElementById('commenter_status_bar').innerText = '没有下一页, 点击[展示全部]进行复制'
            alert('没有下一页')
            return false
        } else {
            np.removeAttribute('href')
            np.click()
        }
        return true
    }

    function setup_commenter_collect_layouts() {
        if (document.getElementById('commenter_auto_np_interval')) {
            return
        }
        document.querySelector('[data-anchor="#comment"]').click()
        let observer = new MutationObserver(collect_dom_to_pages);
        let options = {
            'childList': true,
            'subtree': true,
            // 'characterData': true,
            // 'attributes': true,
        };
        // observer.observe(document.querySelector('#comment>.mc'), options)
        var tries = 0
        var checkExist = setInterval(function () {
            tries += 1
            var c0 = document.getElementById('comment-0')
            if (c0) {
                // console.log("Exists!");
                c0.setAttribute('style', 'display: block;')
                observer.observe(document.querySelector('#comment .mc >.comments-list>.tab-con'), options);
                commenter_collect_layouts()
                clearInterval(checkExist);
                return
            }
            if (tries > 20) {
                clearInterval(checkExist);
                return
            }
        }, 200);

    }

    function setup() {
        var tab = document.querySelector('#detail .tab-main>ul')
        var button = document.createElement("button");
        button.innerHTML = '<b>收集评论</b>'
        button.setAttribute('id', 'commenter_collect')
        button.setAttribute('style', button_style)
        button.style.zoom = '1.3'
        button.addEventListener('click', setup_commenter_collect_layouts)
        tab.appendChild(button)
    }
    window.onload = setup
})();