您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
专门用来汇出与汇入工作坊模组清单的脚本!
当前为
// ==UserScript== // @name Steam 工作坊模组辅助脚本 // @namespace http://tampermonkey.net/ // @version 1.0 // @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()); } })();