├── .editorconfig ├── .gitignore ├── LICENSE ├── README.md ├── gist-backup.js └── package.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | 15 | [package.json] 16 | indent_style = space 17 | indent_size = 2 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by http://www.gitignore.io 2 | 3 | ### Linux ### 4 | *~ 5 | 6 | 7 | ### PhpStorm ### 8 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode 9 | 10 | ## Directory-based project format 11 | .idea/ 12 | # if you remove the above rule, at least ignore user-specific stuff: 13 | # .idea/workspace.xml 14 | # .idea/tasks.xml 15 | # and these sensitive or high-churn files: 16 | # .idea/dataSources.ids 17 | # .idea/dataSources.xml 18 | # .idea/sqlDataSources.xml 19 | # .idea/dynamic.xml 20 | 21 | ## File-based project format 22 | *.ipr 23 | *.iws 24 | *.iml 25 | 26 | ## Additional for IntelliJ 27 | out/ 28 | 29 | # generated by mpeltonen/sbt-idea plugin 30 | .idea_modules/ 31 | 32 | # generated by JIRA plugin 33 | atlassian-ide-plugin.xml 34 | 35 | # generated by Crashlytics plugin (for Android Studio and Intellij) 36 | com_crashlytics_export_strings.xml 37 | 38 | 39 | ### Node ### 40 | lib-cov 41 | lcov.info 42 | *.seed 43 | *.log 44 | *.csv 45 | *.dat 46 | *.out 47 | *.pid 48 | *.gz 49 | 50 | pids 51 | logs 52 | results 53 | build 54 | .grunt 55 | 56 | node_modules 57 | gists 58 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Sasha Khamkov 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gist backup 2 | 3 | This package will download all gist files to directories named after gist description. 4 | 5 | Pre-run 6 | --- 7 | 8 | Run `[sudo] npm install gist-backup --global` 9 | 10 | Run 11 | --- 12 | 13 | `gist-backup ` 14 | 15 | or just 16 | 17 | `gist-backup` (will be prompted for username, password and local path to backup directory) 18 | 19 | --- 20 | 21 | If all went well, you'll see "gists" directory populated with directories named after gist description. 22 | Gists with similar descriptions will be appended with 'duplicate N' (where N is an incremented number), gists without description will simply be called 'Untitled'. 23 | -------------------------------------------------------------------------------- /gist-backup.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var rest = require('restler'); 3 | var request = require('request'); 4 | var fs = require('fs'); 5 | var mkdirp = require('mkdirp'); 6 | var prompt = require('readline-sync'); 7 | 8 | var ghUsername = process.argv[2] || prompt.question('Your GitHub Username? '); 9 | var ghPassword = process.argv[3] || prompt.question('Your GitHub Password? ', { hideEchoBack: true }); 10 | var baseUrl = process.argv[4] || prompt.question('Base URL if enterprise (Press Enter to use GitHub "https://api.github.com" by default)? ') || 'https://api.github.com'; 11 | var savedir = process.argv[5] || prompt.question('Path to back-up dir (will create if not exists)? ') || 'gists'; 12 | 13 | mkdirp(savedir); 14 | 15 | function getGists(page) { 16 | 17 | 'use strict'; 18 | 19 | var options = { 20 | username: ghUsername, 21 | password: ghPassword, 22 | url: baseUrl, 23 | headers: { 24 | 'User-Agent': 'Gists backup' 25 | } 26 | }; 27 | 28 | rest.get(baseUrl + '/gists?per_page=100&page=' + page, options).on('complete', function (data, response) { 29 | if (data instanceof Error) { 30 | console.log('Error:', data.message); 31 | return; 32 | } 33 | var increment = 1; 34 | data.forEach(function (gist) { 35 | var description = (!gist.description) ? 'Untitled' : gist.description 36 | .trim() 37 | .replace(/[^a-zA-Z0-9\s\^\&\'\@\{\}\[\]\,\$\=\!\-\#\(\)\%\.\+\~\_]/g, ''); 38 | 39 | if (description.length > 255) { 40 | description = description.substring(0,255); 41 | } 42 | var dir = savedir + '/' + description; 43 | 44 | try { 45 | fs.statSync(dir); 46 | dir = dir + ' duplicate ' + increment++; 47 | mkdirp.sync(dir, function (error) { 48 | if (error) { 49 | throw error; 50 | } else { 51 | console.log('successfully created ' + dir); 52 | } 53 | 54 | }); 55 | } 56 | catch (err) { 57 | mkdirp.sync(dir, function (error) { 58 | if (error) { 59 | console.log('Error: ' + error); 60 | } else { 61 | console.log('successfully created ' + dir); 62 | } 63 | 64 | }); 65 | } 66 | 67 | for (var file in gist.files) { 68 | if (gist.files.hasOwnProperty(file)) { 69 | var raw_url = gist.files[file].raw_url; 70 | var filename = gist.files[file].filename; 71 | var streamed = fs.createWriteStream(dir + '/' + filename); 72 | request(raw_url).pipe(streamed); 73 | streamed.on('error', function(error){ 74 | console.log('Write error: ' + error); 75 | }); 76 | } 77 | } 78 | }); 79 | if (page === 1 && response.headers.link) { 80 | var links = response.headers.link.split(','); 81 | for (var link in links) { 82 | if (links.hasOwnProperty(link)) { 83 | link = links[link]; 84 | if (link.indexOf('rel="next') > -1) { 85 | var pages = link.match(/[0-9]+/)[0]; 86 | } 87 | } 88 | for (var p = 2; p < pages; p++) { 89 | getGists(p); 90 | } 91 | } 92 | } 93 | }).on('error', function(err) { 94 | console.log('Error: ' + err); 95 | return; 96 | }); 97 | } 98 | 99 | getGists(1); 100 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gist-backup", 3 | "version": "1.0.9", 4 | "description": "This script will download all gist files to local directory", 5 | "main": "gist-backup.js", 6 | "dependencies": { 7 | "mkdirp": "^0.5.0", 8 | "readline-sync": "^1.4.1", 9 | "request": "~2.34.0", 10 | "restler": "~3.2.0" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/sanusart/gists-backup.git" 15 | }, 16 | "keywords": [ 17 | "gist", 18 | "backup" 19 | ], 20 | "author": "Sasha Khamkov", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/sanusart/gists-backup/issues" 24 | }, 25 | "preferGlobal": "true", 26 | "bin": { 27 | "gist-backup": "gist-backup.js" 28 | }, 29 | "homepage": "https://github.com/sanusart/gists-backup" 30 | } 31 | --------------------------------------------------------------------------------