hackernews2nntp
Version:
Post Hacker News Stories & Comments to an NNTP Server
146 lines (120 loc) • 4.15 kB
text/coffeescript
fs = require 'fs'
path = require 'path'
util = require 'util'
meta = require '../package.json'
Crawler2 = require './crawler2'
livedata = require './livedata'
u = require './utils'
program = require 'commander'
Q = require 'q'
request = require 'request'
conf =
url_pattern: 'https://hacker-news.firebaseio.com/v0/item/%d.json'
verbose: 0
conn_per_sec: Crawler2.CONN_PER_SEC
# return a promise
ids_get = (mode, spec) ->
if mode == 'exact'
return Q.fcall ->
id = parseInt(spec[0]) || 0
throw new Error "`#{spec}` must be > 0" if id < 1
[id]
if mode == 'last'
return ids_last spec[0]
if mode == 'top100'
return livedata.top100()
if mode == 'range'
return ids_range spec
if mode == 'several' # debug
return Q.fcall -> spec
else
return Q.fcall -> throw new Error "invalid mode `#{mode}`"
# return a promise
ids_range = (spec) ->
generate = (low, high) ->
max = 100000
if low < 1
throw new Error "#{low} < 1"
else if high < low
throw new Error "#{high} < #{low}"
else if high-low > max
throw new Error "#{high}-#{low} > #{max}"
else
(idx for idx in [low..high])
from = parseInt(spec[0]) || 0
to = parseInt(spec[1]) || 0
if from >= 1 && to == 0
# till the end
livedata.maxitem()
.then (maxitem) ->
generate from, maxitem
else
Q.fcall -> generate from, to
# return a promise
ids_last = (spec) ->
num = parseInt(spec) || 0
if num < 1
return Q.fcall -> throw new Error "`#{spec}` must be >= 1"
livedata.maxitem()
.then (maxitem) ->
result = maxitem - num + 1
if result < 1
throw new Error "#{maxitem}-#{num} < 1"
else
(idx for idx in [result..maxitem])
maxitem_save = (file, id) ->
return unless file
try
fs.writeFileSync file, id
catch e
u.warnx "maxitem saiving failed: #{e.message}"
maxitem_can_be_saved = (mode, stat) ->
return false unless mode?.match /^(last|range)$/
threshold = 80
succ = (stat.downloaded.files / (stat.total()-stat.stale)) * 100
if succ < threshold
u.warnx "#{Math.floor succ}% successful downloaded items is
< that #{threshold}% threshold, maxitem is not saved"
succ >= threshold
exports.main = ->
program
.version meta.version
.usage "[options] mode [spec]
\n Available modes: top100, last <number>, exact <id>, range <from> <to>"
.option '-s, --show-stat', 'Print some statistics after all requests'
.option '-v, --verbose', 'Print HTTP status on stderr (implies -s)', -> conf.verbose++
.option '--maxitem-save <file>', 'Write the highest id number to (for last & range modes only)'
.option '-u, --url-pattern <string>', "Debug. Default: #{conf.url_pattern}", conf.url_pattern
.option '--nokids', "Debug"
.option '--ids-only', "Debug"
.option '--conn-per-sec <digit>', "HTTP request limit (0 == no limit). Default: #{conf.conn_per_sec}", conf.conn_per_sec
.parse process.argv
if program.args.length < 1
program.outputHelp()
process.exit 1
mode = program.args[0]
ids_get mode, program.args[1..-1]
.then (ids) ->
if program.idsOnly
console.error ids
process.exit 0
crawler2 = new Crawler2 program.connPerSec
crawler2.url_pattern = program.urlPattern
unless conf.verbose
crawler2.logger = ->
# empty
crawler2.look4kids = false if program.nokids
process.on 'exit', ->
console.error "\nSTAT: " + util.inspect crawler2.stat.succinct() if conf.verbose || program.showStat
console.error util.inspect crawler2.stat._history, { showHidden: false, depth: null } if conf.verbose >= 2
if program.maxitemSave && maxitem_can_be_saved mode, crawler2.stat
maxitem_save program.maxitemSave, ids[ids.length-1]
crawler2.event.on 'data', (iter_id, data) ->
process.stdout.write "#{data}\n"
crawler2.event.on 'herr', (err) ->
console.error "ERROR: ii=#{err.iter_id}/id=#{err.id}: #{err.message}" if conf.verbose
# start 1st iteration
crawler2.event.emit 'items', crawler2.stat.iter_id_next(), ids
.catch (err) ->
u.errx err.message
.done()