cosmic-web/server.js

204 lines
7.7 KiB
JavaScript

const express = require('express')
const app = express()
const port = 3000
var fs = require('fs')
var path = require('path')
const { bufferFile, wc, head, escape } = require('./utils')
app.engine('cosmic', function (filePath, options, callback) {
fs.readFile(filePath, function (err, content) {
if (err) return callback(err)
var rendered = content.toString()
for (var s in options) {
if (typeof options[s] === 'string') {
rendered = rendered.replace('##' + s + '##', options[s])
}
}
return callback(null, rendered)
})
})
app.set('views', './views')
app.set('view engine', 'cosmic')
app.get('/', async function (req, res) {
const intro = bufferFile('/var/gopher/intro.gophermap')
const recent = await head('/var/gopher/listing.gophermap', 20)
var lines = await wc('/var/gopher/listing.gophermap')
var content = ''
for (let i = 0; i < recent.length; ++i) {
const split = recent[i].split('\t')
let link = split[1]
link = link.replace(/\.txt$/, '.html')
let name = split[0].substr(1)
content += '<a href="' + link + '">' + lines + ' <span class="dim">&gt;&gt;</span> ' + escape(name) + '</a>\n'
lines--
}
res.render('index', { intro: intro, recent: content })
})
app.get('/log', async function (req, res) {
const list = await head('/var/gopher/listing.gophermap')
var lines = await wc('/var/gopher/listing.gophermap')
var content = ''
for (let i = 0; i < list.length; ++i) {
const split = list[i].split('\t')
let link = split[1]
link = link.replace(/\.txt$/, '.html')
let name = split[0].substr(1)
content += '<a href="' + link + '">' + String(lines).padStart(3, '0') + ' <span class="dim">&gt;&gt;</span> ' + escape(name) + '</a>\n'
lines--
}
const back = '<a href="/"><span class="dim">&lt;&lt;</span> BACK TO COSMIC VOYAGE</a>'
const label = 'RS001 Log Entries (Newest First):'
const compiled = back + '\n\n' + label + '\n\n' + content
const fullUrl = 'https://cosmic.voyage' + req.originalUrl
res.render('basic', { content: compiled, canonical: fullUrl})
})
app.get('/ships', async function (req, res) {
const intro = bufferFile('/var/gopher/ships/ships.gophermap')
const list = await head('/var/gopher/listing.gophermap')
const ships = {}
for (let i = 0; i < list.length; ++i) {
const split = list[i].split('\t')
let name = split[0].substr(1)
name = name.split(' - ')[0].trim()
ships[name] = ships[name] + 1 || 1
}
var keys = Object.keys(ships);
keys.sort();
var content = ''
for(let i = 0; i < keys.length; ++i){
content += '<span class="dim">&gt;&gt;</span> <a href="/ships/' + encodeURIComponent(keys[i]) + '">' + keys[i] + ' (' + ships[keys[i]] + ' logs)</a>\n'
}
res.render('ships', { intro: intro, ships: content })
})
app.get('/ships/*', async function (req, res) {
const list = await head('/var/gopher/listing.gophermap')
const ship = decodeURIComponent(req.path).replace(new RegExp('/ships/', 'i'), '').replace(new RegExp('/(?:index.html)?$', 'i'), '');
const description = escape(bufferFile('/var/gopher/' + ship + '/.description')) || ''
const license = bufferFile('/var/gopher/' + ship + '/LICENSE') || ''
var licenseLabel = ''
var licenseContent = ''
if (license) {
var l = await head('/var/gopher/' + ship + '/LICENSE', 1)
licenseLabel = l[0]
licenseContent = '<a href="/' + encodeURIComponent(ship) + '/LICENSE">' + licenseLabel + '</a>\n'
} else {
licenseLabel = 'All rights reserved.'
}
var content = ship + ' - Ship Log:\n'
for (let i = list.length - 1; i >= 0; --i) {
if (list[i].startsWith('0' + ship)) {
const split = list[i].split('\t')
let link = split[1]
link = link.replace(/\.txt$/, '.html')
let name = split[0].substr(1)
content += '<a href="' + link + '">' + String(list.length - i).padStart(3, '0') + ' <span class="dim">&gt;&gt;</span> ' + escape(name) + '</a>\n'
}
}
const fullUrl = 'https://cosmic.voyage' + req.originalUrl
res.render('ship-detail', { description: description, content: content, license: licenseContent, canonical: fullUrl })
})
app.get('/rss.xml', async function (req, res) {
var content = bufferFile('/var/gopher/rss.xml')
const domainRegexp = new RegExp('gopher://cosmic.voyage(/./)?', 'g')
const extensionRegexp = new RegExp('\.txt', 'g')
content = content.replace(domainRegexp, 'https://cosmic.voyage/')
content = content.replace(extensionRegexp, '.html')
res.setHeader('content-type', 'text/xml')
res.render('raw', { content: content })
})
app.get('/atom.xml', async function (req, res) {
var content = bufferFile('/var/gopher/atom.xml')
const domainRegexp = new RegExp('gopher://cosmic.voyage/(0/)?', 'g')
const extensionRegexp = new RegExp('\.txt', 'g')
content = content.replace(domainRegexp, 'https://cosmic.voyage/')
content = content.replace(extensionRegexp, '.html')
res.setHeader('content-type', 'text/xml')
res.render('raw', { content: content })
})
app.get('/sitemap.xml', async function (req, res) {
var content = bufferFile('/var/gopher/sitemap.xml')
fs.stat('/var/gopher/rss.xml', (err, stats) => {
if (err) {
res(err)
}
content = content.replace(/##date##/g, stats.mtime.toISOString())
res.setHeader('content-type', 'text/xml')
res.render('raw', { content: content })
})
})
// Any link to a direct static resource will show it
app.use(express.static(path.join(__dirname, '/static')))
// Override default LICENSE display and format for cosmic styles
app.get('*/LICENSE', function(req, res){
let error = false
var file = path.join('/var/gopher/', decodeURIComponent(req.path));
fs.exists(file, function(exists) {
if (exists) {
const file = escape(bufferFile('/var/gopher/' + decodeURIComponent(req.path)))
const back = '<a href="/log"><span class="dim">&lt;&lt;</span> BACK TO RS001 LOG</a>'
const content = back + '\n\n' + file
res.setHeader('content-type', 'text/html')
const fullUrl = 'https://cosmic.voyage' + req.originalUrl
res.render('basic', { content: content, canonical: fullUrl})
} else {
error = true
}
})
// If license isn't found, give the 404
if (error) {
const back = '<a href="/"><span class="dim">&lt;&lt;</span> BACK TO COSMIC VOYAGE</a>'
const error = 'Message not found. Please try again.'
const content = back + '\n\n' + error
res.status(404)
const fullUrl = 'https://cosmic.voyage' + req.originalUrl
res.render('basic', { content: content, canonical: fullUrl})
}
})
// Any other gopher content directly linked will show as-is
app.use(express.static('/var/gopher'))
// Grab anything that's a .txt and wrap it in .html
app.get('*', function(req, res){
let error = false
if (req.path.indexOf('.html') !== -1) {
var file = path.join('/var/gopher/', decodeURIComponent(req.path).replace(/\.html/, '.txt'));
fs.exists(file, function(exists) {
if (exists) {
const file = escape(bufferFile('/var/gopher/' + decodeURIComponent(req.path).replace(/\.html/, '.txt')))
const back = '<a href="/log"><span class="dim">&lt;&lt;</span> BACK TO RS001 LOG</a>'
const content = back + '\n\n' + file
const fullUrl = 'https://cosmic.voyage' + req.originalUrl
res.render('basic', { content: content, canonical: fullUrl})
} else {
error = true
}
})
} else {
error = true
}
// If resource isn't found, give the 404
if (error) {
const back = '<a href="/"><span class="dim">&lt;&lt;</span> BACK TO COSMIC VOYAGE</a>'
const error = 'Message not found. Please try again.'
const content = back + '\n\n' + error
res.status(404)
const fullUrl = 'https://cosmic.voyage' + req.originalUrl
res.render('basic', { content: content, canonical: fullUrl})
}
})
app.listen(port, () => console.log(`listening on port ${port}!`))