├── .gitignore ├── bin └── jutebag ├── lib ├── utils │ ├── shell.js │ ├── validator.js │ ├── scraper.js │ ├── markdown.js │ ├── api.js │ └── storage.js ├── jutebag.js ├── add.js ├── init.js ├── archive.js ├── unread.js ├── all.js └── download.js ├── package.json ├── index.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | tests* 4 | -------------------------------------------------------------------------------- /bin/jutebag: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict'; 4 | 5 | require('../'); -------------------------------------------------------------------------------- /lib/utils/shell.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jutebag 3 | * 4 | * A command line interface for Pocket a.k.a. getpocket.com a.k.a. Read It Later 5 | * 6 | * Copyright(c) 2013 André König 7 | * MIT Licensed 8 | * 9 | */ 10 | 11 | /* global exports: true */ 12 | 13 | ;(function () { 14 | "use strict"; 15 | 16 | exports.exit = function (code) { 17 | process.exit(code || 0); // Default value 0 18 | }; 19 | })(); -------------------------------------------------------------------------------- /lib/utils/validator.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jutebag 3 | * 4 | * A command line interface for Pocket a.k.a. getpocket.com a.k.a. Read It Later 5 | * 6 | * Copyright(c) 2013 André König 7 | * MIT Licensed 8 | * 9 | */ 10 | 11 | /* global exports: true */ 12 | 13 | ;(function () { 14 | "use strict"; 15 | 16 | exports.isValidURL = function (url) { 17 | return (/(http|ftp|https):\/\/[\w-]+(\.[\w-]+)+([\w.,@?^=%&:\/~+#-]*[\w@?^=%&\/~+#-])?/).test(url); 18 | }; 19 | })(); -------------------------------------------------------------------------------- /lib/utils/scraper.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jutebag 3 | * 4 | * A command line interface for Pocket a.k.a. getpocket.com a.k.a. Read It Later 5 | * 6 | * Copyright(c) 2013 André König 7 | * MIT Licensed 8 | * 9 | */ 10 | 11 | /* global require: true, modules: true */ 12 | 13 | var readability = require("node-readability"); 14 | 15 | module.exports = (function () { 16 | "use strict"; 17 | 18 | return { 19 | convert : function (url, cb) { 20 | readability.read(url, function (err, article) { 21 | cb(err, { 22 | title: article.getTitle(), 23 | content: article.getContent() 24 | }); 25 | }); 26 | } 27 | }; 28 | })(); -------------------------------------------------------------------------------- /lib/utils/markdown.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jutebag 3 | * 4 | * A command line interface for Pocket a.k.a. getpocket.com a.k.a. Read It Later 5 | * 6 | * Copyright(c) 2013 André König 7 | * MIT Licensed 8 | * 9 | */ 10 | 11 | /* global require: true, modules: true */ 12 | 13 | var fs = require("fs"), 14 | html = require("to-markdown"); 15 | 16 | ;(function () { 17 | "use strict"; 18 | 19 | exports.saveHTML = function (content) { 20 | content = html.toMarkdown(content); 21 | 22 | return { 23 | to : function (filename, cb) { 24 | filename += ".md"; 25 | 26 | fs.writeFile(filename, content, "utf8", function (err) { 27 | cb(err, filename); 28 | }); 29 | } 30 | }; 31 | }; 32 | })(); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jutebag", 3 | "version": "0.4.0", 4 | "description": "A command line interface for Pocket a.k.a. getpocket.com a.k.a. Read It Later", 5 | "main": "index.js", 6 | "bin": { 7 | "jb": "./bin/jutebag" 8 | }, 9 | "preferGlobal": true, 10 | "scripts": { 11 | "test": "echo \"Error: no test specified\" && exit 1" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git@github.com:akoenig/jutebag.git" 16 | }, 17 | "keywords": [ 18 | "pocket", 19 | "cli" 20 | ], 21 | "author": "André König ", 22 | "license": "MIT", 23 | "dependencies": { 24 | "coffee-script" : "1.6.x", 25 | "colors": "0.6.x", 26 | "commander": "git://github.com/akoenig/commander.js.git#master", 27 | "node-pocket": "0.9.x", 28 | "node-readability": "0.0.x", 29 | "slug": "0.2.x", 30 | "to-markdown": "0.0.x", 31 | "underscore": "1.4.x" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jutebag 3 | * 4 | * A command line interface for Pocket a.k.a. getpocket.com a.k.a. Read It Later 5 | * 6 | * Copyright(c) 2013 André König 7 | * MIT Licensed 8 | * 9 | */ 10 | 11 | var cli = require("commander"), 12 | pkg = require("./package.json"); 13 | 14 | ;(function () { 15 | "use strict"; 16 | 17 | var jutebag = require("./lib/jutebag"), 18 | command; 19 | 20 | process.title = pkg.name; 21 | 22 | cli.version(pkg.version); 23 | 24 | // Binding the 'jutebag' commands 25 | // to the command line interface. 26 | for (command in jutebag.commands) { 27 | if (jutebag.commands.hasOwnProperty(command)) { 28 | command = jutebag.commands[command]; 29 | 30 | cli 31 | .command(command.pattern) 32 | .description(command.description) 33 | .and(function (cli) { 34 | if (command.options) { 35 | command.options.forEach(function (option) { 36 | cli.option(option.pattern, option.description); 37 | }); 38 | } 39 | }) 40 | .action(command.exec); 41 | } 42 | } 43 | 44 | // Executing the 'init' command if there is 45 | // no configuation available. 46 | if (!jutebag.configuration.exists()) { 47 | jutebag.commands.init.exec(); 48 | } else { 49 | cli.parse(process.argv); 50 | } 51 | })(); -------------------------------------------------------------------------------- /lib/jutebag.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jutebag 3 | * 4 | * A command line interface for Pocket a.k.a. getpocket.com a.k.a. Read It Later 5 | * 6 | * Copyright(c) 2013 André König 7 | * MIT Licensed 8 | * 9 | */ 10 | 11 | var fs = require("fs"), 12 | path = require("path"), 13 | storage = require("./utils/storage"), 14 | underscore = require("underscore"), 15 | HOME = process.env[(process.platform === "win32") ? "USERPROFILE" : "HOME"] + path.sep + ".jutebag"; 16 | 17 | ;(function () { 18 | "use strict"; 19 | 20 | var jutebag = module.exports, 21 | modules; 22 | 23 | // Init the storage layer. 24 | // Defining the meta data for the creatable stores. 25 | storage.init({ 26 | configuration: HOME + path.sep + "config.json", 27 | data: HOME + path.sep + "data.json" 28 | }); 29 | 30 | jutebag.configuration = storage.getStore("configuration"); 31 | jutebag.configuration.load(); 32 | 33 | jutebag.datastore = storage.getStore("data"); 34 | jutebag.datastore.load(); 35 | 36 | // Add commands. Read all files from the current directory and 37 | // require them if they do not match the current module ('jutebag.js'). 38 | jutebag.commands = {}; 39 | 40 | modules = fs.readdirSync(__dirname); 41 | modules.forEach(function (module) { 42 | var stats = fs.statSync(__dirname + path.sep + module); 43 | 44 | if (!stats.isDirectory() && path.extname(module) === ".js" && __filename.indexOf(module) === -1) { 45 | module = module.replace(path.extname(module), ""); 46 | 47 | jutebag.commands[module] = require('./' + module)(jutebag.configuration, jutebag.datastore); 48 | } 49 | }); 50 | 51 | // Sort the commands 52 | if (underscore.isObject(jutebag.commands)) { 53 | jutebag.commands = underscore.toArray(jutebag.commands); 54 | } 55 | 56 | jutebag.commands = jutebag.commands.sort(function (cmd1, cmd2) { 57 | return cmd1.id - cmd2.id; 58 | }); 59 | })(); -------------------------------------------------------------------------------- /lib/utils/api.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jutebag 3 | * 4 | * A command line interface for Pocket a.k.a. getpocket.com a.k.a. Read It Later 5 | * 6 | * Copyright(c) 2013 André König 7 | * MIT Licensed 8 | * 9 | */ 10 | 11 | require("coffee-script"); 12 | 13 | var Pocket = require("node-pocket"); 14 | 15 | module.exports = (function () { 16 | "use strict"; 17 | 18 | var CONSUMER_KEY = "12478-c9b80470aa8931337c551c64"; 19 | 20 | return { 21 | getRequestToken : function (redirectUrl, cb) { 22 | var pocket = new Pocket(CONSUMER_KEY); 23 | 24 | pocket.getRequestToken({ 25 | url: redirectUrl 26 | }, cb); 27 | }, 28 | getAccessToken : function (requestToken, cb) { 29 | var pocket = new Pocket(CONSUMER_KEY); 30 | 31 | pocket.getAccessToken({ 32 | code: requestToken 33 | }, function (err, data) { 34 | if (err) { 35 | cb(err); 36 | return; 37 | } 38 | 39 | cb(null, data.access_token); 40 | }); 41 | }, 42 | add : function (accessToken, url, tags, cb) { 43 | var pocket = new Pocket(CONSUMER_KEY, accessToken); 44 | 45 | pocket.add({ 46 | url: url, 47 | tags: tags 48 | }, cb); 49 | }, 50 | sync : function (accessToken, elderly, cb) { 51 | var pocket = new Pocket(CONSUMER_KEY, accessToken); 52 | 53 | elderly = elderly || {}; 54 | 55 | pocket.get({ 56 | state: "all", 57 | detailType: "complete", 58 | sort: "newest", 59 | since: elderly.timestamp 60 | }, function (err, items) { 61 | var newbies, 62 | id; 63 | 64 | if (err) { 65 | cb(err); 66 | 67 | return; 68 | } 69 | 70 | for (id in items.list) { 71 | if (items.list.hasOwnProperty(id)) { 72 | items.list[id].unread = (items.list[id].status == 0); 73 | } 74 | } 75 | 76 | newbies = items.list; 77 | newbies.since = items.since; 78 | 79 | cb(null, newbies); 80 | }); 81 | } 82 | }; 83 | })(); -------------------------------------------------------------------------------- /lib/add.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jutebag 3 | * 4 | * A command line interface for Pocket a.k.a. getpocket.com a.k.a. Read It Later 5 | * 6 | * Copyright(c) 2013 André König 7 | * MIT Licensed 8 | * 9 | */ 10 | 11 | var api = require("./utils/api"), 12 | colors = require("colors"), 13 | shell = require("./utils/shell"), 14 | validator = require("./utils/validator"); 15 | 16 | module.exports = function (configuration) { 17 | "use strict"; 18 | 19 | var addCmd = {}; 20 | 21 | Object.defineProperties(addCmd, { 22 | "id": { 23 | enumerable: true, 24 | writable: false, 25 | value: 2 26 | }, 27 | "pattern": { 28 | enumerable: true, 29 | writable: false, 30 | value: "add [url]" 31 | }, 32 | "description": { 33 | enumerable: true, 34 | writable: false, 35 | value: "Adds a website into your pocket" 36 | }, 37 | "options": { 38 | enumerable: true, 39 | writable: false, 40 | value: [ 41 | { 42 | "pattern": '-t, --tags ""', 43 | "description": "The tags you want to add to this URL." 44 | } 45 | ] 46 | }, 47 | "exec": { 48 | enumerable: true, 49 | writable: false, 50 | value: function (url, options) { 51 | var accessToken, 52 | tags; 53 | 54 | accessToken = configuration.get("accessToken"); 55 | 56 | tags = options.tags; 57 | 58 | if (validator.isValidURL(url)) { 59 | api.add(accessToken, url, tags, function (err) { 60 | var messages = { 61 | success: "\n ✓ Saved URL.\n".green, 62 | failure: ("\n ✖ Outsch. Saving URL was not successful: \n\n " + err + "\n").red 63 | }; 64 | 65 | console.log(messages[!(err) ? 'success' : 'failure']); 66 | 67 | shell.exit(+(!!err)); 68 | }); 69 | } else { 70 | console.log("\n ✖ Not a valid URL.\n".red); 71 | 72 | shell.exit(); 73 | } 74 | } 75 | } 76 | }); 77 | 78 | return addCmd; 79 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jutebag 2 | 3 | A command line interface for the incredible [Pocket](http://getpocket.com) a.k.a. getpocket.com a.k.a. Read It Later service. 4 | 5 | ## Installation 6 | 7 | $ npm install -g jutebag 8 | 9 | ## Commands 10 | 11 | ### init 12 | 13 | $ jb init 14 | 15 | The Pocket authentication process. In order to interact with the Pocket service we have to do a handshake first. 16 | 17 | ### add 18 | 19 | Usage: 20 | 21 | $ jb add [url] [--tags or -t] "comma, separated, tags" 22 | 23 | Example: 24 | 25 | $ jb add http://joyent.com/blog/watch-it-wednesdays-where-node-goes-from-here -t "node.js, future" 26 | 27 | Adds the given URL to your reading list (you have to check this URL if you are interested in [Node.js](http://nodejs.org) ;). Please note that defining tags is optional. 28 | 29 | ### all 30 | 31 | Usage: 32 | 33 | $ jb all [--tags or -t] "comma, separated, tags" 34 | 35 | Example: 36 | 37 | $ jb all -t "node.js, future" 38 | 39 | This command lists all your items. It does not matter if it is an archived or an unread item. The tag parameter is (as always) optional. 40 | 41 | ### archive 42 | 43 | Usage: 44 | 45 | $ jb archive [--tags or -t] "comma, separated, tags" 46 | 47 | Example: 48 | 49 | $ jb archive -t "video" 50 | 51 | Lists all your archived items. The tag parameter is (as always) optional. 52 | 53 | 54 | ### unread 55 | 56 | Usage: 57 | 58 | $ jb unread [--tags or -t] "comma, separated, tags" 59 | 60 | Example: 61 | 62 | $ jb unread -t "node.js" # Lists all unread items with the tag "node.js" 63 | 64 | Lists all your unread items. The "tag parameters" are optional. 65 | 66 | ## License 67 | 68 | [MIT License](http://www.opensource.org/licenses/mit-license.php) 69 | 70 | ## Author 71 | 72 | Copyright (c) 2013, [André König](http://iam.andrekoenig.info) 73 | 74 | ## Changelog 75 | 76 | ### v0.3.0 (20130321) 77 | 78 | * New command for listing all items from the archive: jb archive 79 | * New command for listing all items (unread and archived): jb all 80 | 81 | ### v0.2.0 (20130320) 82 | 83 | * New command "jb unread". Lists all unread items. 84 | * Filter function for displaying unread items with a specific tag (e.g. jb unread --tags "node.js") 85 | * New internal command architecture. Sweet! 86 | 87 | ### v0.1.1 (20130318) 88 | 89 | * Functionality for defining tags while adding a website (--tags or -t). 90 | * Exception handling for connection problems. 91 | 92 | ### v0.1.0 (20130318) 93 | 94 | * Functionality for adding websites to your reading list. 95 | * Initial version -------------------------------------------------------------------------------- /lib/init.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jutebag 3 | * 4 | * A command line interface for Pocket a.k.a. getpocket.com a.k.a. Read It Later 5 | * 6 | * Copyright(c) 2013 André König 7 | * MIT Licensed 8 | * 9 | */ 10 | 11 | var api = require("./utils/api"), 12 | colors = require('colors'), 13 | http = require("http"), 14 | shell = require("./utils/shell"); 15 | 16 | module.exports = function (configuration) { 17 | "use strict"; 18 | 19 | var initCmd = {}; 20 | 21 | Object.defineProperties(initCmd, { 22 | "id": { 23 | enumerable: true, 24 | writable: false, 25 | value: 1 26 | }, 27 | "pattern": { 28 | enumerable: true, 29 | writable: false, 30 | value: "init" 31 | }, 32 | "description": { 33 | enumerable: true, 34 | writable: false, 35 | value: "Configuration assistant" 36 | }, 37 | "exec": { 38 | enumerable: true, 39 | writable: false, 40 | value: function () { 41 | var requestToken, 42 | indicator, 43 | host, 44 | port; 45 | 46 | host = "localhost"; 47 | port = 8099; 48 | 49 | http.createServer(function (req, res) { 50 | if (req.url === "/") { 51 | api.getAccessToken(requestToken, function (err, accessToken) { 52 | if (err) { 53 | console.log(("\n ✖ Outsch. Problem while requesting access token: \n\n " + err + "\n").red); 54 | } else { 55 | res.writeHead(200, {'Content-Type': 'text/plain; charset=utf8'}); 56 | res.end("✓ Cool! Now you can use your 'jutebag'. Have fun!"); 57 | 58 | configuration.set("accessToken", accessToken); 59 | configuration.save(); 60 | 61 | console.log("\n\n ✓ Done! Have fun.\n".green); 62 | 63 | clearInterval(indicator); 64 | } 65 | 66 | shell.exit(+(!!(err))); 67 | }); 68 | } 69 | }).listen(port, host); 70 | 71 | api.getRequestToken("http://" + host + ":" + port, function (err, result) { 72 | if (err) { 73 | console.log(("\n ✖ Outsch. Problem while determining the request token: \n\n " + err + "\n").red); 74 | 75 | shell.exit(1); 76 | } 77 | 78 | console.log(("\n In order to interact with the Pocket service you have to visit this URL to obtain an access token.\n\n " + result.redirectUrl + " \n").green); 79 | 80 | process.stdout.write(" Waiting here until you visited the URL ..."); 81 | 82 | indicator = setInterval(function () { 83 | process.stdout.write("."); 84 | }, 500); 85 | 86 | requestToken = result.code; 87 | }); 88 | } 89 | } 90 | }); 91 | 92 | return initCmd; 93 | }; -------------------------------------------------------------------------------- /lib/archive.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jutebag 3 | * 4 | * A command line interface for Pocket a.k.a. getpocket.com a.k.a. Read It Later 5 | * 6 | * Copyright(c) 2013 André König 7 | * MIT Licensed 8 | * 9 | */ 10 | 11 | var api = require("./utils/api"), 12 | colors = require('colors'), 13 | shell = require("./utils/shell"), 14 | underscore = require("underscore"); 15 | 16 | module.exports = function (configuration, datastore) { 17 | "use strict"; 18 | 19 | var archiveCmd = {}; 20 | 21 | Object.defineProperties(archiveCmd, { 22 | "id": { 23 | enumerable: true, 24 | writable: false, 25 | value: 4 26 | }, 27 | "pattern": { 28 | enumerable: true, 29 | writable: false, 30 | value: "archive" 31 | }, 32 | "description": { 33 | enumerable: true, 34 | writable: false, 35 | value: "Lists all you archived items." 36 | }, 37 | "options": { 38 | enumerable: true, 39 | writable: false, 40 | value: [ 41 | { 42 | "pattern": '-t, --tags ""', 43 | "description": "The tags you want to filter your items in the archive for." 44 | } 45 | ] 46 | }, 47 | "exec": { 48 | enumerable: true, 49 | writable: false, 50 | value: function (options) { 51 | var accessToken, 52 | tags, 53 | elderly; 54 | 55 | accessToken = configuration.get("accessToken"); 56 | 57 | if (options.tags) { 58 | tags = options.tags.split(","); 59 | } 60 | 61 | elderly = datastore.get(); 62 | 63 | api.sync(accessToken, elderly, function (err, data) { 64 | var items, 65 | output = "\n"; 66 | 67 | if (err) { 68 | console.log(("\n ✖ Outsch. Getting your archived items was not successful: \n\n " + err + "\n").red); 69 | } else { 70 | datastore.set(data); 71 | datastore.save(); 72 | 73 | items = datastore 74 | .where("unread") 75 | .is(false) 76 | .and("tags") 77 | .matches(tags) 78 | .end(); 79 | 80 | underscore.toArray(items).forEach(function (item) { 81 | var TAB, 82 | title, 83 | tags; 84 | 85 | TAB = " "; 86 | title = item.resolved_title ? " ★ " + item.resolved_title : ' ⚠ No title'; 87 | tags = underscore.pluck(underscore.toArray(item.tags), "tag").join(", "); 88 | 89 | // Constructing the output 90 | output += "\n"; 91 | output += title.green.bold + "\n\n"; 92 | output += TAB + item.resolved_url + "\n\n"; 93 | output += TAB + ("ID: " + item.item_id + ((tags) ? " - Tags: " + tags : "") + "\n\n").grey; 94 | output += "\n"; 95 | }); 96 | 97 | console.log(output); 98 | } 99 | 100 | shell.exit(+(!!(err))); 101 | }); 102 | } 103 | } 104 | }); 105 | 106 | return archiveCmd; 107 | }; -------------------------------------------------------------------------------- /lib/unread.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jutebag 3 | * 4 | * A command line interface for Pocket a.k.a. getpocket.com a.k.a. Read It Later 5 | * 6 | * Copyright(c) 2013 André König 7 | * MIT Licensed 8 | * 9 | */ 10 | 11 | var api = require("./utils/api"), 12 | colors = require('colors'), 13 | shell = require("./utils/shell"), 14 | underscore = require("underscore"); 15 | 16 | module.exports = function (configuration, datastore) { 17 | "use strict"; 18 | 19 | var unreadCmd = {}; 20 | 21 | Object.defineProperties(unreadCmd, { 22 | "id": { 23 | enumerable: true, 24 | writable: false, 25 | value: 5 26 | }, 27 | "pattern": { 28 | enumerable: true, 29 | writable: false, 30 | value: "unread" 31 | }, 32 | "description": { 33 | enumerable: true, 34 | writable: false, 35 | value: "Displays a nice list of all your unread websites." 36 | }, 37 | "options": { 38 | enumerable: true, 39 | writable: false, 40 | value: [ 41 | { 42 | "pattern": '-t, --tags ""', 43 | "description": "The tags you want to filter your items in the unread list for." 44 | } 45 | ] 46 | }, 47 | "exec": { 48 | enumerable: true, 49 | writable: false, 50 | value: function (options) { 51 | var accessToken, 52 | tags, 53 | elderly; 54 | 55 | accessToken = configuration.get("accessToken"); 56 | 57 | if (options.tags) { 58 | tags = options.tags.split(","); 59 | } 60 | 61 | elderly = datastore.get(); 62 | 63 | api.sync(accessToken, elderly, function (err, data) { 64 | var items, 65 | output = "\n"; 66 | 67 | if (err) { 68 | console.log(("\n ✖ Outsch. Getting your unread items was not successful: \n\n " + err + "\n").red); 69 | } else { 70 | datastore.set(data); 71 | datastore.save(); 72 | 73 | items = datastore 74 | .where("unread") 75 | .is(true) 76 | .and("tags") 77 | .matches(tags) 78 | .end(); 79 | 80 | underscore.toArray(items).forEach(function (item) { 81 | var TAB, 82 | title, 83 | tags; 84 | 85 | TAB = " "; 86 | title = item.resolved_title ? " ★ " + item.resolved_title : ' ⚠ No title'; 87 | tags = underscore.pluck(underscore.toArray(item.tags), "tag").join(", "); 88 | 89 | // Constructing the output 90 | output += "\n"; 91 | output += title.green.bold + "\n\n"; 92 | output += TAB + item.resolved_url + "\n\n"; 93 | output += TAB + ("ID: " + item.item_id + ((tags) ? " - Tags: " + tags : "") + "\n\n").grey; 94 | output += "\n"; 95 | }); 96 | 97 | console.log(output); 98 | } 99 | 100 | shell.exit(+(!!(err))); 101 | }); 102 | } 103 | } 104 | }); 105 | 106 | return unreadCmd; 107 | }; -------------------------------------------------------------------------------- /lib/all.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jutebag 3 | * 4 | * A command line interface for Pocket a.k.a. getpocket.com a.k.a. Read It Later 5 | * 6 | * Copyright(c) 2013 André König 7 | * MIT Licensed 8 | * 9 | */ 10 | 11 | var api = require("./utils/api"), 12 | colors = require('colors'), 13 | shell = require("./utils/shell"), 14 | underscore = require("underscore"); 15 | 16 | module.exports = function (configuration, datastore) { 17 | "use strict"; 18 | 19 | var allCmd = {}; 20 | 21 | Object.defineProperties(allCmd, { 22 | "id": { 23 | enumerable: true, 24 | writable: false, 25 | value: 3 26 | }, 27 | "pattern": { 28 | enumerable: true, 29 | writable: false, 30 | value: "all" 31 | }, 32 | "description": { 33 | enumerable: true, 34 | writable: false, 35 | value: "Returns a list with all your items in your pocket (unread AND archived)." 36 | }, 37 | "options": { 38 | enumerable: true, 39 | writable: false, 40 | value: [ 41 | { 42 | "pattern": '-t, --tags ""', 43 | "description": "Displays only the items that have these tags." 44 | } 45 | ] 46 | }, 47 | "exec": { 48 | enumerable: true, 49 | writable: false, 50 | value: function (options) { 51 | var accessToken, 52 | tags, 53 | elderly; 54 | 55 | accessToken = configuration.get("accessToken"); 56 | 57 | if (options.tags) { 58 | tags = options.tags.split(","); 59 | } 60 | 61 | elderly = datastore.get(); 62 | 63 | api.sync(accessToken, elderly, function (err, data) { 64 | var items, 65 | output = "\n"; 66 | 67 | if (err) { 68 | console.log(("\n ✖ Outsch. Getting your items was not successful: \n\n " + err + "\n").red); 69 | } else { 70 | datastore.set(data); 71 | datastore.save(); 72 | 73 | items = datastore 74 | .where("tags") 75 | .matches(tags) 76 | .end(); 77 | 78 | underscore.toArray(items).forEach(function (item) { 79 | var TAB, 80 | title, 81 | tags; 82 | 83 | if (item.resolved_url) { 84 | TAB = " "; 85 | title = item.resolved_title ? " ★ " + item.resolved_title : ' ⚠ No title'; 86 | tags = underscore.pluck(underscore.toArray(item.tags), "tag").join(", "); 87 | 88 | // Constructing the output 89 | output += "\n"; 90 | output += title.green.bold + "\n\n"; 91 | output += TAB + item.resolved_url + "\n\n"; 92 | output += TAB + ("ID: " + item.item_id + ((tags) ? " - Tags: " + tags : "") + "\n\n").grey; 93 | output += "\n"; 94 | } 95 | }); 96 | 97 | console.log(output); 98 | } 99 | 100 | shell.exit(+(!!(err))); 101 | }); 102 | } 103 | } 104 | }); 105 | 106 | return allCmd; 107 | }; -------------------------------------------------------------------------------- /lib/download.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jutebag 3 | * 4 | * A command line interface for Pocket a.k.a. getpocket.com a.k.a. Read It Later 5 | * 6 | * Copyright(c) 2013 André König 7 | * MIT Licensed 8 | * 9 | */ 10 | 11 | var api = require("./utils/api"), 12 | colors = require("colors"), 13 | markdown = require("./utils/markdown"), 14 | path = require("path"), 15 | scraper = require("./utils/scraper"), 16 | shell = require("./utils/shell"), 17 | slug = require("slug"), 18 | validator = require("./utils/validator"); 19 | 20 | module.exports = function (configuration, datastore) { 21 | "use strict"; 22 | 23 | var downloadCmd = {}; 24 | 25 | Object.defineProperties(downloadCmd, { 26 | "id": { 27 | enumerable: true, 28 | writable: false, 29 | value: 6 30 | }, 31 | "pattern": { 32 | enumerable: true, 33 | writable: false, 34 | value: "download [id]" 35 | }, 36 | "description": { 37 | enumerable: true, 38 | writable: false, 39 | value: "Downloads a website from your Pocket and saves the content as a Markdown file." 40 | }, 41 | "exec": { 42 | enumerable: true, 43 | writable: false, 44 | value: function (id) { 45 | var accessToken, 46 | url, 47 | elderly; 48 | 49 | function scrape (url) { 50 | var filename; 51 | 52 | console.log("\n Started scraping: " + url); 53 | 54 | scraper.convert(url, function (err, article) { 55 | // Creating the filename. Current working directory + slug from article title 56 | filename = process.cwd() + path.sep + slug(article.title).toLowerCase(); 57 | 58 | if (err) { 59 | console.log(("\n ✖ Outsch. Scraping the website was not successful: \n\n " + err + "\n").red); 60 | 61 | shell.exit(1); 62 | } else { 63 | markdown.saveHTML(article.content).to(filename, function (error, filename) { 64 | if (error) { 65 | console.log(("\n ✖ Outsch. Error while saving the scraped website: \n\n " + error + "\n").red); 66 | } else { 67 | console.log(("\n ✓ Saved website as Markdown file: " + filename + "\n").green); 68 | } 69 | 70 | shell.exit(+(!!error)); 71 | }); 72 | } 73 | }); 74 | } 75 | 76 | // URL download feature. Beside the possibility 77 | // to download a website by an item from the Pocket 78 | // it is also possible to download a whole website 79 | // by it's url. 80 | if (validator.isValidURL(id)) { 81 | url = id; 82 | 83 | scrape(url); 84 | } else { 85 | (function () { 86 | var accessToken, 87 | elderly; 88 | 89 | accessToken = configuration.get("accessToken"); 90 | 91 | elderly = datastore.get(); 92 | 93 | api.sync(accessToken, elderly, function (err, data) { 94 | if (err) { 95 | console.log(("\n ✖ Outsch. Getting the item with the ID " + id + " was not successful: \n\n " + err + "\n").red); 96 | 97 | shell.exit(1); 98 | } else { 99 | url = datastore 100 | .where("item_id") 101 | .is(id) 102 | .end(); 103 | 104 | url = (url[id] || {}).resolved_url; 105 | 106 | if (!url) { 107 | console.log(("\n ✖ There is no item with the ID " + id + " in your Pocket.\n").red); 108 | 109 | shell.exit(1); 110 | } else { 111 | scrape(url); 112 | } 113 | } 114 | }); 115 | })(); 116 | } 117 | } 118 | } 119 | }); 120 | 121 | return downloadCmd; 122 | }; -------------------------------------------------------------------------------- /lib/utils/storage.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jutebag 3 | * 4 | * A command line interface for Pocket a.k.a. getpocket.com a.k.a. Read It Later 5 | * 6 | * Copyright(c) 2013 André König 7 | * MIT Licensed 8 | * 9 | */ 10 | 11 | /* global require: true, modules: true */ 12 | 13 | var underscore = require("underscore"); 14 | 15 | ;(function () { 16 | "use strict"; 17 | 18 | var fs = require("fs"), 19 | path = require("path"); 20 | 21 | function _extend(obj, target) { 22 | var o; 23 | 24 | for (o in obj) { 25 | target[o] = obj[o]; 26 | } 27 | 28 | return target; 29 | } 30 | 31 | function Store (path, data) { 32 | this.path = path; 33 | this.data = data; 34 | } 35 | 36 | Store.prototype = { 37 | 38 | exists : function () { 39 | return this.data; 40 | }, 41 | 42 | load : function () { 43 | var config; 44 | 45 | try { 46 | this.data = require(this.path); 47 | } catch (e) {} 48 | }, 49 | 50 | save : function () { 51 | // Check if the directory in which the store 52 | // should be saved exists. If not create. 53 | var directory = path.dirname(this.path), 54 | exists = fs.existsSync(directory); 55 | 56 | if (!exists) { 57 | fs.mkdirSync(directory); 58 | } 59 | 60 | fs.writeFileSync(this.path, JSON.stringify(this.data)); 61 | }, 62 | 63 | get : function (attr) { 64 | if (!attr) { 65 | return _extend(this.data, {}); 66 | } else { 67 | return this.data[attr]; 68 | } 69 | }, 70 | 71 | set : function (attr, value) { 72 | this.data = this.data || {}; 73 | 74 | // If the attr parameter is an object, we have 75 | // to replace the complete "data" property with 76 | // the new data (e.g. store.set({foo: "bar"})); ) 77 | if (typeof attr === "object") { 78 | this.data = _extend(attr, {}); 79 | } else if (value) { 80 | if (typeof value === "object") { 81 | this.data[attr] = _extend(value, {}); 82 | } else { 83 | this.data[attr] = value; 84 | } 85 | } 86 | }, 87 | 88 | where : function (attr) { 89 | var that = this, 90 | results = this.data; 91 | 92 | return { 93 | is : function (value) { 94 | var id, 95 | filteredResults = {}; 96 | 97 | for (id in results) { 98 | if (results.hasOwnProperty(id)) { 99 | // 1. Layer properties 100 | // 101 | // e.g.: {id: value} 102 | // 103 | if (id === attr && results[id] === value) { 104 | filteredResults[id] = results[id]; 105 | // 2. Layer properties (if value is also an object) 106 | // 107 | // e.g.: {id: {attr: value}} 108 | // 109 | } else if (results[id][attr] !== undefined && results[id][attr] === value) { 110 | filteredResults[id] = results[id]; 111 | } 112 | } 113 | } 114 | 115 | results = filteredResults; 116 | 117 | return this; 118 | }, 119 | and : function (prop) { 120 | attr = prop; // Changed the 'attr' param in where function. 121 | 122 | return this; 123 | }, 124 | matches : function (list) { 125 | var id, 126 | filterables, 127 | filteredResults = {}; 128 | 129 | list = underscore.toArray(list); 130 | 131 | if (list.length > 0) { 132 | list = list.sort(function (a, b) { 133 | return a - b; 134 | }); 135 | list = list.join(""); 136 | list = list.replace(" ", ""); 137 | 138 | for (id in results) { 139 | filterables = undefined; 140 | 141 | if (results.hasOwnProperty(id)) { 142 | // 1. Layer properties 143 | // 144 | // e.g.: {id: value} 145 | // 146 | if (id === attr && results[id]) { 147 | filterables = results[id]; 148 | // 2. Layer properties (if value is also an object) 149 | // 150 | // e.g.: {id: {attr: value}} 151 | // 152 | } else if (results[id][attr]) { 153 | filterables = results[id][attr]; 154 | } 155 | 156 | if (filterables) { 157 | if (underscore.isObject(filterables)) { 158 | filterables = underscore.keys(filterables); 159 | } 160 | 161 | filterables = filterables.sort(function (a, b) { 162 | return a - b; 163 | }); 164 | 165 | filterables = filterables.join(""); 166 | 167 | if (filterables === list) { 168 | filteredResults[id] = results[id]; 169 | } 170 | } 171 | } 172 | } 173 | 174 | results = filteredResults; 175 | } 176 | 177 | return this; 178 | }, 179 | end : function () { 180 | return results; 181 | } 182 | }; 183 | } 184 | }; 185 | 186 | module.exports = (function () { 187 | var stores = {}; 188 | 189 | return { 190 | init : function (meta) { 191 | var store; 192 | 193 | for (store in meta) { 194 | if (meta.hasOwnProperty(store)) { 195 | stores[store] = new Store(meta[store]); 196 | } 197 | } 198 | }, 199 | getStore : function (type) { 200 | return stores[type]; 201 | } 202 | }; 203 | })(); 204 | })(); --------------------------------------------------------------------------------