Steam快速添加购物车

在商店页显示双语游戏名称,双击名称可以快捷搜索。

目前为 2021-09-12 提交的版本。查看 最新版本

// ==UserScript==
// @name         Fast_Add_Cart
// @name:zh-CN   Steam快速添加购物车
// @namespace    https://blog.chrxw.com
// @version      2.0
// @description  在商店页显示双语游戏名称,双击名称可以快捷搜索。
// @description:zh-CN  在商店页显示双语游戏名称,双击名称可以快捷搜索。
// @author       Chr_
// @include      /https://store\.steampowered\.com\/search/.*/
// @include      /https://store\.steampowered\.com\/publisher/.*/
// @include      /https://store\.steampowered\.com\/cart/.*/
// @license      AGPL-3.0
// @icon         https://blog.chrxw.com/favicon.ico
// @grant        GM_addStyle
// ==/UserScript==

(async () => {
    'use strict';
    //初始化

    let pathname = window.location.pathname;

    if (pathname.indexOf('search') !== -1) { //搜索页
        let t = setInterval(() => {
            let container = document.getElementById('search_resultsRows');
            if (container != null) {
                clearInterval(t);
                for (let ele of container.children) {
                    addButton(ele);
                }
                container.addEventListener('DOMNodeInserted', ({ relatedNode }) => {
                    if (relatedNode.parentElement === container) {
                        addButton(relatedNode);
                    }
                });
            }
        }, 500);
    } else if (pathname.indexOf('publisher') !== -1) { //发行商主页
        let t = setInterval(() => {
            let container = document.getElementById('RecommendationsRows');
            if (container != null) {
                clearInterval(t);
                for (let ele of container.querySelectorAll('a.recommendation_link')) {
                    addButton2(ele);
                }
                container.addEventListener('DOMNodeInserted', ({ relatedNode }) => {
                    if (relatedNode.nodeName === 'DIV') {
                        console.log(relatedNode);
                        for (let ele of relatedNode.querySelectorAll('a.recommendation_link')) {
                            addButton2(ele);
                        }
                    }
                });
            }
        }, 500);
    } else { //购物车页
        let continer = document.querySelector('div.cart_area_body');

        let genBr = () => { return document.createElement('br'); };
        let genBtn = (text, onclick) => {
            let btn = document.createElement('button');
            let spn = document.createElement('span');
            btn.textContent = text;
            btn.className = 'btn_medium btnv6_blue_hoverfade fac_cartbtns';
            btn.addEventListener('click', onclick);
            return btn;
        };
        let inputBox = document.createElement('textarea');

        inputBox.className = 'fac_inputbox';
        inputBox.placeholder = ['一行一条, 支持的格式如下:',
            '1. 商店链接: https://store.steampowered.com/app/xxx',
            '2. appID:   xxx a/xxx app/xxx',
            '3. subID:       s/xxx sub/xxx',
            '4. bundleID:    b/xxx bundle/xxx'
        ].join('\n');


        let btnArea = document.createElement('div');

        let btnImport = genBtn('🔼批量导入购物车', async () => { inputBox.value = await importCart(inputBox.value); });
        let btnExport = genBtn('🔽导出当前购物车', () => { inputBox.value = exportCart(); });
        let btnHelp = genBtn('🔣帮助', () => {
            showAlert('帮助', [
                '<p>【🔼批量导入购物车】将当前购物车内容保存至文本框。</p>',
                '<p>【🔽导出当前购物车】按照文本框内容批量导入购物车。</p>',
                '<p>【🔣帮助】显示没什么卵用的帮助。</p>',
                '<p>【<a href=https://keylol.com/t747892-1-1 target="_blank">发布帖</a>】 【<a href=https://blog.chrxw.com/scripts.html target="_blank">脚本反馈</a>】</p>'
            ].join('<br>'), true)
        });

        btnArea.appendChild(btnImport);
        btnArea.appendChild(btnExport);
        btnArea.appendChild(btnHelp);


        continer.appendChild(btnArea);
        btnArea.appendChild(genBr());
        btnArea.appendChild(genBr());
        continer.appendChild(inputBox);

    }
    //导入购物车
    function importCart(text) {
        return new Promise(async (resolve, reject) => {
            let regFull = new RegExp(/(app|a|bundle|b|sub|s)\/(\d+)/);
            let regShort = new RegExp(/()(\d+)/);
            let lines = [];
            let dialog = showAlert('操作中……', '正在导入购物车...', true);
            for (let line of text.split('\n')) {
                let match = line.match(regFull) ?? line.match(regShort);
                if (!match) {
                    let tmp = line.split('#')[0];
                    lines.push(`${tmp} #格式有误`);
                    continue;
                }
                let [_, type, subID] = match;
                switch (type.toLowerCase()) {
                    case '':
                    case 'a':
                    case 'app':
                        type = 'app';
                        break;
                    case 's':
                    case 'sub':
                        type = 'sub';
                        break;
                    case 'b':
                    case 'bundle':
                        type = 'bundle';
                        break;
                    default:
                        let tmp = line.split('#')[0];
                        lines.push(`${tmp} #格式有误`);
                        continue;
                }

                if (type === 'sub' || type === 'bundle') {
                    let [succ, msg] = await addCart(type, subID, '');
                    lines.push(`${type}/${subID} #${msg}`);

                } else {
                    try {
                        let subInfos = await getGameSubs(subID);
                        let [sID, subName] = subInfos[0];
                        let [succ, msg] = await addCart('sub', sID, subID);
                        lines.push(`${type}/${subID} #${subName} ${msg}`);
                    } catch (e) {
                        lines.push(`${type}/${subID} #未找到可用SUB`);
                    }
                }
                let d = showAlert('操作中……', `<p>${lines.join('</p><p>')}</p>`, true);
                setTimeout(() => { d.Dismiss(); }, 1200);
            }
            dialog.Dismiss();
            resolve(lines.join('\n'));
        });
    }
    //导出购物车
    function exportCart() {
        let data = [];
        for (let item of document.querySelectorAll('div.cart_item_list>div.cart_row ')) {
            let id = item.getAttribute('data-ds-appid') ?? item.getAttribute('data-ds-bundleid');
            if (item.hasAttribute('data-ds-bundleid')) {
                data.push(`bundle/${id}`);
            } else if (item.hasAttribute('data-ds-appid')) {
                data.push(`app/${id}`);
            }
        }
        return data.join('\n');
    }
    //添加按钮
    function addButton(element) {
        if (element.getAttribute('added') !== null) { return; }
        element.setAttribute('added', '');

        let appID = (element.href.match(/\/app\/(\d+)/) ?? [null, null])[1];

        if (appID == null) { return; }

        let btn = document.createElement('button');
        btn.addEventListener('click', async (e) => {
            chooseSubs(appID);
            e.preventDefault();
        }, false);
        btn.id = appID;
        btn.className = 'fac_listbtns';
        btn.textContent = '🛒';
        element.appendChild(btn);
    }
    //添加按钮
    function addButton2(element) {
        if (element.getAttribute('added') !== null) { return; }
        element.setAttribute('added', '');

        let appID = (element.href.match(/\/app\/(\d+)/) ?? [null, null])[1];

        if (appID == null) { return; }

        let btn = document.createElement('button');
        btn.addEventListener('click', async (e) => {
            chooseSubs(appID);
            e.preventDefault();
        }, false);
        btn.id = appID;
        btn.className = 'fac_publisherbtns';
        btn.textContent = '🛒';
        element.appendChild(btn);
    }
    //选择SUB
    async function chooseSubs(appID) {
        let dialog = showAlert('操作中……', '<p>读取可用SUB</p>', true);
        getGameSubs(appID)
            .then(async (subInfos) => {
                if (subInfos.length === 0) {
                    showAlert('添加购物车失败', '<p>未找到可用SUB, 可能尚未发行或者是免费游戏.</p>', false);
                    return;
                } else {
                    console.log(subInfos);
                    if (subInfos.length === 1) {
                        let [subID, subName] = subInfos[0];
                        await addCart('sub', subID, appID);
                        let done = showAlert('添加购物车成功', `<p>${subName}</p>`, true);
                        setTimeout(() => { done.Dismiss(); }, 1200);
                        dialog.Dismiss();
                    } else {
                        let dialog2 = showAlert('请选择SUB', '<div id=fac_choose></div>', true);
                        dialog.Dismiss();
                        await new Promise((resolve) => {
                            let t = setInterval(() => {
                                if (document.getElementById('fac_choose') !== null) {
                                    clearInterval(t);
                                    resolve();
                                }
                            }, 200);
                        });
                        let divContiner = document.getElementById('fac_choose');
                        for (let [subID, subName] of subInfos) {
                            let btn = document.createElement('button');
                            btn.addEventListener('click', async () => {
                                let dialog = showAlert('操作中……', `<p>添加 ${subName} 到购物车</p>`, true);
                                dialog2.Dismiss();
                                let [succ, msg] = await addCart('sub', subID, appID);
                                let done = showAlert(msg, `<p>${subName}</p>`, succ);
                                setTimeout(() => { done.Dismiss(); }, 1200);
                                dialog.Dismiss();
                            });
                            btn.textContent = '🛒添加购物车';
                            btn.className = 'fac_choose';
                            let p = document.createElement('p');
                            p.textContent = subName;
                            p.appendChild(btn);
                            divContiner.appendChild(p);
                        }
                    }
                }

            })
            .catch(err => {
                let done = showAlert('网络错误', `<p>${err}</p>`, false);
                setTimeout(() => { done.Dismiss(); }, 2000);
                dialog.Dismiss();
            });
    }
    //读取sub信息
    function getGameSubs(appID) {
        return new Promise((resolve, reject) => {
            let lang = document.cookie.replace(/(?:(?:^|.*;\s*)Steam_Language\s*\=\s*([^;]*).*$)|^.*$/, "$1")
            fetch(`https://store.steampowered.com/api/appdetails?appids=${appID}&lang=${lang}`, {
                method: 'GET',
                credentials: 'include',
            })
                .then(async response => {
                    if (response.ok) {
                        let data = await response.json();
                        let result = data[appID];
                        if (result.success !== true) {
                            reject('返回了未知结果');
                        }
                        let subInfos = [];
                        for (let pkg of result.data.package_groups) {
                            for (let sub of pkg.subs) {
                                let { packageid, option_text, price_in_cents_with_discount } = sub;
                                if (price_in_cents_with_discount > 0) { //排除免费SUB
                                    subInfos.push([packageid, option_text]);
                                }
                            }
                        }
                        resolve(subInfos);
                    } else {
                        reject('网络请求失败');
                    }
                }).catch(err => {
                    reject(err);
                });
        });
    }
    //添加购物车,只支持subID
    function addCart(type = 'sub', subID, appID = null) {
        window.localStorage['fac_subid'] = subID;
        return new Promise((resolve, reject) => {
            let data = {
                action: "add_to_cart",
                originating_snr: "1_store-navigation__",
                sessionid: document.cookie.replace(/(?:(?:^|.*;\s*)sessionid\s*\=\s*([^;]*).*$)|^.*$/, "$1"),
                snr: "1_5_9__403",
            }
            data[`${type}id`] = String(subID);
            let s = [];
            for (let k in data) {
                s += `${k}=${encodeURIComponent(data[k])}&`;
            }
            fetch('https://store.steampowered.com/cart/', {
                method: 'POST',
                credentials: 'include',
                body: s,
                headers: { 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8' },
            })
                .then(async response => {
                    if (response.ok) {
                        let data = await response.text();
                        if (appID !== null) {
                            let reg = new RegExp('app\/' + appID);
                            if (data.search(reg) !== -1) {
                                resolve([true, '添加购物车成功']);
                            }
                            else {
                                resolve([false, '添加购物车失败']);
                            }
                        } else {
                            resolve([true, '添加购物车成功']);
                        }
                    } else {
                        resolve([false, '网络请求失败']);
                    }
                }).catch(err => {
                    console.error(err);
                    resolve([false, '未知错误:' + err]);
                });
        });
    }
    //显示提示
    function showAlert(title, text, succ = true) {
        return ShowAlertDialog(`${succ ? '✅' : '❌'}${title}`, text);
    }
})();

GM_addStyle(`
button.fac_list_btn,
button.fac_publisherbtns,
button.fac_listbtns {
  display: none;
  position: relative;
  z-index: 100;
  padding: 1px;
}
button.fac_listbtns {
  top: -25px;
  left: 300px;
  position: relative;
}
button.fac_publisherbtns {
  bottom: 7px;
  left: 310px;
  position: absolute;
}
button.fac_listbtns {
  top: -25px;
  left: 300px;
  position: relative;
}
button.fac_cartbtns {
  padding: 5px 10px;
}
button.fac_cartbtns:not(:last-child) {
  margin-right: 15px;
}
a.search_result_row:hover button.fac_listbtns,
div.recommendation:hover button.fac_publisherbtns {
  display: block;
}
button.fac_choose {
  padding: 1px;
  margin: 2px 5px;
}
textarea.fac_inputbox{
  height: 120px;
  resize: vertical;
  font-size: 10px;
}
`);

QingJ © 2025

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