Make Seance into separate Object

Now, the Seance object is what everyone operates with. That way,
we can do fancy stuff like emitting events, etc. if the need so
arises (as it will, very soon; trust me).
This commit is contained in:
Badri Sunderarajan 2020-05-04 17:55:41 +05:30
parent e0b5ab31b4
commit 0d9ecfd8f5
2 changed files with 412 additions and 398 deletions

View file

@ -11,10 +11,12 @@ const Rembrandt = require('rembrandt')
const config = require('./config') const config = require('./config')
const MEDIUM_IMG_CDN = 'https://miro.medium.com/fit/c/' class Seance {
constructor(...args) {
this.MEDIUM_IMG_CDN = 'https://miro.medium.com/fit/c/'
try { try {
const ghostAdmin = new GhostAdminAPI({ this.ghostAdmin = new GhostAdminAPI({
url: config.ghost.url, url: config.ghost.url,
version: config.ghost.version, version: config.ghost.version,
key: config.ghost.admin_key, key: config.ghost.admin_key,
@ -22,19 +24,26 @@ try {
} catch(err) { } catch(err) {
console.error('Your Ghost isn\'t configured. Please run `seance setup` to fix this!') console.error('Your Ghost isn\'t configured. Please run `seance setup` to fix this!')
} }
/** /**
* function [fetchFromMedium] * function [fetchFromMedium]
* @returns [string] status * @returns [string] status
*/ */
}
const fetchFromMedium = async (mediumUrl) => { async fetchFromMedium (mediumUrl) {
console.info(`Fetching: ${mediumUrl}`); console.info(`Fetching: ${mediumUrl}`);
output = path.join(process.env.PWD, 'content') var output = path.join(process.env.PWD, 'content')
var json
json = await this.fetchMediumJSON(mediumUrl)
// use mediumexporter's getPost function to fetch a Medium post // use mediumexporter's getPost function to fetch a Medium post
const post = await getPost(mediumUrl, { const post = await getPost(mediumUrl, {
returnObject: true, returnObject: true,
output: output, output: output,
postJSON: json
}).catch((err) => { }).catch((err) => {
return { return {
error: err, error: err,
@ -43,7 +52,7 @@ const fetchFromMedium = async (mediumUrl) => {
// set output folder path // set output folder path
// this is based on what mediumexporter chooses as the output folder // this is based on what mediumexporter chooses as the output folder
outputFolder = path.join(output, post.slug) var outputFolder = path.join(output, post.slug)
console.info(`Saving to: ${outputFolder}`) console.info(`Saving to: ${outputFolder}`)
@ -80,15 +89,16 @@ const fetchFromMedium = async (mediumUrl) => {
return post return post
}; };
/** /**
* function [pushToGhost] * function [pushToGhost]
* @returns [string] status * @returns [string] status
*/ */
const pushToGhost = async (postSlug) => { async pushToGhost (postSlug) {
console.info('Pushing: ' + postSlug); console.info('Pushing: ' + postSlug);
// Decide working path // Decide working path
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)) {
@ -107,7 +117,7 @@ const pushToGhost = async (postSlug) => {
} }
// Decide WebDAV upload path // Decide WebDAV upload path
current_date = new Date() var current_date = new Date()
const uploadPath = path.join( const uploadPath = path.join(
current_date.getUTCFullYear().toString(), current_date.getUTCFullYear().toString(),
@ -125,7 +135,7 @@ const pushToGhost = async (postSlug) => {
// load metadata file // load metadata file
console.debug('Loading metadata') console.debug('Loading metadata')
postMetaFile = path.join(postFolder, 'metadata.json') var postMetaFile = path.join(postFolder, 'metadata.json')
let postMeta = await JSON.parse(fs.readFileSync(postMetaFile)) let postMeta = await JSON.parse(fs.readFileSync(postMetaFile))
// Process lines // Process lines
@ -167,14 +177,14 @@ const pushToGhost = async (postSlug) => {
console.warn('Skipping missing image: ' + imageName) console.warn('Skipping missing image: ' + imageName)
} else { } else {
// check for separator image // check for separator image
var isScissors = await checkScissors(imagePath) var isScissors = await this.checkScissors(imagePath)
if (isScissors) { if (isScissors) {
newLine = '\n---\n' newLine = '\n---\n'
} else { } else {
// upload pic to server // upload pic to server
console.debug(`Adding to upload queue: ${imageName}`) console.debug(`Adding to upload queue: ${imageName}`)
uploadedImages.push(imageName) uploadedImages.push(imageName)
uploadDav(davPath, imagePath) this.uploadDav(davPath, imagePath)
newLine = '![' + imageAlt + '](' + uploadedPath + '/' + imageName + ')' newLine = '![' + imageAlt + '](' + uploadedPath + '/' + imageName + ')'
} }
@ -201,7 +211,7 @@ const pushToGhost = async (postSlug) => {
console.warn(`Skipping feature image "${imageName}": file not found`) console.warn(`Skipping feature image "${imageName}": file not found`)
} else { } else {
console.log(`Uploading feature image: ${imageName}`) console.log(`Uploading feature image: ${imageName}`)
uploadDav(davPath, imagePath) this.uploadDav(davPath, imagePath)
featuredImagePath = uploadedPath + '/' + imageName featuredImagePath = uploadedPath + '/' + imageName
} }
} }
@ -217,12 +227,12 @@ const pushToGhost = async (postSlug) => {
// Uploads will continue in paralell though // Uploads will continue in paralell though
console.debug('Adding to Ghost') console.debug('Adding to Ghost')
ghostAdmin.posts.add({ this.ghostAdmin.posts.add({
title: postMeta.title, title: postMeta.title,
custom_excerpt: postMeta.subtitle || null, custom_excerpt: postMeta.subtitle || null,
tags: postMeta.tags, tags: postMeta.tags,
authors: users, authors: users,
html: markdown.toHTML(fs.readFileSync(postOutput, mode='utf-8')), html: markdown.toHTML(fs.readFileSync(postOutput, 'utf-8')),
feature_image: featuredImagePath feature_image: featuredImagePath
}, {source: 'html'}) }, {source: 'html'})
.then((res) => { .then((res) => {
@ -234,20 +244,32 @@ const pushToGhost = async (postSlug) => {
}) })
}; };
/** /**
* function [mediumToGhost] * function [mediumToGhost]
* @returns [string] status * @returns [string] status
*/ */
const mediumToGhost = (mediumUrl) => { mediumToGhost (mediumUrl) {
console.info('Copying: ' + mediumUrl); console.info('Copying: ' + mediumUrl);
}; }
async function fetchMediumJSON(mediumUrl) { async fetchMediumJSON(mediumUrl) {
console.debug(`Fetching: ${mediumUrl}`) var json
var text
if (mediumUrl.match(/^http/i)) {
// add ?json attribute
mediumUrl = mediumUrl.replace(/#.*$/, '')
mediumUrl= `${mediumUrl}?format=json`
const response = await fetch(mediumUrl) const response = await fetch(mediumUrl)
const text = await response.text() text = await response.text()
const json = await JSON.parse(text.substr(text.indexOf('{'))) } else {
throw { error: 'URL must be a Medium URL' }
}
json = await JSON.parse(text.substr(text.indexOf('{')))
return json; return json;
} }
@ -256,7 +278,7 @@ async function fetchMediumJSON(mediumUrl) {
* @returns [boolean] matchStatus * @returns [boolean] matchStatus
*/ */
const checkScissors = async (imagePath) => { async checkScissors (imagePath) {
// Decide "separator" image // Decide "separator" image
// If set, images matching this will be ignored and replaced // If set, images matching this will be ignored and replaced
// with a horizontal-rule ("---" in markdown) instead. // with a horizontal-rule ("---" in markdown) instead.
@ -290,7 +312,7 @@ const checkScissors = async (imagePath) => {
* function [createUser] * function [createUser]
* @returns [object] ghost data json * @returns [object] ghost data json
*/ */
const generateUserData = async (mediumUsername, email) => { async generateUserData (mediumUsername, email) {
console.debug('Creating: @' + mediumUsername + '(email: ' + email + ')'); console.debug('Creating: @' + mediumUsername + '(email: ' + email + ')');
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);
@ -320,7 +342,7 @@ const generateUserData = async (mediumUsername, email) => {
console.log("Uploading to server") console.log("Uploading to server")
await uploadDav(path.join(config.webdav.path_prefix,'avatars'), await this.uploadDav(path.join(config.webdav.path_prefix,'avatars'),
filePath) filePath)
// Generate Ghost JSON // Generate Ghost JSON
@ -347,7 +369,7 @@ const generateUserData = async (mediumUsername, email) => {
return(JSON.stringify(ghostData)) return(JSON.stringify(ghostData))
}; };
const createDirIfNotExist = async (client, folder) => { async createDirIfNotExist (client, folder) {
// recursively create subfolders if they don't exist. // recursively create subfolders if they don't exist.
//safety: don't touch directories outside WEBDAV_PATH_PREFIX //safety: don't touch directories outside WEBDAV_PATH_PREFIX
@ -370,7 +392,7 @@ const createDirIfNotExist = async (client, folder) => {
console.debug(`Noting missing subdirectory: ${folder}`) console.debug(`Noting missing subdirectory: ${folder}`)
// first, create the parent directory (if required) // first, create the parent directory (if required)
if (!await createDirIfNotExist(client, path.dirname(folder))) { if (!await this.createDirIfNotExist(client, path.dirname(folder))) {
// if not created, we fail too :-/ // if not created, we fail too :-/
return false return false
} }
@ -409,10 +431,10 @@ const createDirIfNotExist = async (client, folder) => {
* function [uploadDav] * function [uploadDav]
* @returns [string] status * @returns [string] status
*/ */
const uploadDav = async (dirPath, filePath) => { async uploadDav (dirPath, filePath) {
// connect to webdav // connect to webdav
client = createClient( const client = createClient(
config.webdav.server_url, config.webdav.server_url,
{ {
username: config.webdav.username, username: config.webdav.username,
@ -422,29 +444,26 @@ const uploadDav = async (dirPath, filePath) => {
// create directory if not exists // create directory if not exists
console.debug(`[dav-upload] Loading ${dirPath}`) console.debug(`[dav-upload] Loading ${dirPath}`)
if (!await createDirIfNotExist(client, dirPath)) { if (!await this.createDirIfNotExist(client, dirPath)) {
console.error(`[dav-upload] Could not upload ${path.basename(filePath)} :(`) console.error(`[dav-upload] Could not upload ${path.basename(filePath)} :(`)
return false return false
} }
// upload a file // upload a file
console.debug('Uploading file') console.debug('Uploading file')
outStream = client.createWriteStream( const outStream = client.createWriteStream(
path.join(dirPath, path.basename(filePath)) path.join(dirPath, path.basename(filePath))
) )
outStream.on('finish', () => console.debug('Uploaded successfully.')) outStream.on('finish', () => console.debug('Uploaded successfully.'))
inStream = fs.createReadStream(filePath) const inStream = fs.createReadStream(filePath)
.pipe(outStream) .pipe(outStream)
return true return true
} }
module.exports = { }
fetchFromMedium,
pushToGhost, module.exports = {
mediumToGhost, Seance
generateUserData,
checkScissors,
uploadDav
} }

View file

@ -11,12 +11,7 @@ const yaml = require('js-yaml')
const config = require('./config') const config = require('./config')
const { const {
fetchFromMedium, Seance,
pushToGhost,
mediumToGhost,
generateUserData,
checkScissors,
uploadDav,
} = require ('./functions') } = require ('./functions')
program program
@ -120,7 +115,7 @@ program.command('fetch-medium <post_url>')
.alias('fetch') .alias('fetch')
.description('fetch a Medium post') .description('fetch a Medium post')
.action((post_url) => { .action((post_url) => {
fetchFromMedium(post_url) new Seance().fetchFromMedium(post_url)
.then((post) => { .then((post) => {
console.info(`"${post.title}" fetched successfully.`) console.info(`"${post.title}" fetched successfully.`)
}) })
@ -130,20 +125,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) => {
pushToGhost(file); new 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) => {
pushToGhost(mediumUrl); new 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 generateUserData(username, email) const jsonOut = await new Seance().generateUserData(username, email)
.catch((err) => { .catch((err) => {
console.log(`Error: ${err.error}`) console.log(`Error: ${err.error}`)
return return
@ -162,13 +157,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
) )
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 checkScissors(file)) console.log(await new Seance().checkScissors(file))
}) })
program.parse(process.argv) program.parse(process.argv)