天猫详情商品信息提取

天猫详情商品信息提取辅助,用于批量开票的信息辅助

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください。
// ==UserScript==
// @name         天猫详情商品信息提取
// @namespace    http://tampermonkey.net/
// @homepage     https://einvoice.taobao.com/?source=qn#/invoice/compensate
// @version      1.1.0
// @description  天猫详情商品信息提取辅助,用于批量开票的信息辅助
// @author       You
// @match        https://trade.tmall.com/detail/orderDetail.htm*
// @match        https://myseller.taobao.com/home.htm*
// @match        https://einvoice.taobao.com/*
// @match        https://qn.taobao.com/home.htm/*
// @match        https://dppt.jiangsu.chinatax.gov.cn:8443/*
// @icon         https://icons.duckduckgo.com/ip2/tmall.com.ico
// @grant        GM_setClipboard
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @grant        GM_listValues
// @grant        GM_deleteValue
// @license      MIT
// ==/UserScript==

(async function () {
    'use strict';

    /**
     * 新版天猫详情
     * @returns obj
     */
    async function get_order_obj() {
        const result = { hasRepeat: false }
        const products = []
        let finded = await el_find("div[class^=order-info_order-info] tr.order-item");
        if (!finded) {
            let msg = "页面加载超时/结构更新,未找到商品列表信息";
            alert(msg);
            throw msg;
        }
        const order_item = document.querySelectorAll("div[class^=order-info_order-info] tr.order-item");

        const titles = []
        for (let item of order_item) {
            const temp = get_detail(item)
            console.log("temp=", temp)
            if (temp !== null && temp.price !== '0.00') {
                if (titles.includes(temp.title)) {
                    result.hasRepeat = true
                    continue;
                }
                products.push(temp)
            }
        }

        function get_detail(item) {
            const tds = item.querySelectorAll('td');
            if (tds.length < 5) {
                return null
            }
            const details = {}
            // 标题
            details.title = tds[0].querySelector('a')?.innerText;
            // 商品编码
            const code = /[A-Z]{2,3}\d{6}-?[0-9A-Z]{0,5}/.exec(tds[0].innerText)
            details.code = code !== null ? code[0] : ''
            // 状态
            details.status = tds[2].innerText
            // 单价
            details.price = tds[3].innerText.replace('\n', '')
            // 数量
            details.count = tds[4].innerText
            return details;
        }

        const order_id_re = /订单编号:[1-9][0-9]*/.exec(document.querySelector("div[class^=misc-info_misc-info__]")?.innerText)
        if (order_id_re === null) {
            return null
        }
        result.id = order_id_re[0].replace('订单编号:', '')
        result.products = products
        return result
    }

    function getExportInvoiceInfo() {
        const values = prompt("请粘贴天猫发票平台导出数据")
        const list = [];
        for (let row of values.replace('\r\n', '\n').split('\n')) {
            const cells = row.split('\t');
            list.push(cells);
        }
        const T = {};
        const title = list[0];
        for (let i = 0; i < title.length; i++) {
            T[title[i]] = i;
        }

        const trades = {};
        for (let item of list) {
            const id = item[T['*订单编号']];
            if (id === '*订单编号') { continue; }
            let temp = trades[id] === void 0 ? { 'id': id, 'products': {}, 'company': {} } : trades[id];
            // 如果企业信息未空,则填充
            if (JSON.stringify(temp.company) === '{}') {
                temp.company.name = item[T['*发票抬头']] !== void 0 ? item[T['*发票抬头']] : '';
                temp.company.id = item[T['购方税号']] !== void 0 ? item[T['购方税号']] : '';
                temp.company.address = item[T['企业地址']] !== void 0 ? item[T['企业地址']] : '';
                temp.company.phone = item[T['企业电话']] !== void 0 ? item[T['企业电话']] : '';
                temp.company.bank = item[T['开户行']] !== void 0 ? item[T['开户行']] : '';
                temp.company.account = item[T['开户账号']] !== void 0 ? item[T['开户账号']] : '';
            }
            // 添加商品
            let name = item[T['货物名称']];
            temp.products[name] = {
                'price_invoice': item[T['商品金额']],
                'count': item[T['数量']]
            }
            // 重新写入trades
            trades[id] = temp;
        }
        return trades;
    }

    GM_registerMenuCommand("提取订单信息", () => {
        const trades = GM_listValues();
        for (let item of trades) {
            GM_deleteValue(item)
        }
        let ids = prompt("输入订单号");
        for (let k of ['\n', '\r', '\t', ' ', ',', ',,', ',,']) {
            ids = ids.replaceAll(k, ',')
        }
        const arr = ids.split(',');
        for (let id of arr) {
            GM_setValue(id, false);
        }
        if (arr.length > 0 && arr[0] !== '') {
            GM_setValue('running', true);
            GM_setValue('current', arr[0]);
            window.open(`https://trade.tmall.com/detail/orderDetail.htm?biz_order_id=${arr[0]}`)
        }
    })

    GM_registerMenuCommand("导入发票信息", () => {
        const trades = GM_listValues();
        const invoices = getExportInvoiceInfo();
        for (let item of trades) {
            // 用订单号查询存储结果,没有结果的设为激活的,下一次处理
            const trade = GM_getValue(item, false);
            console.log(typeof (trade), trade)
            const invoice = invoices[item]; // 从导入数据提取订单信息
            if (trade && invoice !== void 0) {
                const ps = invoice.products; // 天猫导出的数据
                const co = invoice.company; // 天猫导出的数据
                console.log("ps=", ps)
                console.log("co=", co)
                console.log("trade=", trade)
                const nps = [];
                for (let p of trade.products) {
                    if (!p.status.includes("退款成功")) {
                        const p1 = ps[p.title];
                        nps.push({
                            'title': p.title,
                            'code': p.code,
                            'price_invoice': p1.price_invoice,
                            'price_page': p.price_page,
                            'count': p1.count,
                            'status': p.status,
                        })
                    }
                }
                trade.products = nps;
                trade.company = co;
                GM_setValue(item, trade);
            }
        }
        alert("发票信息导入处理完成");
    })

    GM_registerMenuCommand("结果=>发票基本信息", () => {
        const trades = GM_listValues();
        let results = '';
        for (let item of trades) {
            // 用订单号查询存储结果,没有结果的设为激活的,下一次处理
            const trade = GM_getValue(item, false);
            if (trade.company !== void 0) {
                const id = trade.id;
                const co = trade.company;
                results += `${id}\t普通发票\t\t是\t\t${co.name}\t\t${co.id}\t${co.address}\t${co.phone}\t${co.bank}\t${co.account}\t${id}\n`;
            }
        }
        console.log(results);
        GM_setClipboard(results);
        alert("发票基本信息已复制")
    })

    GM_registerMenuCommand("结果=>发票明细信息", () => {
        const trades = GM_listValues();
        let results = '';
        for (let item of trades) {
            // 用订单号查询存储结果,没有结果的设为激活的,下一次处理
            const trade = GM_getValue(item, false);
            if (trade.company !== void 0) {
                const id = trade.id;
                const ps = trade.products;
                for (let p of ps) {
                    results += `${id}\t${p.code}\t\t\t件\t${p.count}\t\t${p.price_invoice}\t0.13\n`;
                }
            }
        }
        console.log(results);
        GM_setClipboard(results);
        alert("发票明细信息已复制")
    })


    GM_registerMenuCommand("屏蔽发票申请界面元素", () => {
        if (!document.URL.includes('merchant-invoice')) { return }
        const els = document.querySelectorAll("div[class*=SearchContent_search]");
        const keys = ["是否免赔", "开票倒计时"];
        for (let el of els) {
            for (let key of keys) {
                if (el.innerText.includes(key)) { el.style.display = "none"; }
            }
        }
    })

    /**
     * 
     * @param {string} selector 
     * @param {number} count 查找次数,默认10
     * @param {number} ms 查找间隔,默认500ms
     * @returns 
     */
    async function el_find(selector, count = 10, ms = 500) {
        let found = false
        for (let i = 0; i < count; i++) {
            console.log(`尝试${i + 1}/${count}次查找【${selector}】`)
            found = await new Promise((resolve) => {
                setTimeout(() => {
                    resolve(document.querySelector(selector) !== null);
                }, ms);
            })
            if (found) {
                console.log('got it ' + selector)
                break
            }
        }
        return found;
    }

    // 订单详情读取

    if (!document.URL.includes('https://qn.taobao.com/home.htm/trade-platform/tp/detail')) { return; }

    setTimeout(async () => {
        const current = GM_getValue('current', false);
        if (!current) { return; }
        if (document.URL.includes(current)) {
            const p = await get_order_obj()
            console.log(current, ":", p)
            if (p.hasRepeat) {
                console.log(current, '存在重复商品,跳过未统计')
            } else {
                GM_setValue(current, p);
                console.log(GM_getValue(current, `GM获取失败,${current}`));
            }
        }
        const running = GM_getValue('running', false);
        if (!running) { return; }
        const trades = GM_listValues();
        for (let item of trades) {
            // 用订单号查询存储结果,没有结果的设为激活的,下一次处理
            if (!GM_getValue(item, false)) {
                GM_setValue('current', item);
                window.location = `https://qn.taobao.com/home.htm/trade-platform/tp/detail?bizOrderId=${item}`
                return;
            }
        }
        GM_deleteValue('running');
        GM_deleteValue('current');
        alert("订单遍历完成");
    }, 3000);
})();