hackernews2nntp
Version:
Post Hacker News Stories & Comments to an NNTP Server
161 lines (122 loc) • 3.86 kB
text/coffeescript
readline = require 'readline'
path = require 'path'
{spawn} = require 'child_process'
program = require 'commander'
shellquote = require 'shell-quote'
which = require 'which'
meta = require '../package.json'
Message = require './message'
mbox = require './mbox'
conf =
verbose: false
format: 'rnews'
cmd: ['sudo', 'rnews', '-N']
destination: 'printer'
ext_deps: []
template_dir: null
warnx = (msg) ->
console.error "#{path.basename process.argv[1]} warning: #{msg}"
errx = (msg) ->
console.error "#{path.basename process.argv[1]} error: #{msg}"
process.exit 1
log = (msg) ->
console.error "#{path.basename process.argv[1]}: #{msg}" if conf.verbose
dispatcher = {
printer: (id, mail) ->
log "#{id}: writing"
process.stdout.write "#{mail}\n"
fork: (id, mail) ->
text = ''
stderr = ''
log "#{id}: writing to `#{conf.cmd.join ' '}`"
cmd = spawn conf.cmd[0], conf.cmd[1..-1]
cmd.on 'error', (err) ->
warnx "#{id}: cmd failed: #{err.message}"
cmd.stdout.on 'data', (data) -> text += data
cmd.stderr.on 'data', (data) -> stderr += data
cmd.on 'close', (code) ->
log "#{id}: cmd exit code: #{code}"
cmd.stdin.write "#{mail}\n"
cmd.stdin.end()
}
wrap_mail = (message) ->
return unless message
sendoff = dispatcher[conf.destination]
message.render()
.then (mail) ->
if conf.format == 'rnews'
sendoff message.json_data.id, "#! rnews #{Buffer.byteLength(mail) + 1}\n#{mail}"
else if conf.format == 'mbox'
sendoff message.json_data.id, "#{mbox.prefix message.json_data}\n#{mbox.escape mail}"
else
sendoff message.json_data.id, mail
.done()
message_create = (json, parts = []) ->
try
message = new Message json, parts
message.opt.alt_dir = conf.template_dir
catch err
if err.message.match /invalid json input/
warnx "json validation failed"
return null
else
throw err
message
ext_deps = -> ['w3m', conf.cmd[0]]
ext_deps_check = ->
for idx in ext_deps()
try
which.sync idx
catch e
errx "#{idx} not found in PATH"
exports.main = ->
program
.version meta.version
.option '-v, --verbose', 'Print debug info to stderr'
.option '--fork', "Don't print the result to stdout, but feed an external program w/ input"
.option '--cmd <string>', "Custom external command instead of `#{conf.cmd.join ' '}`"
.option '-f, --format <format>', 'Convert to rnews (default), mbox or plain'
.option '--template-dir <name>', 'A directory w/ custom templates'
.parse process.argv
conf.verbose = program.verbose if program.verbose
conf.destination = 'fork' if program.fork
conf.format = program.format if program.format
conf.cmd = shellquote.parse program.cmd if program.cmd
conf.template_dir = program.templateDir if program.templateDir
ext_deps_check()
rl = readline.createInterface {
input: process.stdin
output: process.stdout
terminal: false
}
poll = null
poll_read = 0
pollopts = []
rl.on 'line', (line) ->
try
json = JSON.parse line
catch err
warnx "line not parsed: #{err.message}"
return
if !poll && json.type == 'poll'
# read next several lines to construct a poll
poll = json
if json.parts?.length < 1
warnx "invalid poll w/ id=#{json.id}"
poll = null
return
poll_read = json.parts.length
else if poll
--poll_read
pollopts.push json
if poll_read == 0 # we're done collecting
message = message_create poll, pollopts
wrap_mail message
poll = null
pollopts = []
else
message = message_create json
wrap_mail message
process.stdout.on 'error', (err) ->
warnx "program you pipe in stopped reading input" if err.code == "EPIPE"
errx err