hackernews2nntp
Version:
Post Hacker News Stories & Comments to an NNTP Server
153 lines (125 loc) • 3.98 kB
text/coffeescript
fs = require 'fs'
path = require 'path'
crypto = require 'crypto'
os = require 'os'
{spawn} = require 'child_process'
Mustache = require 'mustache'
json_schema = require('jjv')()
Q = require 'q'
mimelib = require 'mimelib'
class Message
@NEWSGROUP_DEFAULT = 'news.ycombinator'
@SCHEMA = JSON.parse fs.readFileSync(path.join __dirname, 'schema.json').toString()
@TemplateGet = (name, alt_dir = '') ->
dirs = [path.resolve(__dirname, '..', 'template')]
dirs.unshift alt_dir if alt_dir
result = null
err = null
for idx in dirs
try
result = fs.readFileSync(path.join idx, "#{name}.txt").toString()
err = null
break
catch e
err = e
return result unless err
throw err
constructor: (@json_data, @parts = []) ->
@opt = {}
# sync validation
for idx in @parts.concat(@json_data)
err = json_schema.validate Message.SCHEMA, idx
throw new Error('invalid json input: ' + JSON.stringify err) if err
id: ->
"<#{@json_data.id}@news.ycombinator.com>"
parent_id: ->
return '' unless @json_data.parent
"<#{@json_data.parent}@news.ycombinator.com>"
# return date in rfc2822 format
date: ->
# I assume @json_data.time comes in UTC
d = new Date(@json_data.time * 1000)
d.toUTCString()
# return an obj
#
# {
# global: 123
# text: 456
# html: 789
# }
content_id: ->
r = "#{@json_data.time}_#{crypto.pseudoRandomBytes(8).toString('hex')}@#{os.hostname()}"
{
global: "<#{r}>"
text: "<text_#{r}>"
html: "<html_#{r}>"
}
headers: ->
{
newsgroup: Message.NEWSGROUP_DEFAULT
message_id: @id()
boundary: crypto.pseudoRandomBytes(16).toString('hex')
parent_msgid: @parent_id()
content_id: @content_id()
permalink: "https://news.ycombinator.com/item?id=#{@json_data.id}"
from: "#{@json_data.by} <#{@json_data.by}@example.com>"
date: @date()
path: os.hostname()
profile: "https://news.ycombinator.com/user?id=#{@json_data.by}"
subject: if !@json_data.title then '' else mimelib.encodeMimeWord @json_data.title
}
# return a promise
@HTML_filter: (html) ->
deferred = Q.defer()
text = ''
stderr = ''
if !html? || html == ""
deferred.resolve text
return deferred.promise
w3m = spawn 'w3m', ['-T', 'text/html', '-dump', '-I', 'UTF-8',
'-O', 'UTF-8', '-cols', '72', '-no-graph']
w3m.on 'error', (err) ->
deferred.reject(new Error "w3m exec failed: #{err.message}")
w3m.stdout.on 'data', (data) -> text += data
w3m.stderr.on 'data', (data) -> stderr += data
w3m.on 'close', (code) ->
if code == 0
deferred.resolve text
else
deferred.reject(new Error "w3m failed w/ exit code #{code}\nw3m stderr: #{stderr}")
w3m.stdin.write html
w3m.stdin.end()
deferred.promise
polparts_collect: ->
deferred = Q.defer()
if @parts.length == 0
deferred.resolve []
return deferred.promise
parts = []
idx = 0 # we cannot use idx value from 'for item,idx in @parts'
for item in @parts
Message.HTML_filter(item.text)
.then (r) =>
++idx
parts.push r.trim()
deferred.resolve parts if idx == @parts.length
deferred.promise
# return a promise
render: (_tdd_hash) ->
json = JSON.parse JSON.stringify(@json_data) # omglol
json.mail = @headers()
if @json_data.type == 'poll'
@polparts_collect()
.then (r) =>
json.mail.polparts = r
_tdd_hash.polparts = r if _tdd_hash
Mustache.render Message.TemplateGet(json.type, @opt.alt_dir), json
else
Message.HTML_filter @json_data.text
.then (r) =>
json.mail.body_text = r.trim()
_tdd_hash.body_text = r.trim() if _tdd_hash
Mustache.render Message.TemplateGet(json.type, @opt.alt_dir), json
toString: ->
@render()
module.exports = Message