1edee70807
The long-standing "make title proper" bug has finally been fixed. Yayy! So now, basically, the title and subtitle gets set properly even if Medium messed it up by showing some of them twice.
243 lines
5.2 KiB
JavaScript
243 lines
5.2 KiB
JavaScript
const express = require('express')
|
|
const bodyParser = require('body-parser')
|
|
const { vueRenderer } = require('@doweb/vuexpress')
|
|
const slugify = require('underscore.string/slugify')
|
|
const fs = require('fs')
|
|
|
|
const { Seance } = require ('./seance')
|
|
|
|
const app = express()
|
|
var expressWs = require('express-ws')(app)
|
|
|
|
// ALlow URLencoded API
|
|
app.use(bodyParser.urlencoded({
|
|
extended: false
|
|
}))
|
|
|
|
// Allow JSON API
|
|
app.use(bodyParser('json'))
|
|
|
|
// Enable static files
|
|
app.use(express.static('public'))
|
|
app.use(express.static('static'))
|
|
|
|
// Set up VueXpress
|
|
let options = {
|
|
views: './views',
|
|
cache: true,
|
|
watch: true,
|
|
metaInfo: {
|
|
title: 'Seance',
|
|
script: [
|
|
{ type: 'text/javascript', src: '/app.js' },
|
|
],
|
|
},
|
|
extractCSS: true,
|
|
cssOutputPath: '/css/styles.css',
|
|
publicPath: 'public',
|
|
compilerConfig: {
|
|
// custom webpack config
|
|
},
|
|
compilerConfigCallback: function(webpackConfig) {
|
|
// change the merged webpackconfig if you like
|
|
return webpackConfig;
|
|
},
|
|
|
|
}
|
|
|
|
const renderer = vueRenderer(options)
|
|
app.use(renderer)
|
|
|
|
// Views
|
|
|
|
app.get('/', (req, res) => {
|
|
res.render('index', {
|
|
baseUrl: req.hostname.startsWith('localhost')
|
|
? req.protocol + '://' + req.headers.host
|
|
: '//' + req.hostname, // auto choose http or https
|
|
})
|
|
})
|
|
|
|
app.post('/fetch', (req, res) => {
|
|
var json
|
|
var post
|
|
|
|
try {
|
|
json = JSON.parse(req.body.data)
|
|
} catch (err) {
|
|
console.log(err)
|
|
}
|
|
|
|
if (json) {
|
|
post = json.payload.value
|
|
console.log(post)
|
|
|
|
// set author
|
|
post.author = post.displayAuthor
|
|
|
|
// If the author's not available, get it from somewhere else
|
|
// function courtesy mediumexporter
|
|
let authors = []
|
|
if (json.payload.references && json.payload.references.User) {
|
|
Object.keys(json.payload.references.User).forEach(k => {
|
|
let u = json.payload.references.User[k]
|
|
authors.push({
|
|
name: u.name,
|
|
username: u.username,
|
|
userId: u.userId
|
|
})
|
|
})
|
|
post.authors = authors
|
|
|
|
if (!post.author) {
|
|
post.author = authors[0].name
|
|
}
|
|
}
|
|
|
|
// set featured image
|
|
if (post.virtuals.previewImage) {
|
|
post.featuredImage = 'https://cdn-images-1.medium.com/max/800/' + post.virtuals.previewImage.imageId
|
|
}
|
|
|
|
// set ID
|
|
if (!post.slug) {
|
|
post.slug = slugify(post.title)
|
|
}
|
|
|
|
// save to disk
|
|
fs.writeFileSync(`${post.slug}.json`, JSON.stringify(json), 'utf-8')
|
|
}
|
|
|
|
// render the final post
|
|
res.render('fetch-medium', {
|
|
post: {
|
|
title: post.title,
|
|
subtitle: post.content.subtitle,
|
|
author: post.author,
|
|
featuredImage: post.featuredImage,
|
|
mediumUrl: post.mediumUrl,
|
|
slug: post.slug,
|
|
}
|
|
})
|
|
})
|
|
|
|
app.get('/fetch', (req, res) => {
|
|
res.redirect(303, '/')
|
|
})
|
|
|
|
app.get('/api', (req, res) => {
|
|
res.json({
|
|
status: 'success',
|
|
message: 'Welcome to the Seance API :)',
|
|
})
|
|
})
|
|
|
|
app.ws('/ws/fetch-medium', (ws, req) => {
|
|
ws.on('message', async(msg) => {
|
|
|
|
command = msg.split(' ')
|
|
if (command.length == 3 && command[0] == 'fetch') {
|
|
const postSlug = command[1]
|
|
const password = command[2]
|
|
|
|
if (password != process.env.SEANCE_PW) {
|
|
ws.send('error: Something went wrong. Please try again :(')
|
|
return
|
|
}
|
|
|
|
const postMetaFile = `${postSlug}.json`
|
|
|
|
if (!fs.existsSync(postMetaFile)) {
|
|
ws.send(`error: post ${postSlug} not yet loaded`)
|
|
return
|
|
}
|
|
|
|
// Start the Seance session
|
|
seance = new Seance()
|
|
|
|
// set up handlers
|
|
seance.on('update', (e) => {
|
|
ws.send(`update: ${e.message}`)
|
|
})
|
|
|
|
seance.on('notification', (e) => {
|
|
ws.send(`notification: ${e.message}`)
|
|
})
|
|
|
|
seance.on('error', (e) => {
|
|
ws.send(`error: ${e.message}`)
|
|
})
|
|
|
|
seance.fetchFromMedium(postMetaFile)
|
|
.then((post) => {
|
|
console.info(`"${post.title}" fetched successfully.`)
|
|
ws.send(`done: ${post.slug}`)
|
|
})
|
|
}
|
|
|
|
})
|
|
|
|
ws.on('close', () => {
|
|
console.log('socket closed')
|
|
})
|
|
})
|
|
|
|
app.ws('/ws/push-ghost', (ws, req) => {
|
|
ws.on('message', async(msg) => {
|
|
|
|
// respond to keepalive
|
|
if (msg == '__ping__') {
|
|
ws.send('__pong__')
|
|
return
|
|
}
|
|
|
|
command = msg.split(' ')
|
|
if (command.length == 3 && command[0] == 'push') {
|
|
const postSlug = command[1]
|
|
|
|
// check password
|
|
if (command[2] != process.env.SEANCE_PW) {
|
|
ws.send('error: Something went wrong. Please try again :(')
|
|
return
|
|
}
|
|
|
|
// Start the Seance session
|
|
seance = new Seance()
|
|
|
|
// set up handlers
|
|
seance.on('update', (e) => {
|
|
ws.send(`update: ${e.message}`)
|
|
})
|
|
|
|
seance.on('notification', (e) => {
|
|
ws.send(`notification: ${e.message}`)
|
|
})
|
|
|
|
seance.on('error', (e) => {
|
|
ws.send(`error: ${e.message}`)
|
|
})
|
|
|
|
seance.pushToGhost(postSlug)
|
|
.then(() => {
|
|
console.info(`"${postSlug}" pushed successfully.`)
|
|
|
|
// send 'done' message
|
|
ws.send(`done: ${postSlug}`)
|
|
})
|
|
} else {
|
|
ws.send('error: Malformed message')
|
|
return
|
|
}
|
|
|
|
})
|
|
|
|
ws.on('close', () => {
|
|
console.log('socket closed')
|
|
})
|
|
})
|
|
|
|
const port = process.env.PORT || 4000
|
|
app.listen(port, () => {
|
|
console.log(`Listening on ${port}`)
|
|
})
|