-
Notifications
You must be signed in to change notification settings - Fork 2
/
app.coffee
150 lines (139 loc) · 4.54 KB
/
app.coffee
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
express = require 'express'
FeedParser = require 'feedparser'
request = require 'request'
crypto = require 'crypto'
Rss = require 'rss'
q = require 'q'
Pipe = require './pipe'
# Shortcut for empty callback
noop = -> null
class FeedList
# Refresh feed the evry 30 minutes
REFRESH_INTERVAL: 1000 * 10 * 30
# Check feed for refreshing every 15 minutes
CHECKING_INTERVAL: 1000 * 60 * 15
# Maximum element to display in the RSS
MAX_ITEMS = 20
# Listening port
PORT: process.env.PORT or 3000
# Get all feeds
feeds: require './feeds.json'
constructor: ()->
# Create an express app
@app = do express
# Bind endpoint
@app.get '/feed.json', @json
@app.get /^\/(feed)?|(feed.rss)?$/, @rss
# Fetch every feed once
do @fetchAll
# Set an interval to refersh the feed
setInterval @fetchAll, @CHECKING_INTERVAL
# Then start listening
@app.listen @PORT, =>
console.log 'Listening on http://localhost:%s', @PORT
setTimeout =>
@mustRefresh feed for feed in @feeds
, 2000
dohash: (d)-> crypto.createHash('md5').update(d).digest('hex')
mustRefresh: (feed)=>
# Not fetched yet
!feed.lastUpdate or
# Or fetched a moment ago
Date.now() - feed.lastUpdate.getTime() > @REFRESH_INTERVAL
fetchAll: =>
for feed in @feeds
if @mustRefresh feed
console.log "Fetch feed", feed.url
@fetchFeed feed, @saveFeed
else
console.log "Skip feed", feed.url
fetchFeed: (feed, callback=noop)=>
# Create a new feed parser
feedparser = new FeedParser normalize: yes
# Get the feed
request if feed.timestamp then feed.url + "?" + Date.now() else feed.url
# Wait for the request to send data
.on 'response', (res)->
# Pipe response to feedparse
@pipe feedparser
# Unable to fetch the feed, just ignore it
.on 'error', noop
# When parsing ends
feedparser.on 'readable', -> callback feed, @
saveFeed: (feed, stream)=>
# Save last update time
feed.lastUpdate = new Date
# Create list of items for this feed if any
feed.items = feed.items or {}
# For each feed itemm
while item = do stream.read
# Items must have a date
if date = item.date || item.pubDate
# Create a hash with its link and date
if hash = @dohash [item.link, date].join(" ")
# Compiled the feed item
@buildFeedItem(feed, item).then (compiled)->
# Save the item
feed.items[hash] = compiled
buildFeedItem: (feed, item)=>
deferred = do q.defer
# A list of promises to solve through the pipes
promises = []
# Pipe feed (if any)
if feed.pipe?
# Create a pipe instance for this feed
pipe = new Pipe(feed)
# Put item into the pipe
for method in feed.pipe.split('|')
# The filter may mention arguments
args = method.split ':'
# Gets filter name
filter = args[0]
# Pipe must exist
if pipe[filter]?
# Reject the first arguments (which is the name of the filter)
promise = (filter, args)->
(itemSoFar)->
if itemSoFar
pipe[filter].apply(pipe, args.slice(1, args.length) ) itemSoFar
# Add the pipe promise as a closure to the list
promises.push promise(filter, args)
# Resolve all promises
promises.reduce( q.when, q(item) ).then (item)->
# The item must have been succesfully treated
if item?
# Resolve a new object
deferred.resolve
title: item.title
description: item.description
url: item.link or item.guid
author: item.author
date: item.pubDate or item.date
# The object have been rejected
else do deferred.reject
# Return a promise
deferred.promise
items: =>
# A list of items to display
items = []
# For each feed
for feed in @feeds
# Every single feed item must be prepared
for hash of feed.items
# Add feed's items to the output
items.push feed.items[hash]
# Sort the list of items
items = items.sort (a,b)-> b.date.getTime() - a.date.getTime()
rss: (req, res)=>
# Get items
items = @items()
# Create Rss output
rss = new Rss title: "Journalism++ Feed", site_url: 'http://www.jplusplus.org'
# Add every items to the ouput
rss.item item for item in items.slice(0, MAX_ITEMS)
# Send the final XML
res.send rss.xml(indent: true)
json: (req, res)=>
# Renders items directly
res.json @items()
new FeedList