diff --git a/functions.js b/functions.js index e28141d..93873ff 100644 --- a/functions.js +++ b/functions.js @@ -1,7 +1,8 @@ const r2 = require('r2') const path = require('path') const fs = require('fs') -const { once } = require('events') +const { once, EventEmitter } = require('events') +const { inherits } = require('util') const getPost = require('mediumexporter').getPost const { createClient } = require('webdav') const readline = require('readline') @@ -34,7 +35,14 @@ class Seance { async fetchFromMedium (mediumUrl, options = { 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 json @@ -60,7 +68,11 @@ class Seance { // this is based on what mediumexporter chooses as the output folder 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))) { fs.mkdirSync(outputFolder, { recursive: true }) @@ -101,14 +113,22 @@ class Seance { * @returns [string] status */ async pushToGhost (postSlug) { - console.info('Pushing: ' + postSlug); + this.emit('update', { + status: 'starting', + message: 'Starting upload: ' + postSlug, + loglevel: 'info' + }) // Decide working path var postFolder = path.resolve('content/' + postSlug) // Verify file exists 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 } @@ -118,7 +138,11 @@ class Seance { // Verify post exists 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 } @@ -139,7 +163,11 @@ class Seance { const uploadedPath = config.webdav.uploaded_path_prefix + '/' + uploadPath // load metadata file - console.debug('Loading metadata') + this.emit('update', { + status: 'starting', + message: 'Loading metadata', + loglevel: 'debug' + }) var postMetaFile = path.join(postFolder, 'metadata.json') let postMeta = await JSON.parse(await fs.promises.readFile(postMetaFile)) @@ -161,6 +189,13 @@ class Seance { // Note down uploaded images 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) { // Line to output // 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 // already been uploaded 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 { var imagePath = path.join(postFolder, 'images', imageName) // We can only upload if the file exists! 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 { - 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) featuredImagePath = uploadedPath + '/' + imageName } @@ -233,7 +289,12 @@ class Seance { // This will happen once all the line reading is finished // 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({ title: postMeta.title, @@ -246,9 +307,17 @@ class Seance { .then((res) => { // Check if user was added 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 // (it's never a scissors since it never matches) 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 } else { @@ -316,7 +391,13 @@ class Seance { let result = await isScissors.compare() return result.passed } 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 } } @@ -327,34 +408,64 @@ class Seance { * @returns [object] ghost data json */ 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 json = await fetchMediumJSON(mediumUrl); if (!json.success) { - console.error(`Error: ${json.error}`) + this.emit('error', { + message: `Error: ${json.error}`, + }) return false } - console.debug(`Name: ${json.payload.user.name}`) - console.debug(`Bio: ${json.payload.user.bio}`) + this.emit('update', { + 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 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 filetype = imageId.split('.')[imageId.split('.').length - 1] let fileName = `${mediumUsername}.${filetype}` 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() 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'), filePath) @@ -403,7 +514,11 @@ class Seance { } else if (err.response.status == 404) { // 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) if (!await this.createDirIfNotExist(client, path.dirname(folder))) { @@ -411,7 +526,12 @@ class Seance { 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 await client.createDirectory(folder) .catch(async (err) => { @@ -420,20 +540,34 @@ class Seance { await client.stat(folder) .catch((err2) => { // 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 }) } else { // what's this? Panic! - 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 } }) } else { // 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()) throw err } @@ -478,6 +612,9 @@ class Seance { } +// Make Seance an EventEmitter +inherits(Seance, EventEmitter) + module.exports = { Seance } diff --git a/index.js b/index.js index 821ee04..bd88036 100755 --- a/index.js +++ b/index.js @@ -14,6 +14,22 @@ const { Seance, } = 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 .version('1.0.0-dev') .description('pull posts from Medium and add them to a Ghost blog'); @@ -125,20 +141,20 @@ program.command('push-ghost ') .alias('push') .description('push a downloaded Medium post to Ghost') .action((file) => { - new Seance().pushToGhost(file); + seance.pushToGhost(file) }); program.command('medium-to-ghost ') .alias('import') .description('copy a Medium file over to Ghost') .action((mediumUrl) => { - new Seance().pushToGhost(mediumUrl); + seance.pushToGhost(mediumUrl); }); program.command('create-user ') .description('create ghost-import.json to import Medium user to Ghost') .action(async (username, email) => { - const jsonOut = await new Seance().generateUserData(username, email) + const jsonOut = await seance.generateUserData(username, email) .catch((err) => { console.log(`Error: ${err.error}`) return @@ -157,13 +173,13 @@ program.command('webdav-test ') current_date.getUTCMonth().toString(), 'test' // TODO: replace with article slug ) - new Seance().uploadDav(dir_path, file); + new seance.uploadDav(dir_path, file); }); program.command('check-scissors ') .description('[test command] check if an image matches the set separator') .action(async (file) => { - console.log(await new Seance().checkScissors(file)) + console.log(await seance.checkScissors(file)) }) program.parse(process.argv)