├── .gitignore ├── COPYING.text ├── README.markdown ├── package.json └── pin-cushion /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /COPYING.text: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) 2015, Elliott Cable 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any Purpose with or 6 | without fee is hereby granted, provided that the above Copyright notice and this permission 7 | notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS 10 | SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL 11 | THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY 12 | DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF 13 | CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE 14 | OR PERFORMANCE OF THIS SOFTWARE. 15 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | `pin-cushion` Maintenance status: Last active in 2018Versions & releasespin-cushion on the NPM registryOpen-source licensing detailsChat on FreenodeTwitter followers 2 | ============= 3 | A simple command-line [Pinboard.in][] client: 4 | 5 | pin-cushion [verb] [arguments] 6 | pin-cushion posts/recent 7 | pin-cushion posts/suggest --url "http://www.ponylang.org" 8 | 9 | pb-rename() { 10 | pin-cushion tags/rename --old "$1" --new "$2" 11 | } 12 | 13 | You get the idea. To use it, you must first record [your authentication token][auth] for the API: 14 | 15 | npm install -g pin-cushion 16 | pin-cushion --auth elliottcable:DEADBEEF1234567890 17 | 18 | This only provides abstracted access to the Pinboard API as defined on their site: 19 | > ### 20 | 21 | Any Pinboard API method described there may be passed as the `verb`; and all described arguments are 22 | accepted as command-line `flags`. These are not stored in this library; as your command-line 23 | instructions are simply converted directly to API calls; so this tool probably doesn't need much in 24 | the form of maintenance. `:P` 25 | 26 | [Pinboard.in]: 28 | 29 | ### Piping and JSON output 30 | If not explicitly passed a `--format` parameter, then `pin-cushion` will spit out a formatted 31 | object-description of the response, intended for human consumption. If a format is explicitly 32 | provided, then the response from the server will be printed, unmodified; this is particularly useful 33 | with the [`jq`](https://stedolan.github.io/jq/) command-line JSON manipulation tool: 34 | 35 | pin-cushion posts/recent --format=json | jq # Simply pretty-print 36 | pin-cushion posts/recent --format=json | jq '.posts[] | .href' # Extract URLs of recent pins 37 | 38 | This obviously lends itself to constructing complex shell pipes. Personally, I suggest aliasing 39 | this: 40 | 41 | pc() { pin-cushion "$1" --format=json "$@" ;} 42 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { "author" : "ELLIOTTCABLE (http://ell.io/tt)" 2 | 3 | , "name" : "pin-cushion" 4 | , "version" : "1.0.0" 5 | , "license" : "ISC" 6 | 7 | , "description" : "Simple, maintained command-line interface to the Pinboard.in API." 8 | , "repository" : "ELLIOTTCABLE/pin-cushion" 9 | 10 | , "bin" : "pin-cushion" 11 | 12 | , "dependencies" : { 13 | "pinboard.js" : "^1.0.2" 14 | , "monowrap" : "^1.0.4" 15 | , "lodash" : "^4.0.0" 16 | , "minimist" : "^1.2.0" 17 | , "configstore" : "^1.4.0" 18 | , "debug" : "^2.2.0" 19 | 20 | , "js-yaml" : "^3.5.2" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /pin-cushion: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var Pinboard = require('pinboard.js') 3 | , minimist = require('minimist') 4 | , lo = require('lodash') 5 | , YAML = require('js-yaml') 6 | , wrap = require('monowrap') 7 | 8 | , Configstore = require('configstore') 9 | , node_debug = require('debug') 10 | , package = require('./package.json') 11 | 12 | var config = new Configstore(package.name) 13 | , flags = require('minimist')(process.argv.slice(2), { 14 | '--' : true 15 | , 'alias' : { 'D': 'debug' } 16 | , 'boolean': [ 'debug' ] 17 | }) 18 | 19 | var args = flags._.concat(flags['--']) 20 | , verb = lo.camelCase(args[0]) 21 | 22 | , debugging = flags.debug 23 | 24 | , noArgs, API, dumpYAML 25 | 26 | if (debugging) { 27 | node_debug.enable('pin-cushion') 28 | delete flags.debug 29 | } 30 | 31 | debug = node_debug('pin-cushion') 32 | debug('-- verb: %j', verb) 33 | debug('-- args: %j', args) 34 | debug('-- flags: %j', flags) 35 | 36 | if (flags.auth != null) { 37 | if (!lo.isString(flags.auth) || !lo.includes(flags.auth, ':')) { 38 | console.error("Usage: %s --auth 'username:DEADBEEF'", require('path').basename(process.argv[1])) 39 | console.error(" (API token obtained at .)") 40 | process.exit(10) 41 | } 42 | var auth = flags.auth.split(':') 43 | config.set('username', auth[0]) 44 | config.set('token', auth[1]) 45 | console.error('-- Authentication information recorded!') 46 | process.exit(0) 47 | } 48 | 49 | function usage(API){ 50 | console.error("Usage: %s [verb] [arguments]", require('path').basename(process.argv[1])) 51 | console.error(" %s --auth 'username:DEADBEEF'", require('path').basename(process.argv[1])) 52 | 53 | if (null != API) { 54 | var verbs = Object.keys(API.__proto__).map(function(key){ 55 | return lo.kebabCase(key).split('-').join('/') }) 56 | 57 | console.error(wrap( 58 | "Verbs: " + verbs.join(', ') 59 | , { top: 1, width: process.stdout.columns } )) 60 | } 61 | 62 | console.error(wrap( 63 | "API token from ; more usage examples at " + 64 | "; and full API documentation at " + 65 | "!" 66 | , { top: 1, width: process.stdout.columns } )) 67 | process.exit(10) 68 | } 69 | 70 | if (flags.format == null) { 71 | dumpYAML = true 72 | flags.format = 'json' 73 | } 74 | 75 | 76 | // --- ---- /!\ ---- ---- 77 | 78 | try { 79 | API = new Pinboard({ auth: { 80 | type: 'token', 81 | username: config.get('username'), 82 | token: config.get('token') } }) 83 | } catch (e) { 84 | if (e.message === "`params.auth.username` is required for authentication. Please provide it.") { 85 | if (args.length < 1) usage() 86 | 87 | console.error("-- Error: You must authenticate with `--auth` first!") 88 | process.exit(11) 89 | } 90 | } 91 | 92 | if (args.length < 1) usage(API) 93 | 94 | if (null == API[verb]) { 95 | console.error("-- Error: '%s' verb is unknown to `pinboard.js`!", verb) 96 | process.exit(12) 97 | } 98 | 99 | API[verb](flags, function callback(err, response, body){ 100 | debug('Response!') 101 | debug('-- err: %j', err) 102 | debug('-- response: %j', response) 103 | debug('-- body: %s', body) 104 | 105 | if (err != null) { 106 | console.error('-- Error: %s', err) 107 | process.exit(9) 108 | } 109 | 110 | if (response.statusCode < 200 || response.statusCode > 299) { 111 | console.error('API error: %s', body) 112 | process.exit(Math.floor(response.statusCode / 100)) 113 | } 114 | 115 | if (dumpYAML) 116 | body = YAML.safeDump(JSON.parse(body), { 117 | lineWidth: (process.stdout.columns || 80) - 2 118 | }) 119 | 120 | console.log(body) 121 | }) 122 | --------------------------------------------------------------------------------