|
/********************************************************* |
|
* |
|
* @TITLE: Simplenote and Boostrap Note Syncronization |
|
* |
|
* @DESCRIPTION: Keeps Simplenote and Boostnote insync |
|
* |
|
* @INSTRUCTIONS: |
|
* - Set Resophnotes to save as Plain Text. |
|
* - Change csonDir and txtDir accordingly |
|
* - Change csonObj.folder = '94b8c2e8acf3448698d7' with |
|
* foldername determined by a sample cson on your system. |
|
* - Run npm install |
|
* |
|
* Run in sync mode and make changes and watch output. |
|
* |
|
* You can run duplicates mode which will use |
|
* formulas to determine duplicate Boostnote notes and |
|
* ask if you want to delete. |
|
* |
|
* counter mode just shows how many notes each application has |
|
* |
|
* |
|
* @TODO: |
|
* - Allow adding of new txt notes without restart of program |
|
* - Remove resophnotes dependency by using Simplenote directly. |
|
* - Cleanup duplicate reduntant code |
|
* |
|
* !!!WARNING!!! Boostrap stores references and notes in memory |
|
* after making external changes you should do a cmd+r to reload |
|
* boostrap or you may encounter note loss and/or duplication. |
|
* |
|
* @AUTHOR: Eric Soukenka |
|
* @DATE: April 20th 2017 |
|
* |
|
********************************************************* */ |
|
var parseJson = require('parse-json'); |
|
var fs = require('fs'); |
|
var path = require('path'); |
|
var CSON = require('cson'); |
|
var crypto = require('crypto'); |
|
var sanitize = require("sanitize-filename"); |
|
var chokidar = require('chokidar'); |
|
var stringSimilarity = require('string-similarity'); |
|
var query = require('cli-interact').getYesNo; |
|
var readlineSync = require('readline-sync'); |
|
var exec = require("child_process").exec |
|
var spawn = require("child_process").spawn |
|
var moment = require("moment") |
|
|
|
|
|
duplicates = () => { |
|
noteCounter(); |
|
dupCounter = 0; |
|
answer = ''; |
|
fs.readdirSync(csonDir).forEach(function(csonFile1) { |
|
if (path.extname(csonFile1) == '.cson') { |
|
status = 'false'; |
|
try { |
|
csonObj1 = CSON.parse(fs.readFileSync(csonDir + '/' + csonFile1).toString()); |
|
loop = true; |
|
fs.readdirSync(csonDir).forEach(function(csonFile2) { |
|
if (loop) { |
|
if (path.extname(csonFile2) == '.cson') { |
|
csonObj2 = CSON.parse(fs.readFileSync(csonDir + '/' + csonFile2).toString()); |
|
x = stringSimilarity.compareTwoStrings(csonObj1.content, csonObj2.content); |
|
if (x > 0.9) { |
|
if (status == 'false') { |
|
status = 'checking'; |
|
} else if (status = 'checking') { |
|
status = 'duplicate'; |
|
if (answer != 'a') { |
|
answer = readlineSync.question(`Rating: ${x}\n(1) ${csonObj1.title}\n(2) ${csonObj2.title}\n(3) skip\n(a) all\n(l) log\nChoose 1 | 2 | 3 | a | l\n`); |
|
} |
|
if (answer == 1) { |
|
fs.unlink(`${csonDir}/${csonFile1}`); |
|
console.log(`DELETING: ${csonDir}/${csonFile1} ${csonObj1.title}`); |
|
} else if (answer == 2) { |
|
fs.unlink(`${csonDir}/${csonFile2}`); |
|
console.log(`DELETING: ${csonDir}/${csonFile2} ${csonObj2.title}`); |
|
} else if (answer == 3) { |
|
console.log(`SKIPPING`); |
|
} else if (answer == 'a') { |
|
console.log(`DELETING: ${csonDir}/${csonFile2} ${csonObj2.title}`); |
|
fs.unlink(`${csonDir}/${csonFile2}`); |
|
} else if (answer == 'l') { |
|
console.log(`LOGGING: ${csonDir}/${csonFile2} ${csonObj2.title}`); |
|
} |
|
loop = false; |
|
dupCounter++; |
|
} |
|
} |
|
} |
|
} |
|
}) |
|
} catch (e) { |
|
console.log('ERROR'); |
|
} |
|
} |
|
}) |
|
console.log(`Duplicates: ${dupCounter}`) |
|
noteCounter(); |
|
} |
|
|
|
similiar = (file) => { |
|
if (path.extname(file) == '.cson' && fs.existsSync(file)) { |
|
status = 'new'; |
|
csonObj = CSON.parse(fs.readFileSync(file).toString()); |
|
fs.readdirSync(txtDir).forEach(function(txtFile) { |
|
if (path.extname(txtFile) == '.txt') { |
|
txtContent = fs.readFileSync(txtDir + '/' + txtFile).toString() |
|
x = stringSimilarity.compareTwoStrings(csonObj.content, txtContent); |
|
if (x > 0.9) { |
|
if (status == 'new') { |
|
status = 'existing'; |
|
} else if (status = 'existing') { |
|
console.log(`DUPLICATE: "${csonObj.title.trim()}" "${txtContent.split('\n')[0]}" - ${x}`); |
|
} |
|
} |
|
} |
|
}) |
|
} |
|
} |
|
|
|
processor = (file) => { |
|
if (path.extname(file) == '.cson') { |
|
readFile = path.basename(file); |
|
filename = readFile.replace(/\.[^/.]+$/, ""); |
|
writeFile = txtDir + '/' + filename + '.txt'; |
|
csonObj = CSON.parse(fs.readFileSync(file).toString()) |
|
if(csonObj.content) { |
|
if (fs.existsSync(writeFile)) { |
|
if (fs.readFileSync(writeFile).toString().trim() != csonObj.content.trim()) { |
|
console.log(`MODIFED: cson ${csonObj.title.trim()}`) |
|
fs.writeFileSync(writeFile, csonObj.content.trim() + '\n'); |
|
} |
|
} else { |
|
console.log(`ADDED: cson ${csonObj.title.trim()}`) |
|
fs.writeFileSync(writeFile, csonObj.content.trim() + '\n'); |
|
} |
|
} else { |
|
console.log(`ERROR: ${file} does not have content and we do not support snippets yet2`) |
|
} |
|
} |
|
if (path.extname(file) == '.txt') { |
|
readFile = path.basename(file); |
|
filename = readFile.replace(/\.[^/.]+$/, ""); |
|
writeFile = csonDir + '/' + filename + '.cson'; |
|
if (fs.existsSync(writeFile)) { |
|
csonObj = CSON.parse(fs.readFileSync(writeFile).toString()) |
|
if (fs.readFileSync(file).toString().trim() != csonObj.content.trim()) { |
|
csonObj.content = fs.readFileSync(file).toString().trim() |
|
csonObj.title = fs.readFileSync(file).toString().trim().split('\n')[0] + '\n'; |
|
csonObj.updatedAt = moment().toISOString() |
|
if (csonObj.title.substring(0, 1) == '#') { |
|
csonObj.title = csonObj.title.substring(1) |
|
} |
|
console.log(`MODIFIED: txt ${fs.readFileSync(file).toString().trim().split('\n')[0]}`); |
|
fs.writeFileSync(writeFile, CSON.stringify(csonObj)); |
|
} |
|
} else { |
|
console.log(`SKIPPING: NEW NOTE: ${writeFile}`); |
|
console.log(`NOTE: will be created upon restart of this program`); |
|
} |
|
} |
|
} |
|
|
|
noteCounter = () => { |
|
var csonCount = 0; |
|
var txtCount = 0; |
|
|
|
fs.readdirSync(csonDir).forEach(function(readfile) { |
|
if (path.extname(readfile) == '.cson') { |
|
csonCount++; |
|
} |
|
}); |
|
fs.readdirSync(txtDir).forEach(function(readfile2) { |
|
if (path.extname(readfile2) == '.txt') { |
|
txtCount++; |
|
} |
|
}); |
|
console.log(`COUNTER: Boostnote: ${csonCount}`); |
|
console.log(`COUNTER: Simplenote: ${txtCount}`); |
|
} |
|
|
|
clean = () => { |
|
noteCounter(); |
|
console.log("Running clean mode to ensure clean start"); |
|
fs.readdirSync(txtDir).forEach(function(readfile) { |
|
filename = readfile.split('.').shift() |
|
extension = readfile.split('.').pop() |
|
if (extension == 'txt') { |
|
if (!fs.existsSync(csonDir + '/' + filename + '.cson')) { |
|
csonObj = {} |
|
csonObj.type = 'MARKDOWN_NOTE' |
|
csonObj.folder = '94b8c2e8acf3448698d7' |
|
csonObj.tags = [] |
|
csonObj.isStarred = false |
|
csonObj.createdAt = moment().toISOString() |
|
csonObj.updatedAt = moment().toISOString() |
|
csonObj.title = fs.readFileSync(txtDir + '/' + readfile).toString().trim().split('\n')[0]; |
|
csonObj.content = fs.readFileSync(txtDir + '/' + readfile).toString().trim(); |
|
if(csonObj.title && csonObj.content) { |
|
filename = crypto.randomBytes(10).toString('hex'); |
|
writeFile = csonDir + '/' + filename + '.cson'; |
|
fs.writeFileSync(writeFile, CSON.stringify(csonObj)) |
|
fs.renameSync(txtDir + '/' + readfile, txtDir + '/' + filename + '.txt'); |
|
console.log(`WROTE: ${writeFile} - ${csonObj.title}`); |
|
console.log(`RENAMED: ${txtDir + '/' + readfile} --> ${txtDir + '/' + filename + '.txt'}`); |
|
} else { |
|
fs.unlink(`${txtDir}/${readfile}`); |
|
console.log(`Removed invalid/empty note found at ${txtDir}/${readfile}`); |
|
} |
|
} |
|
} |
|
}); |
|
fs.readdirSync(csonDir).forEach(function(readfile) { |
|
filename = readfile.split('.').shift() |
|
extension = readfile.split('.').pop() |
|
if (extension == 'cson') { |
|
if (!fs.existsSync(txtDir + '/' + filename + '.txt')) { |
|
csonObj = CSON.parse(fs.readFileSync(csonDir + '/' + readfile)) |
|
if(csonObj.content) { |
|
writeFile = txtDir + '/' + filename + '.txt'; |
|
fs.writeFileSync(writeFile, csonObj.content.trim() + '\n'); |
|
console.log(`WROTE: ${writeFile} - ${csonObj.title}`); |
|
} else { |
|
console.log(`ERROR: ${csonDir}/${readfile} does not have content and we do not support snippets yet`) |
|
} |
|
} |
|
} |
|
}); |
|
noteCounter(); |
|
} |
|
|
|
var mode = process.argv[2]; |
|
var txtDir = '/Users/esouke/Documents/Notes'; |
|
var csonDir = '/Users/esouke/Boostnote/notes'; |
|
if (mode == '-h' || mode == undefined) { |
|
console.log('This will syncronize Boostrap and Simplenote'); |
|
console.log(`node ${process.argv[1]} sync`); |
|
console.log(`node ${process.argv[1]} duplicates`); |
|
console.log(`node ${process.argv[1]} counter`); |
|
console.log(`node ${process.argv[1]} clean`); |
|
} else if (mode == 'counter') { |
|
noteCounter(); |
|
} else if (mode == 'clean') { |
|
clean(); |
|
} else if (mode == 'duplicates') { |
|
duplicates(); |
|
} else if (mode == 'sync') { |
|
//boost-to-simple |
|
csonWatcherReady = false; |
|
//var csonWatcher = chokidar.watch(csonDir).on('all', (event, path) => { |
|
var csonWatcher = chokidar.watch(csonDir, { |
|
alwaysState: true, |
|
awaitWriteFinish: { |
|
stabilityThreshold: 5000, |
|
pollInterval: 100 |
|
}, |
|
}).on('all', (event, path) => { |
|
if (csonWatcherReady) { |
|
noteCounter(); |
|
similiar(path); |
|
} |
|
if (event != 'unlink') { |
|
processor(path) |
|
} else { |
|
filename = path.replace(/.*\//, ''); |
|
filename = filename.replace(/\.[^/.]+$/, ""); |
|
filename = txtDir + '/' + filename + '.txt' |
|
if (fs.existsSync(filename)) { |
|
console.log(`DELETING: ${fs.readFileSync(filename).toString().trim().split('\n')[0]}`); |
|
fs.unlink(filename); |
|
} |
|
} |
|
}); |
|
csonWatcher.on('ready', () => csonWatcherReady = true); |
|
//end boost-to-simple |
|
|
|
//simple-to-boost |
|
txtWatcherReady = false; |
|
var txtWatcher = chokidar.watch(txtDir, { |
|
alwaysState: true, |
|
awaitWriteFinish: { |
|
stabilityThreshold: 2000, |
|
pollInterval: 100 |
|
}, |
|
}).on('all', (event, path) => { |
|
if (txtWatcherReady) { |
|
similiar(path); |
|
noteCounter(); |
|
} |
|
if (event != 'unlink') { |
|
processor(path) |
|
} else { |
|
filename = path.replace(/.*\//, ''); |
|
filename = filename.replace(/\.[^/.]+$/, ""); |
|
filename = csonDir + '/' + filename + '.cson' |
|
if (fs.existsSync(filename)) { |
|
console.log(`DELETING: ${filename}`); |
|
fs.unlink(filename); |
|
} |
|
} |
|
}); |
|
txtWatcher.on('ready', () => txtWatcherReady = true); |
|
//end simple-to-boost |
|
clean(); |
|
console.log(`Watching ${txtDir} and ${csonDir}`) |
|
} |