Greasy Fork

Greasy Fork is available in English.

Steam 工作坊模组辅助脚本

专门用来汇出与汇入工作坊模组清单的脚本!

当前为 2018-12-28 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Steam 工作坊模组辅助脚本
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  专门用来汇出与汇入工作坊模组清单的脚本!
// @author       LaysDragon镭锶龙
// @match        https://steamcommunity.com/id/*/myworkshopfiles/*
// @require      https://cdnjs.cloudflare.com/ajax/libs/URI.js/1.19.1/URI.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/1.3.8/FileSaver.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/lz-string/1.4.4/lz-string.min.js
// @require      https://code.jquery.com/jquery-3.3.1.min.js
// @grant        none
// ==/UserScript==

(function() {
    'use strict';
    window.URI = URI;
    var $NJ = $;
    window.$NJ = $;
    $.noConflict()
    //var URI = URI;
    //var saveAs = saveAs;

    if(typeof URI().query(true).appid === 'undefined' || URI().query(true).appid === "0"){
        return;
    }

    //$NJ("head").append ('<link href="https://code.jquery.com/ui/1.12.1/themes/vader/jquery-ui.css" rel="stylesheet" type="text/css">');

    $NJ('.rightDetailsBlock:eq(1)').prepend('<br>');
    $NJ('.rightDetailsBlock:eq(1)').prepend($NJ('<span class="btn_green_white_innerfade btn_medium"> <span> 汇入模组清单 </span> </span>').click(startImport));
    $NJ('.rightDetailsBlock:eq(1)').prepend($NJ('<span class="btn_green_white_innerfade btn_medium" style="margin:0px 10px 10px 0px"> <span> 汇出模组清单 </span> </span>').click(startExport));


    $NJ('body').append($NJ(`
<form class="smallForm" method="POST" name="PublishedFileSubscribe" id="PublishedFileSubscribe" action="https://steamcommunity.com/sharedfiles/subscribe">
<input type="hidden" name="id" value="">
<input type="hidden" name="appid" value="">
<input type="hidden" name="sessionid" value="${getSessionID()}">
</form>
`))
    function getSessionID(){
        return $NJ('form#PublishedFileUnsubscribe input[name="sessionid"]').val()
    }
    function getPageList(){
        return $NJ('[id^=Subscription]').map(function(){
            let ele = $NJ(this);
            //https://steamcommunity.com/sharedfiles/filedetails/?id=
            return {
                name:ele.find('.workshopItemTitle').text(),
                //link:ele.find('a:has(div.workshopItemTitle)').attr('href'),
                id:URI(ele.find('a:has(div.workshopItemTitle)').attr('href')).query(true).id,
                //img:ele.find('img.workshopItemPreviewImage').attr('src')
            }
        }).get();
    }
    function currentMaxItems(){
        let all = new Set(['10','20','30']);
        let items = new Set($NJ('.workshopBrowsePaging div:eq(1) a').map(function(){return $NJ(this).text()}).get());
        let target = [...all].filter(x => !items.has(x));
        return target[0];

    }
    function selectMaxItems(value){
        let all = new Set(['10','20','30']);
        if(!all.has(value)){
            throw new Error('Invalid Select Option');
        }
        $NJ
        (`.workshopBrowsePaging div:eq(1) a:contains("${value}")`)[0].click();
    }


    function hasNextPage(){
        let nextPageButton = $NJ('.workshopBrowsePagingControls:first').find('span:contains(">"),a:contains(">")');
        return nextPageButton.length!=0 && !nextPageButton.hasClass('disabled');
    }
    function goNextPage(){
        $NJ('.workshopBrowsePagingControls:first').find('span:contains(">"),a:contains(">")')[0].click();
    }
    //status: STOP PROCESSING
    function getStatus(){
        return localStorage.getItem('exporterStatus')||"STOP";
    }

    let allFlags = new Set(['TASK_CLEAR_IMPORT']);
    function setFlag(flag){
        if(!allFlags.has(flag)){
            throw new Error('Invalid Flag');
        }
        localStorage.setItem('exporterFlag_'+flag,true);
    }

    function clearFlag(flag){
        if(!allFlags.has(flag)){
            throw new Error('Invalid Flag');
        }
        localStorage.setItem('exporterFlag_'+flag,false);
    }

    function getFlag(flag){
        if(!allFlags.has(flag)){
            throw new Error('Invalid Flag');
        }
        return JSON.parse(localStorage.getItem('exporterFlag_'+flag)||'false');
    }

    function getAppID(){
        return URI().query(true).appid;
    }
    function getAppName(){
        return $NJ
        ('.HeaderUserInfoSection:last').text();
    }
    function getUserID(){
        return URI().segment(1);
    }
    function setStatus(status){
        let all = new Set(['STOP','PROCESSING']);
        if(!all.has(status)){
            throw new Error('Invalid Status');
        }
        localStorage.setItem('exporterStatus',status);
    }
    function getListData(){
        return JSON.parse(localStorage.getItem('exporterLists')||'[]');
    }
    function setListData(data){
        localStorage.setItem('exporterLists',JSON.stringify(data));
    }

    function getImportListData(){
        return JSON.parse(localStorage.getItem('exporterImportLists')||'[]');
    }
    function setImportListData(data){
        localStorage.setItem('exporterImportLists',JSON.stringify(data));
    }

    function startExport(){

        //$NJ( "#export_notification_dialog" ).dialog();
        ShowBlockingWaitDialog('汇出MOD清单','脚本正在进行模组资料收集作业,请稍后...');
        if(getStatus()=="STOP"){
            setStatus('PROCESSING');
            setListData([]);
            location.href = URI().search(function(data) {
                data.p = 1;
                return data;
            }).toString();
        }
    }
    function startImport(){
        {


            let $Body = $NJ('<div/>');


            $Body.append( $NJ(`<p>请填入存档字串或者JSON字串</p>`));


            let $TextArea = $NJ('<textarea/>' , { 'class': 'newmodal_prompt_textarea' } );
            $Body.append( $NJ('<div/>', {'class': 'newmodal_prompt_with_textarea gray_bevel fullwidth ' } ).append( $TextArea ) );

            var deferred = new jQuery.Deferred();
            var fnOK = function() { deferred.resolve( $TextArea.val() ); };
            var fnCancel = function() { deferred.reject(); };

            let $okButton = $NJ('<button/>', {type: 'submit', 'class': 'btn_green_white_innerfade btn_medium' } ).append( $NJ( '<span/>' ).text( '确定' ) );
            $okButton.click( function(){
                importing($TextArea.val());
                Modal.Dismiss();
            } );
            deferred.always( function() { Modal.Dismiss(); } );

            let $CancelButton = _BuildDialogButton( '取消' );
            $CancelButton.click( fnCancel);


            let Modal = _BuildDialog( '汇入模组清单', $Body, [$okButton, $CancelButton ],fnCancel );
            deferred.promise( Modal );
            Modal.Show();

        }

    }

    function tryParseJson(str) {
        try {
            var obj = JSON.parse(str);
        } catch (e) {
            return {success:false};
        }
        return {success:true,data:obj};
    }

    function importing(data){
        try{
            if(typeof data === "string"){
                var data = tryParseData(data);
            }

            setImportListData(data);
            if(data.appid!==getAppID()){
                throw new Error(`该存档字串游戏为 ${data.name}(${data.appid}) 不符合当前游戏 ${getAppName()}(${getAppID()})!`);
                return;
            }
            ShowConfirmDialog('清空模组列表','请问是否要清空现有的模组列表?','清空','保留')
                .done(
                function(){
                    setFlag('TASK_CLEAR_IMPORT');
                    startExport();
                })
                .fail(
                function(){
                    SubscribeAll(data.mods,getAppID());
                });
        }catch(e){
            ShowAlertDialog('错误',e.message);
        }

    }

    function tryParseData(text){
        if(text.trim()===''){
            throw new Error('字串不能为空!');
            return;
        }
        text = text.trim();
        let data = tryParseJson(text);
        if(data.success){
            return data.data;

        }else{
            try{
                let data = JSON.parse(LZString.decompressFromBase64(text));
                return data;
            }catch (e) {
                throw new Error('字串解析错误!');
                return;
            }
        }

    }

    function saveListText(data){
        let text = data.mods.map(item=>`${item.name} https://steamcommunity.com/sharedfiles/filedetails/?id=${item.id}`).join('\r\n');
        saveText(text,`${getUserID()} ${getAppName()}(${getAppID()}) ${data.date} ${data.mods.length}个模组 清单.txt`);
    }

    function saveJson(data){
        let text = JSON.stringify(data);
        saveText(text,`${getUserID()} ${getAppName()}(${getAppID()}) ${data.date} ${data.mods.length}个模组 JSON.json`);
    }

    function saveText(text,filename){
        let dialog = ShowBlockingWaitDialog('匯出資料','脚本正在生成檔案中,请稍后...')
        setTimeout(()=>dialog.Dismiss(),3000);
        let data = new Blob([text], {type: 'text/plain;charset=utf-8'});
        saveAs(data, filename);
    }
    function timeout(time){
        return new Promise(function(resolve, reject) {
            setTimeout(resolve,time);
        });
    }
    function SubscribeAll(mods, appID ){
        let progressText = $NJ('<span/>');
        progressText.append('正在进行订阅程序...');
        let progressTextUpdated = $NJ(`<span>${mods[0].name}(0/${mods.length})</span>`);
        progressText.append(progressTextUpdated);
        let progress = ShowBlockingWaitDialog('处理中',progressText)
        var p = Promise.resolve();
        mods.forEach((mod,index) =>
                     p = p.then(() => timeout(100)).then(()=>SubscribeItem(mod,appID)).then(()=>progressTextUpdated.text((index+1)>=mods.length?`訂閱完成,共计${mods.length}个模组`:`${mods[index+1].name}(${index+1}/${mods.length})`))
                    );
        p.catch(function(e){
            progress.Dissmiss();
            ShowAlertDialog( '錯誤', '訂閱時發生錯誤:'+e.message );
        });
        p.then(function(){
            progress.Dismiss();
            ShowAlertDialog( '成功', `訂閱完成,共计${mods.length}个模组` ).done(
                function(){
                    location.href = URI().search(function(data) {
                        data.p = 1;
                        return data;
                    }).toString();
                });
        });
    }

    function SubscribeItem( mod, appID )
    {
        return new Promise(function(resolve, reject) {
            $('PublishedFileSubscribe').id.value = mod.id;
            $('PublishedFileSubscribe').appid.value = appID;
            $('PublishedFileSubscribe').request( {
                onSuccess: resolve
            } );
        });
    }
    function UnsubscribeAll()
    {
        var confirmDialog = ShowConfirmDialog( '全部取消訂閱?', '您確定要取消所有 Starbound 的訂閱嗎?<br><br>此動作無法復原!' );
        return new Promise(function(resolve, reject) {
            confirmDialog.done( function() {
                var waitingDialog = ShowBlockingWaitDialog( '請稍候', '正在取消您的訂閱…' );

                $NJ.post(
                    'https://steamcommunity.com/sharedfiles/unsubscribeall/',
                    { 'sessionid' : getSessionID(), 'appid': getAppID(), 'filetype' : 18 }
                ).done( function( data ) {
                    waitingDialog.Dismiss();

                    if ( data.success == 1 )
                    {
                        waitingDialog.Dismiss();
                        resolve();
                    }
                    else
                    {
                        ShowAlertDialog( '錯誤', '取消訂閱時發生錯誤' );
                        reject(new Error('取消訂閱時發生錯誤'))
                    }
                });


            }).fail(
                function(){
                    reject(new Error('使用者取消'));
                }
            );
        });
    }

    function clearImportTask(){
        clearFlag('TASK_CLEAR_IMPORT');
        UnsubscribeAll()
            .then(()=>SubscribeAll(getImportListData().mods,getAppID()))
            .catch((e)=>ShowAlertDialog( '錯誤', e.message ));
    }

    if(getStatus()=="STOP"){

    }else if(getStatus()=="PROCESSING"){
        let processing_dialog = ShowBlockingWaitDialog('汇出MOD清单','脚本正在进行模组资料收集作业,请稍后...');
        if(currentMaxItems() != '30'){
            selectMaxItems('30');
            return;
        }


        let currentList = getListData();
        currentList.push(...getPageList());
        setListData(currentList);
        if(hasNextPage()){
            goNextPage();
        }else{
            let date = new Date();
            let data = {
                appid:getAppID(),
                name:getAppName(),
                date:`${date.getFullYear()}-${date.getMonth()}-${date.getDay()}`,
                mods:currentList
            }
            setStatus('STOP');
            processing_dialog.Dismiss();
            {


                let $Body = $NJ('<div/>');


                $Body.append( $NJ(`<p>本次的模组共${data.mods.length}个${getFlag('TASK_CLEAR_IMPORT')?',清空前请妥善保存模组存档资料':''}</p>`));
                //$Body.append( $NJ(`<p>模組清单json</p>`));

                //let $TextArea = $NJ('<textarea/>' );
                //$TextArea.text( JSON.stringify(data) );
                //$TextArea.click(function(){this.select();})
                //$Body.append( $NJ('<div/>', {'class': 'newmodal_prompt_with_textarea gray_bevel fullwidth ' } ).append( $TextArea ) );


                $Body.append( $NJ(`<p>存档字串,直接复制即可使用</p>`));

                let $TextArea = $NJ('<textarea/>' , { 'class': 'newmodal_prompt_textarea', 'readonly':true } );
                $TextArea.text( LZString.compressToBase64(JSON.stringify(data)));
                $TextArea.click(function(){this.select();})
                $Body.append( $NJ('<div/>', {'class': 'newmodal_prompt_with_textarea gray_bevel fullwidth ' } ).append( $TextArea ) );

                let $copyButton = $NJ('<button/>', {type: 'submit', 'class': 'btn_green_white_innerfade btn_medium' } ).append( $NJ( '<span/>' ).text( '复制' ) );
                $copyButton.click( function(){
                    $TextArea.select();
                    document.execCommand('copy');
                    ShowAlertDialog('成功','字串已复制到剪贴簿!');
                } );


                let $listButton = $NJ('<button/>', {type: 'submit', 'class': 'btn_green_white_innerfade btn_medium' } ).append( $NJ( '<span/>' ).text( getFlag('TASK_CLEAR_IMPORT')?'备份列表':'儲存列表' ) );
                $listButton.click( function(){
                    saveListText(data);
                } );

                let $jsonButton = $NJ('<button/>', {type: 'submit', 'class': 'btn_green_white_innerfade btn_medium' } ).append( $NJ( '<span/>' ).text( getFlag('TASK_CLEAR_IMPORT')?'备份JSON':'儲存JSON' ) );
                $jsonButton.click( function(){
                    saveJson(data);
                } );
                let buttonList =  [$copyButton, $listButton, $jsonButton ];
                if(getFlag('TASK_CLEAR_IMPORT')){
                    let $continueClearImportButton = $NJ('<button/>', {type: 'submit', 'class': 'btn_darkred_white_innerfade btn_medium' } ).append( $NJ( '<span/>' ).text( '清除所有并继续导入' ) );
                    $continueClearImportButton.click( function(){
                        clearImportTask();
                        Modal.Dismiss();
                    } );
                    buttonList.push($continueClearImportButton)
                }

                let Modal = _BuildDialog( getFlag('TASK_CLEAR_IMPORT')?'模组列表备份完成':'模组列表收集完成', $Body,buttonList,function(){ Modal.Dismiss();;} );

                Modal.Show();
                clearFlag('TASK_CLEAR_IMPORT');

            }
        }
    }else{
        console.error('strange status'+ getStatus());
    }

})();