worker-planet/index.js

177 lines
4.5 KiB
JavaScript

import Parser from 'rss-parser'
import { Feed } from 'feed'
import Handlebars from 'handlebars/runtime'
import template from './templates/list_posts.precompiled'
addEventListener('scheduled', event => {
event.waitUntil(handleScheduled(event))
})
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request))
})
/**
* Deliver aggregated content according to the formats requested
* @param {Request} request
*/
async function handleRequest(request) {
const cacheUrl = new URL(request.url)
const cacheKey = new Request(cacheUrl.toString(), request)
const cache = caches.default
const cacheMaxAge = CACHE_MAX_AGE || 3600
let response = await cache.match(cacheKey)
if (response) return response
const path = new URL(request.url).pathname
if (path === '/') {
let content = await WORKER_PLANET_STORE.get('html')
response = new Response(content, {
headers: {
'content-type': 'text/html;charset=UTF-8',
'Cache-Control': `max-age=${cacheMaxAge}`,
},
})
} else if (path === '/rss') {
let content = await WORKER_PLANET_STORE.get('rss')
response = new Response(content, {
headers: {
'content-type': 'application/rss+xml',
'Cache-Control': 'max-age=3600',
},
})
// } else if (path === '/atom') {
// let content = await WORKER_PLANET_STORE.get('atom')
// response = new Response(content)
} else {
return new Response('', { status: 404 })
}
await cache.put(cacheKey, response.clone())
return response
}
/**
* Fetch all source feeds and generate the aggregated content
*
* TODO implement a faster way to fetch several sources
*
* @param {*} event
*/
async function handleScheduled(event) {
let feeds = FEEDS.split(',')
let content = []
let sources = []
let items
for (let url of feeds) {
try {
items = await fetchAndHydrate(url)
sources.push({ name: items[0].source_title, link: items[0].source_link })
} catch (error) {
console.log(`Failed to fetch ${url}`)
console.log(error)
}
content.push(...items)
}
//sort all the elements chronologically (recent first)
content.sort((a, b) => {
let aDate = new Date(a.isoDate)
let bDate = new Date(b.isoDate)
if (aDate < bDate) {
return 1
} else if (aDate === bDate) {
return 0
} else {
return -1
}
})
if (content.length > MAX_SIZE) {
content = content.slice(0, MAX_SIZE)
}
// Generate feed
let feed = createFeed(content)
let html = createHTML(content, sources)
// Store
await WORKER_PLANET_STORE.put('rss', feed.rss2())
//await WORKER_PLANET_STORE.put('atom', feed.atom1())
await WORKER_PLANET_STORE.put('html', html)
}
/**
* Take a feed URL, fetch all items and attach source information
* @param {Array} feeds
*/
async function fetchAndHydrate(feed) {
console.log(`[fetchAndHydrate] start to fetch feed: ${feed}`)
let resp = await fetch(feed)
console.log(`[fetchAndHydrate] response: ${resp.status}`)
let parser = new Parser()
let content = await resp.text()
let contentFeed = await parser.parseString(content)
for (let item of contentFeed.items) {
item.source_title = contentFeed.title
item.source_link = contentFeed.link
if ('content:encoded' in item) {
item.content = item['content:encoded']
}
}
console.log(
`[fetchAndHydrate] Finished fetch feed: ${feed}. ${contentFeed.items.length} items gathered`,
)
return contentFeed.items
}
/**
* Builds a feed object from the provided items
* @param {Array} items
*/
function createFeed(items) {
console.log(`[createFeed] start building the aggregated feed`)
const feed = new Feed({
title: TITLE,
description: DESCRIPTION,
id: CUSTOM_URL,
link: CUSTOM_URL,
})
for (let item of items) {
feed.addItem({
title: item.title,
id: item.guid,
link: item.link,
description: item.contentSnippet,
content: item.content,
author: [
{
name: item.creator,
email: '',
link: item.source_link,
},
],
contributor: [],
date: new Date(item.isoDate),
})
}
console.log(`[createFeed] Finished building the aggregated feed`)
return feed
}
/**
* Generete the HTML page with the aggregated contents
* @param {*} items
* @returns
*/
function createHTML(items, sources) {
console.log(`[createHTML] building the HTML document`)
let template = Handlebars.templates['list_posts']
return template({
items: items,
sources: sources,
page_title: TITLE,
page_description: DESCRIPTION,
})
}