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) { var s if (err) return callback(err) var rendered = content.toString() for (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') const name = split[0].substr(1) content += '' + lines + ' >> ' + escape(name) + '\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') const name = split[0].substr(1) content += '' + String(lines).padStart(3, '0') + ' >> ' + escape(name) + '\n' lines-- } const back = '<< BACK TO COSMIC VOYAGE' 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 += '>> ' + keys[i] + ' (' + ships[keys[i]] + ' logs)\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) { const l = await head('/var/gopher/' + ship + '/LICENSE', 1) licenseLabel = l[0] licenseContent = '' + licenseLabel + '\n' } else { licenseContent = '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') const name = split[0].substr(1) content += '' + String(list.length - i).padStart(3, '0') + ' >> ' + escape(name) + '\n' } } const fullUrl = 'https://cosmic.voyage' + req.originalUrl res.render('ship-detail', { description: description, content: content, license: licenseContent, canonical: fullUrl }) }) app.get('/rss.xml', 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', 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', 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 }) }) }) app.get('/.well-known/webfinger', function(req, res) { var resource = req.query.resource if (resource && resource.startsWith('acct:') && resource.endsWith('@cosmic.voyage')) { const regex = /acct:(\w+)@cosmic\.voyage/i const resources = resource.match(regex) if (resources.length) { const user = resources[1].toLowerCase() const path = '/home/' + user + '/.webfinger.json' if (fs.existsSync(path)) { const content = bufferFile(path) res.setHeader('content-type', 'application/jrd+json') res.render('raw', { content: content }) } } } res.setHeader('content-type', 'application/jrd+json') res.render('raw', { 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 = '<< BACK TO RS001 LOG' 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 = '<< BACK TO COSMIC VOYAGE' 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) { const 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 = '<< BACK TO RS001 LOG' 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 = '<< BACK TO COSMIC VOYAGE' 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}!`))