Make Seance functions event-based
They now emit events when updates happen: the first step to making the asychronous online server version!
This commit is contained in:
parent
4a86adcbe3
commit
73212d9d20
2 changed files with 186 additions and 33 deletions
193
functions.js
193
functions.js
|
@ -1,7 +1,8 @@
|
||||||
const r2 = require('r2')
|
const r2 = require('r2')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const { once } = require('events')
|
const { once, EventEmitter } = require('events')
|
||||||
|
const { inherits } = require('util')
|
||||||
const getPost = require('mediumexporter').getPost
|
const getPost = require('mediumexporter').getPost
|
||||||
const { createClient } = require('webdav')
|
const { createClient } = require('webdav')
|
||||||
const readline = require('readline')
|
const readline = require('readline')
|
||||||
|
@ -34,7 +35,14 @@ class Seance {
|
||||||
async fetchFromMedium (mediumUrl, options = {
|
async fetchFromMedium (mediumUrl, options = {
|
||||||
json: null,
|
json: null,
|
||||||
}) {
|
}) {
|
||||||
console.info(`Fetching: ${mediumUrl}`);
|
|
||||||
|
this.emit('update', {
|
||||||
|
status: 'starting',
|
||||||
|
message: `Fetching: ${mediumUrl}`,
|
||||||
|
loglevel: 'info'
|
||||||
|
})
|
||||||
|
|
||||||
|
console.info();
|
||||||
var output = path.join(process.env.PWD, 'content')
|
var output = path.join(process.env.PWD, 'content')
|
||||||
|
|
||||||
var json
|
var json
|
||||||
|
@ -60,7 +68,11 @@ class Seance {
|
||||||
// this is based on what mediumexporter chooses as the output folder
|
// this is based on what mediumexporter chooses as the output folder
|
||||||
var outputFolder = path.join(output, post.slug)
|
var outputFolder = path.join(output, post.slug)
|
||||||
|
|
||||||
console.info(`Saving to: ${outputFolder}`)
|
this.emit('update', {
|
||||||
|
status: 'normal',
|
||||||
|
message: `Saving to: ${outputFolder}`,
|
||||||
|
loglevel: 'info'
|
||||||
|
})
|
||||||
|
|
||||||
if (!fs.existsSync(path.join(outputFolder, post.slug))) {
|
if (!fs.existsSync(path.join(outputFolder, post.slug))) {
|
||||||
fs.mkdirSync(outputFolder, { recursive: true })
|
fs.mkdirSync(outputFolder, { recursive: true })
|
||||||
|
@ -101,14 +113,22 @@ class Seance {
|
||||||
* @returns [string] status
|
* @returns [string] status
|
||||||
*/
|
*/
|
||||||
async pushToGhost (postSlug) {
|
async pushToGhost (postSlug) {
|
||||||
console.info('Pushing: ' + postSlug);
|
this.emit('update', {
|
||||||
|
status: 'starting',
|
||||||
|
message: 'Starting upload: ' + postSlug,
|
||||||
|
loglevel: 'info'
|
||||||
|
})
|
||||||
|
|
||||||
// Decide working path
|
// Decide working path
|
||||||
var postFolder = path.resolve('content/' + postSlug)
|
var postFolder = path.resolve('content/' + postSlug)
|
||||||
|
|
||||||
// Verify file exists
|
// Verify file exists
|
||||||
if (!fs.existsSync(postFolder)) {
|
if (!fs.existsSync(postFolder)) {
|
||||||
console.error('Could not find post folder! Is it fetched?')
|
|
||||||
|
this.emit('error', {
|
||||||
|
message: 'Could not find post folder! Is it fetched?',
|
||||||
|
})
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,7 +138,11 @@ class Seance {
|
||||||
|
|
||||||
// Verify post exists
|
// Verify post exists
|
||||||
if (!fs.existsSync(postContent)) {
|
if (!fs.existsSync(postContent)) {
|
||||||
console.error("Could not find 'index.md' in " + postSlug + "! Is it fetched?")
|
|
||||||
|
this.emit('error', {
|
||||||
|
message: "Could not find 'index.md' in " + postSlug + "! Is it fetched?",
|
||||||
|
})
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,7 +163,11 @@ class Seance {
|
||||||
const uploadedPath = config.webdav.uploaded_path_prefix + '/' + uploadPath
|
const uploadedPath = config.webdav.uploaded_path_prefix + '/' + uploadPath
|
||||||
|
|
||||||
// load metadata file
|
// load metadata file
|
||||||
console.debug('Loading metadata')
|
this.emit('update', {
|
||||||
|
status: 'starting',
|
||||||
|
message: 'Loading metadata',
|
||||||
|
loglevel: 'debug'
|
||||||
|
})
|
||||||
|
|
||||||
var postMetaFile = path.join(postFolder, 'metadata.json')
|
var postMetaFile = path.join(postFolder, 'metadata.json')
|
||||||
let postMeta = await JSON.parse(await fs.promises.readFile(postMetaFile))
|
let postMeta = await JSON.parse(await fs.promises.readFile(postMetaFile))
|
||||||
|
@ -161,6 +189,13 @@ class Seance {
|
||||||
// Note down uploaded images
|
// Note down uploaded images
|
||||||
var uploadedImages = []
|
var uploadedImages = []
|
||||||
|
|
||||||
|
this.emit('update', {
|
||||||
|
status: 'progress',
|
||||||
|
progress: null, // we don't know the percentage
|
||||||
|
message: 'Parsing post',
|
||||||
|
loglevel: 'info'
|
||||||
|
})
|
||||||
|
|
||||||
for await (const line of readInterface) {
|
for await (const line of readInterface) {
|
||||||
// Line to output
|
// Line to output
|
||||||
// Default is to make it same as input
|
// Default is to make it same as input
|
||||||
|
@ -210,15 +245,36 @@ class Seance {
|
||||||
// if the image is listed in postMeta.images, it would have
|
// if the image is listed in postMeta.images, it would have
|
||||||
// already been uploaded
|
// already been uploaded
|
||||||
if (uploadedImages.indexOf(imageName) != -1) {
|
if (uploadedImages.indexOf(imageName) != -1) {
|
||||||
console.log(`Skipping feature image ${imageName}: already listed for upload`)
|
|
||||||
|
this.emit('update', {
|
||||||
|
status: 'progress',
|
||||||
|
progress: 95, // we don't know the percentage
|
||||||
|
message: `Skipping feature image ${imageName}: already listed for upload`,
|
||||||
|
loglevel: 'info'
|
||||||
|
})
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
var imagePath = path.join(postFolder, 'images', imageName)
|
var imagePath = path.join(postFolder, 'images', imageName)
|
||||||
|
|
||||||
// We can only upload if the file exists!
|
// We can only upload if the file exists!
|
||||||
if (!fs.existsSync(imagePath)) {
|
if (!fs.existsSync(imagePath)) {
|
||||||
console.warn(`Skipping feature image "${imageName}": file not found`)
|
|
||||||
|
this.emit('update', {
|
||||||
|
status: 'progress',
|
||||||
|
progress: 95, // we don't know the percentage
|
||||||
|
message: `Skipping feature image "${imageName}": file not found`,
|
||||||
|
loglevel: 'warning'
|
||||||
|
})
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
console.log(`Uploading feature image: ${imageName}`)
|
|
||||||
|
this.emit('update', {
|
||||||
|
status: 'progress',
|
||||||
|
progress: 95, // we don't know the percentage
|
||||||
|
message: `Uploading feature image: ${imageName}`,
|
||||||
|
loglevel: 'info'
|
||||||
|
})
|
||||||
|
|
||||||
this.uploadDav(davPath, imagePath)
|
this.uploadDav(davPath, imagePath)
|
||||||
featuredImagePath = uploadedPath + '/' + imageName
|
featuredImagePath = uploadedPath + '/' + imageName
|
||||||
}
|
}
|
||||||
|
@ -233,7 +289,12 @@ class Seance {
|
||||||
|
|
||||||
// This will happen once all the line reading is finished
|
// This will happen once all the line reading is finished
|
||||||
// Uploads will continue in paralell though
|
// Uploads will continue in paralell though
|
||||||
console.debug('Adding to Ghost')
|
this.emit('update', {
|
||||||
|
status: 'progress',
|
||||||
|
progress: 100, // we don't know the percentage
|
||||||
|
message: 'Uploading to Ghost',
|
||||||
|
loglevel: 'info'
|
||||||
|
})
|
||||||
|
|
||||||
this.ghostAdmin.posts.add({
|
this.ghostAdmin.posts.add({
|
||||||
title: postMeta.title,
|
title: postMeta.title,
|
||||||
|
@ -246,9 +307,17 @@ class Seance {
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
// Check if user was added
|
// Check if user was added
|
||||||
if (res.primary_author.id == 1) {
|
if (res.primary_author.id == 1) {
|
||||||
console.warn(`WARNING: The admin editor, "${res.primary_author.name}", is set as author for this post. If this is incorrect, there was some problem matching usernames. Please check and set it manually.`)
|
this.emit('notification', {
|
||||||
|
message: `WARNING: The admin editor, "${res.primary_author.name}", is set as author for this post. If this is incorrect, there was some problem matching usernames. Please check and set it manually.`,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
console.log('Post conveyed successfully.')
|
|
||||||
|
this.emit('update', {
|
||||||
|
status: 'progress',
|
||||||
|
progress: 100, // we don't know the percentage
|
||||||
|
message: 'Post conveyed successfully',
|
||||||
|
loglevel: 'info'
|
||||||
|
})
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -301,7 +370,13 @@ class Seance {
|
||||||
// if scissors not set, return false
|
// if scissors not set, return false
|
||||||
// (it's never a scissors since it never matches)
|
// (it's never a scissors since it never matches)
|
||||||
if (!scissors) {
|
if (!scissors) {
|
||||||
console.warn('[scissors] No scissors set, so rejecting all images')
|
|
||||||
|
this.emit('update', {
|
||||||
|
status: 'normal',
|
||||||
|
message: '[scissors] No scissors set, so rejecting all images',
|
||||||
|
loglevel: 'warning'
|
||||||
|
})
|
||||||
|
|
||||||
return false
|
return false
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
|
@ -316,7 +391,13 @@ class Seance {
|
||||||
let result = await isScissors.compare()
|
let result = await isScissors.compare()
|
||||||
return result.passed
|
return result.passed
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn('[scissors] Skipping scissors check:', err.message)
|
|
||||||
|
this.emit('update', {
|
||||||
|
status: 'normal',
|
||||||
|
message: `[scissors] Skipping scissors check:${err.message}`,
|
||||||
|
loglevel: 'warning'
|
||||||
|
})
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -327,34 +408,64 @@ class Seance {
|
||||||
* @returns [object] ghost data json
|
* @returns [object] ghost data json
|
||||||
*/
|
*/
|
||||||
async generateUserData (mediumUsername, email) {
|
async generateUserData (mediumUsername, email) {
|
||||||
console.debug('Creating: @' + mediumUsername + '(email: ' + email + ')');
|
|
||||||
|
this.emit('update', {
|
||||||
|
status: 'starting',
|
||||||
|
message: `Creating: @${mediumUsername} (email: ${email})`,
|
||||||
|
loglevel: 'debug'
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
const mediumUrl = `https://medium.com/@${mediumUsername}/?format=json`;
|
const mediumUrl = `https://medium.com/@${mediumUsername}/?format=json`;
|
||||||
const json = await fetchMediumJSON(mediumUrl);
|
const json = await fetchMediumJSON(mediumUrl);
|
||||||
|
|
||||||
if (!json.success) {
|
if (!json.success) {
|
||||||
console.error(`Error: ${json.error}`)
|
this.emit('error', {
|
||||||
|
message: `Error: ${json.error}`,
|
||||||
|
})
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
console.debug(`Name: ${json.payload.user.name}`)
|
this.emit('update', {
|
||||||
console.debug(`Bio: ${json.payload.user.bio}`)
|
status: 'normal',
|
||||||
|
message: `Name: ${json.payload.user.name}`,
|
||||||
|
loglevel: 'debug'
|
||||||
|
})
|
||||||
|
|
||||||
|
this.emit('update', {
|
||||||
|
status: 'normal',
|
||||||
|
message: `Bio: ${json.payload.user.bio}`,
|
||||||
|
loglevel: 'debug'
|
||||||
|
})
|
||||||
|
|
||||||
// Download and upload image
|
// Download and upload image
|
||||||
|
|
||||||
let imageId = json.payload.user.imageId
|
let imageId = json.payload.user.imageId
|
||||||
console.log(`Image: ${imageId}`)
|
this.emit('update', {
|
||||||
|
status: 'normal',
|
||||||
|
message: `Profile pic: ${imageId}`,
|
||||||
|
loglevel: 'debug'
|
||||||
|
})
|
||||||
|
|
||||||
let imagePath = MEDIUM_IMG_CDN + '256/256/' + imageId
|
let imagePath = MEDIUM_IMG_CDN + '256/256/' + imageId
|
||||||
let filetype = imageId.split('.')[imageId.split('.').length - 1]
|
let filetype = imageId.split('.')[imageId.split('.').length - 1]
|
||||||
let fileName = `${mediumUsername}.${filetype}`
|
let fileName = `${mediumUsername}.${filetype}`
|
||||||
let filePath = path.join(process.env.PWD, fileName)
|
let filePath = path.join(process.env.PWD, fileName)
|
||||||
|
|
||||||
console.log(`Fetching: ${imagePath}`)
|
this.emit('update', {
|
||||||
|
status: 'normal',
|
||||||
|
message: `Fetching profile pic: ${imagePath}`,
|
||||||
|
loglevel: 'info'
|
||||||
|
})
|
||||||
|
|
||||||
const response = await (await r2.get(imagePath).response).buffer()
|
const response = await (await r2.get(imagePath).response).buffer()
|
||||||
await await fs.promises.writeFile(filePath, response, 'base64')
|
await await fs.promises.writeFile(filePath, response, 'base64')
|
||||||
|
|
||||||
console.log("Uploading to server")
|
this.emit('update', {
|
||||||
|
status: 'normal',
|
||||||
|
message: `Uploading profile pic: ${imagePath}`,
|
||||||
|
loglevel: 'info'
|
||||||
|
})
|
||||||
|
|
||||||
await this.uploadDav(path.join(config.webdav.path_prefix,'avatars'),
|
await this.uploadDav(path.join(config.webdav.path_prefix,'avatars'),
|
||||||
filePath)
|
filePath)
|
||||||
|
@ -403,7 +514,11 @@ class Seance {
|
||||||
} else if (err.response.status == 404) {
|
} else if (err.response.status == 404) {
|
||||||
|
|
||||||
// it's a 404, so we'll create the directory
|
// it's a 404, so we'll create the directory
|
||||||
console.debug(`Noting missing subdirectory: ${folder}`)
|
this.emit('update', {
|
||||||
|
status: 'normal',
|
||||||
|
message: `Noting missing subdirectory: ${folder}`,
|
||||||
|
loglevel: 'debug'
|
||||||
|
})
|
||||||
|
|
||||||
// first, create the parent directory (if required)
|
// first, create the parent directory (if required)
|
||||||
if (!await this.createDirIfNotExist(client, path.dirname(folder))) {
|
if (!await this.createDirIfNotExist(client, path.dirname(folder))) {
|
||||||
|
@ -411,7 +526,12 @@ class Seance {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
console.debug(`Creating missing subdirectory: ${folder}`)
|
this.emit('update', {
|
||||||
|
status: 'normal',
|
||||||
|
message: `Creating missing subdirectory: ${folder}`,
|
||||||
|
loglevel: 'debug'
|
||||||
|
})
|
||||||
|
|
||||||
// then, create the current directory
|
// then, create the current directory
|
||||||
await client.createDirectory(folder)
|
await client.createDirectory(folder)
|
||||||
.catch(async (err) => {
|
.catch(async (err) => {
|
||||||
|
@ -420,20 +540,34 @@ class Seance {
|
||||||
await client.stat(folder)
|
await client.stat(folder)
|
||||||
.catch((err2) => {
|
.catch((err2) => {
|
||||||
// Bad guess. Panic (and raise the original error)
|
// Bad guess. Panic (and raise the original error)
|
||||||
console.error(`Error: ${err.toJSON().message}`)
|
|
||||||
console.error("We're not sure what went wrong. Help!")
|
this.emit('update', {
|
||||||
|
status: 'error',
|
||||||
|
message: `Error: ${err.toJSON().message}\nWe're not sure what went wrong. Help!`,
|
||||||
|
loglevel: 'error'
|
||||||
|
})
|
||||||
|
|
||||||
throw err
|
throw err
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// what's this? Panic!
|
// what's this? Panic!
|
||||||
console.error(`Error: ${err.toJSON().message}`)
|
this.emit('update', {
|
||||||
console.error("We're not sure what went wrong. Help!")
|
status: 'error',
|
||||||
|
message: `Error: ${err.toJSON().message}\nWe're not sure what went wrong. Help!`,
|
||||||
|
loglevel: 'error'
|
||||||
|
})
|
||||||
|
|
||||||
throw err
|
throw err
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
// it's not a 404; we don't know how to handle this. Panic!
|
// it's not a 404; we don't know how to handle this. Panic!
|
||||||
|
this.emit('update', {
|
||||||
|
status: 'error',
|
||||||
|
message: 'An unknown error occured. Help!',
|
||||||
|
loglevel: 'error'
|
||||||
|
})
|
||||||
console.error(err.toJSON())
|
console.error(err.toJSON())
|
||||||
throw err
|
throw err
|
||||||
}
|
}
|
||||||
|
@ -478,6 +612,9 @@ class Seance {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make Seance an EventEmitter
|
||||||
|
inherits(Seance, EventEmitter)
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
Seance
|
Seance
|
||||||
}
|
}
|
||||||
|
|
26
index.js
26
index.js
|
@ -14,6 +14,22 @@ const {
|
||||||
Seance,
|
Seance,
|
||||||
} = require ('./functions')
|
} = require ('./functions')
|
||||||
|
|
||||||
|
|
||||||
|
// Set up Seance CLI notifications
|
||||||
|
const seance = new Seance()
|
||||||
|
|
||||||
|
seance.on('update', (e) => {
|
||||||
|
console.log(e.message)
|
||||||
|
})
|
||||||
|
|
||||||
|
seance.on('notification', (e) => {
|
||||||
|
console.warn(e.message)
|
||||||
|
})
|
||||||
|
|
||||||
|
seance.on('error', (e) => {
|
||||||
|
console.error(e.message)
|
||||||
|
})
|
||||||
|
|
||||||
program
|
program
|
||||||
.version('1.0.0-dev')
|
.version('1.0.0-dev')
|
||||||
.description('pull posts from Medium and add them to a Ghost blog');
|
.description('pull posts from Medium and add them to a Ghost blog');
|
||||||
|
@ -125,20 +141,20 @@ program.command('push-ghost <file>')
|
||||||
.alias('push')
|
.alias('push')
|
||||||
.description('push a downloaded Medium post to Ghost')
|
.description('push a downloaded Medium post to Ghost')
|
||||||
.action((file) => {
|
.action((file) => {
|
||||||
new Seance().pushToGhost(file);
|
seance.pushToGhost(file)
|
||||||
});
|
});
|
||||||
|
|
||||||
program.command('medium-to-ghost <mediumUrl>')
|
program.command('medium-to-ghost <mediumUrl>')
|
||||||
.alias('import')
|
.alias('import')
|
||||||
.description('copy a Medium file over to Ghost')
|
.description('copy a Medium file over to Ghost')
|
||||||
.action((mediumUrl) => {
|
.action((mediumUrl) => {
|
||||||
new Seance().pushToGhost(mediumUrl);
|
seance.pushToGhost(mediumUrl);
|
||||||
});
|
});
|
||||||
|
|
||||||
program.command('create-user <username> <email>')
|
program.command('create-user <username> <email>')
|
||||||
.description('create ghost-import.json to import Medium user to Ghost')
|
.description('create ghost-import.json to import Medium user to Ghost')
|
||||||
.action(async (username, email) => {
|
.action(async (username, email) => {
|
||||||
const jsonOut = await new Seance().generateUserData(username, email)
|
const jsonOut = await seance.generateUserData(username, email)
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.log(`Error: ${err.error}`)
|
console.log(`Error: ${err.error}`)
|
||||||
return
|
return
|
||||||
|
@ -157,13 +173,13 @@ program.command('webdav-test <file>')
|
||||||
current_date.getUTCMonth().toString(),
|
current_date.getUTCMonth().toString(),
|
||||||
'test' // TODO: replace with article slug
|
'test' // TODO: replace with article slug
|
||||||
)
|
)
|
||||||
new Seance().uploadDav(dir_path, file);
|
new seance.uploadDav(dir_path, file);
|
||||||
});
|
});
|
||||||
|
|
||||||
program.command('check-scissors <file>')
|
program.command('check-scissors <file>')
|
||||||
.description('[test command] check if an image matches the set separator')
|
.description('[test command] check if an image matches the set separator')
|
||||||
.action(async (file) => {
|
.action(async (file) => {
|
||||||
console.log(await new Seance().checkScissors(file))
|
console.log(await seance.checkScissors(file))
|
||||||
})
|
})
|
||||||
|
|
||||||
program.parse(process.argv)
|
program.parse(process.argv)
|
||||||
|
|
Loading…
Reference in a new issue