├── urls.txt ├── package.json ├── LICENSE ├── README.md ├── .gitignore └── index.js /urls.txt: -------------------------------------------------------------------------------- 1 | https://steamcommunity.com/sharedfiles/filedetails/?id=example1 2 | https://steamcommunity.com/sharedfiles/filedetails/?id=example2 3 | https://steamcommunity.com/sharedfiles/filedetails/?id=example3 -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "batchdownload", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "author": { 7 | "name": "Ronit Ramdam BK", 8 | "email": "ron.ram5126@gmail.com" 9 | }, 10 | "scripts": { 11 | "start": "node index.js" 12 | }, 13 | "dependencies": { 14 | "axios": "^0.19.2", 15 | "jsdom": "^16.2.1", 16 | "node-downloader-helper": "^1.0.12" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Ronit Ramdam BK 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Steam Workshop Collection Downloader 2 | 3 | Simple steam workshop collection downloader built using nodejs. **This package only downloads workshop collections, not individual workshop file. please use [Steam Workshop Download](http://steamworkshop.download/) for individual packages.** Follow the instruction below to use this. 4 | 5 | 1. Firstly you will need Node JS installed in your pc. you can get it from [https://nodejs.org/en/download/](https://nodejs.org/en/download/). 6 | 2. Download or Clone the repository. (you can do it using the clone or download button and download zip, extract the zip into a folder.) 7 | 3. Open cmd or bash in the directory where repository was cloned. *(open cmd or bash and go to the directory. make sure it is prompting on the same drive c: or d: and use `cd `.)* 8 | 4. Run `npm install` command on cmd or bash to install required libraries. 9 | 5. Now open urls.txt using any texteditor and put urls to **collections** as it is provided in given example. *(make sure that the url are to the collection and that they are seperated by line break ``)* 10 | 6. Run `npm start` command to start downloading. All your downloads should be under downloads folder. 11 | 12 | *note: if you want to change the default file extension from .zip use `npm start ` command instead of `npm start`* 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | 106 | downloads -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const jsdom = require("jsdom"); 3 | const axios = require("axios").default; 4 | const { DownloaderHelper } = require("node-downloader-helper"); 5 | 6 | const { JSDOM } = jsdom; 7 | 8 | const urls = fs.readFileSync("./urls.txt").toString().split("\n"); 9 | 10 | function last(arr) { 11 | if (Array.isArray(arr) && arr.length > 0) { 12 | return arr[arr.length - 1]; 13 | } else { 14 | return undefined; 15 | } 16 | } 17 | function validName(name) { 18 | return name 19 | .split(/[\*\/\\\|\?\"\'\:\;]/).join(" ") 20 | .split(/[{[<]/).join("(") 21 | .split(/[}\]>]/).join(")"); 22 | } 23 | 24 | async function download(count = 0) { 25 | const url = urls[count]; 26 | if (count >= urls.length) { 27 | return; 28 | } 29 | const downloadDir = "./downloads"; 30 | if (!fs.existsSync(downloadDir)) { 31 | fs.mkdirSync(downloadDir); 32 | } 33 | if (url.includes("http")) 34 | await axios.get(url).then(async ({data}) => { 35 | 36 | const dom = new JSDOM(data); 37 | const window = dom.window; 38 | const document = window.document; 39 | const linkArray = [].slice.call(document.getElementsByClassName("collectionItemDetails")).map(a => a.children[0]); 40 | const namedUrlArray = linkArray.map(link => [link.href, link.children[0].textContent]) 41 | 42 | const collectionName = document.getElementsByClassName("workshopItemTitle")[0].textContent; 43 | const collectionDir = `${downloadDir}/${validName(collectionName)}`; 44 | 45 | if (!fs.existsSync(collectionDir)) { 46 | fs.mkdirSync(collectionDir) 47 | } 48 | 49 | const appId = last( 50 | document.getElementsByClassName('breadcrumbs')[0] 51 | .getElementsByTagName('a')[0] 52 | .href.split('/') 53 | ); 54 | 55 | console.log(`downloading ${collectionName}`); 56 | 57 | for (let idx = 0; idx < namedUrlArray.length; idx ++) { 58 | const [link, packageName] = namedUrlArray[idx]; 59 | const workshopId = link.split("?id=")[1]; 60 | const url = `http://steamworkshop.download/download/view/${workshopId}`; 61 | const fileName = validName(`${packageName.split("/").join("")}${process.argv[2]||".zip"}`); 62 | 63 | if (fs.existsSync(collectionDir + '/' + fileName)){ 64 | console.log(`Skipping ${packageName.split("/").join("")}, file already downloaded`) 65 | 66 | } else { 67 | await axios.get(url).then(async ({data}) => { 68 | const dom = new JSDOM(data); 69 | const { window } = dom; 70 | const { document } = window; 71 | 72 | const subDownloadButton = document.getElementById("steamdownload"); 73 | 74 | 75 | // const filePath = `${collectionDir}/${fileName}` 76 | 77 | console.log(` ${idx}. ${packageName}`); 78 | if (subDownloadButton) { 79 | let response = await axios.post("http://steamworkshop.download/online/steamonline.php", `item=${workshopId}&app=${appId}`); 80 | const url = last(response.data.split("")[0]; 81 | await downloadAndSave(url, collectionDir, fileName); 82 | } else { 83 | const link = document.getElementsByTagName("table")[0].children[0].children[0].children[0].children[1].children[0]; 84 | const url = link.href; 85 | await downloadAndSave(url, collectionDir, fileName); 86 | } 87 | 88 | }).catch(err => { 89 | console.log(packageName); 90 | console.log(collectionName); 91 | console.log(err); 92 | }); 93 | } 94 | }; 95 | 96 | 97 | }).catch(err => { 98 | console.log(url); 99 | console.log(err); 100 | }); 101 | await download(count + 1); 102 | }; 103 | 104 | async function downloadAndSave(url, dir, fileName) { 105 | await new Promise((res, rej) => { 106 | const helper = new DownloaderHelper(url, dir, { fileName }); 107 | helper.on('end', (...args) => { 108 | res(args) 109 | }); 110 | helper.on('error', (...args) => { 111 | console.error(args); 112 | rej(args); 113 | } ); 114 | helper.start(); 115 | }); 116 | } 117 | 118 | // async function downloadAndSave(url, filePath) { 119 | // const response = await axios.get(url); 120 | // console.log(response.status); 121 | // Object.keys(response.headers).forEach(res => { 122 | // console.log(`${res}: ${JSON.stringify(response.headers[res])}`) 123 | // }); 124 | // await savefile(response.data, filePath); 125 | // } 126 | 127 | // async function savefile(data, filePath) { 128 | 129 | // await new Promise((res, rej) => { 130 | // fs.writeFile(filePath, data, (err) => { 131 | // if(err) { 132 | // rej(err); 133 | // } 134 | // res(); 135 | // }) 136 | // }); 137 | 138 | // } 139 | 140 | 141 | download(); 142 | 143 | 144 | --------------------------------------------------------------------------------