tic2bar
Version:
Command-line application that uploads TIC-80 cartridges to IPFS and encodes url into barcodes & QR Codes
446 lines (387 loc) • 16.6 kB
JavaScript
;
/* Imports */
const prompt = require('prompt-sync')(); /* terminal prompt functions */
const fs = require('fs'); /* file system */
const path = require('path'); /* path system */
const QRCode = require('qrcode'); /* qr code encoder */
const qrdecode = require('node-qrdecode'); /* qr code decoder */
const JSON5 = require('json5') /* json5 parser */
const jsbarcode = require('jsbarcode'); /* barcode creation */
const Quagga = require('quagga').default; /* barcode scanning and decoding */
const { createCanvas, registerFont } = require('canvas'); /* canvas to draw barcode image */
const ipfs_mini = require('ipfs-mini'); /* ipfs api */
const ipfs = new ipfs_mini({host: 'ipfs.infura.io', port: 5001, protocol: 'https'}) /* connecting to infura gateway */
/* config (these variables will be overwritten by the config and are just default values) */
var debug_mode = false; /* debug mode */
var timeout_countdown = 30000; /* how long in milisec till timeout error is thrown */
var type = 'qr'; /* encoder and decoder type qr or 128 (for code128 barcode) */
/* log function to log messages to the console */
function log(msg, type){
switch(type){
case 'info':
console.log('\x1b[0m' + 'INFO: ' + msg);
break;
case 'error':
console.log('ERROR: \x1b[31m' + msg );
process.exit(1);
case 'debug': if(debug_mode) console.log('DEBUG: ' + "\x1b[36m" + msg + '\x1b[0m');
break;
case 'success':
console.log('\x1b[32m' + msg + '\x1b[0m');
break;
default: console.log(msg);
break;
}
}
/* create .tic file.. takes in data and file_path to save the .tic to */
function tic_create(data, file_path){
log('Writing data', 'info');
fs.writeFileSync(file_path, data, 'binary');
log("Successfully written cartridge to: " + file_path, 'success');
log('Finished! ʕ•ᴥ•ʔ', 'success');
process.exit(1);
}
/* encode file to barcode (expected input_path (.tic), output_path and game_title) */
function bar_encode(input_path, output_path, game_title){
/* load file */
log('Loading: ' + input_path, 'info');
let file = fs.readFileSync(input_path, 'binary');
/* ipfs upload */
log('Uploading cartridge to IPFS Gateway', 'info');
let hash = '';
ipfs.add(file, async (error, hash_) => {
if (error) {
return log('Upload to IPFS failed (' + error + ')', 'error');
}
hash = hash_;
log('Successfully uploaded cartridge to: ' + 'https://ipfs.infura.io/ipfs/' + hash_, 'success');
})
/* start connection timeout timer */
let timeout_timer = setTimeout(function(){
/* connection timed out */
log('Connection timeout. Check your internet connection or try again later!', 'error');
}, timeout_countdown)
/* check if hash data has arrived every second */
setInterval(function(){
if(hash != ''){
/* hash data has arrived => create bar code and stop timers */
clearTimeout(this);
clearTimeout(timeout_timer);
registerFont('assets/tic-80-wide-font.ttf', { family: 'Tic' });
/* create canvas */
var canvas = createCanvas(200, 200);
/* create bar code */
log('Creating barcode', 'info');
jsbarcode(canvas, hash, {
font: 'Tic',
fontSize: 16,
textMargin: 10,
height: 200,
width: 1,
margin: 30,
marginTop: 80,
background: '#deeed6',
lineColor: '#140c1c',
text: input_path.split('/').pop().toLowerCase() + ' - tic-80 cartridge'
});
/* save file as jpg or png*/
const out = fs.createWriteStream(output_path)
if (output_path.split('.').pop() == 'jpg'){
const stream = canvas.createJPEGStream()
stream.pipe(out)
out.on('finish', () => {
/* finished */
log('Barcode was created and saved to: ' + output_path, 'success');
log('Finished! ʕ•ᴥ•ʔ', 'success');
process.exit(1);
})
}else{
const stream = canvas.createPNGStream()
stream.pipe(out)
out.on('finish', () => {
/* finished */
log('Barcode was created and saved to: ' + output_path, 'success');
log('Finished! ʕ•ᴥ•ʔ', 'success');
process.exit(1);
})
}
}
}, 1000)
}
/* decode barcode (expected input_path to .png or .jpg, output_path to .tic and game_title) */
function bar_decode(input_path, output_path){
/* decoding barcode */
log('Starting to decode barcode', 'info');
Quagga.decodeSingle({
src: input_path,
numOfWorkers: 0, /* Needs to be 0 when used within node */
inputStream: {
size: 2000 /* restrict input-size to be 2000px in width (long-side) */
},
decoder: {
readers: ["code_128_reader"] /* list of active readers */
},
}, function(result) {
if(result.codeResult) {
/* success of decoding */
let hash = result.codeResult.code;
log('\n');
log('Successfully decoded barcode! hash: ' + hash, 'success');
/* download .zip from ipfs */
log('Downloading cartridge data from IPFS Gateway', 'info');
if(hash == null){
log('Data is corrupted. Check your barcode!', 'error');
}
let data = '';
ipfs.cat(hash, (error, result) => {
if (error) {
log('There was an error while downloading from IPFS: ' + error, 'error');
} else {
/* success of downloading */
log('Successfully downloaded cartridge data!', 'success');
data = result;
}
});
/* start connection timeout timer */
let timeout_timer = setTimeout(function(){
/* connection timed out */
log('Connection timeout. Check your internet connection or try again later!', 'error');
}, timeout_countdown)
/* check if zip data has arrived every second */
setInterval(function(){
if(data != ''){
/* zip data has arrived => create .tic and stop timers */
tic_create(data, output_path);
clearTimeout(this);
clearTimeout(timeout_timer);
}
}, 1000)
} else {
log("Didn't detect a barcode. Check your input file!", 'error');
}
});
}
/* encode file to qr code (expected input_path (.tic), output_path) */
function qr_encode(input_path, output_path){
/* load file */
log('Loading: ' + input_path, 'info');
let file = fs.readFileSync(input_path, 'binary');
/* ipfs upload */
log('Uploading cartridge to IPFS Gateway', 'info');
let hash = '';
ipfs.add(file, async (error, hash_) => {
if (error) {
return log('Upload to IPFS failed (' + error + ')', 'error');
}
hash = hash_;
log('Successfully uploaded cartridge to: ' + 'https://ipfs.infura.io/ipfs/' + hash_, 'success');
})
/* start connection timeout timer */
let timeout_timer = setTimeout(function(){
/* connection timed out */
log('Connection timeout. Check your internet connection or try again later!', 'error');
}, timeout_countdown)
/* check if hash data has arrived every second */
setInterval(function(){
if(hash != ''){
/* hash data has arrived => create qr code and stop timers */
clearTimeout(this);
clearTimeout(timeout_timer);
/* create canvas */
var canvas = createCanvas(300, 300);
/* create qr code */
log('Creating QR code', 'info');
var opts = {
errorCorrectionLevel: 'H',
quality: 0.3,
margin: 10,
scale: 4,
color: {
dark: "#140c1c",
light: "#deeed6"
}
}
QRCode.toCanvas(canvas, 'https://ipfs.infura.io/ipfs/' + hash, opts, function (error) {
if (error) log(error, 'error');
log('Successfully encoded QR Code!','success');
/* save file as jpg or png*/
const out = fs.createWriteStream(output_path);
if (output_path.split('.').pop() == 'jpg'){
const stream = canvas.createJPEGStream();
stream.pipe(out)
out.on('finish', () => {
/* finished */
log('QR Code was created and saved to: ' + output_path, 'success');
log('Finished! ʕ•ᴥ•ʔ', 'success');
process.exit(1);
})
}else{
const stream = canvas.createPNGStream()
stream.pipe(out)
out.on('finish', () => {
/* finished */
log('QR Code was created and saved to: ' + output_path, 'success');
log('Finished! ʕ•ᴥ•ʔ', 'success');
process.exit(1);
})
}
})
}
}, 1000)
}
/* decode qr code (expected input_path to .png or .jpg, output_path to .tic) */
function qr_decode(input_path, output_path){
/* decoding qr code */
log('Starting to decode QR code', 'info');
qrdecode(input_path).then(function(url){
if(url == null){
log('Data is corrupted. Check your QR Code!', 'error');
}
log(url, 'debug');
/* success of decoding */
log('\n');
log('Successfully decoded QR code! url: ' + url, 'success');
/* download .tic from ipfs */
log('Downloading cartridge data from IPFS Gateway', 'info');
let data_ = '';
ipfs.cat(url.split('/').pop(), (error, result) => {
if (error) {
log('There was an error while downloading from IPFS: ' + error, 'error');
} else {
/* success of downloading */
log('Successfully downloaded cartridge data!', 'success');
data_ = result;
}
});
/* start connection timeout timer */
let timeout_timer = setTimeout(function(){
/* connection timed out */
log('Connection timeout. Check your internet connection or try again later!', 'error');
}, timeout_countdown)
/* check if zip data has arrived every second */
setInterval(function(){
if(data_ != ''){
/* zip data has arrived => create .tic and stop timers */
tic_create(data_, output_path);
clearTimeout(this);
clearTimeout(timeout_timer);
}
}, 1000)
});
}
/* load json5 config in current directory */
function load_config(){
/* reading config file and parsing it through json5 */
let config_file = fs.readFileSync(path.resolve(__dirname, 'config.json5'));
let config = JSON5.parse(config_file);
/* assigning variables */
debug_mode = config.debug_mode;
timeout_countdown = config.timeout_countdown;
type = config.type;
/* log config settings */
log('debug_mode: ' + debug_mode, 'debug');
log('encoding / decoding type: ' + type, 'info');
log('timeout_countdown: ' + timeout_countdown, 'debug');
log('\n');
}
/* main function (gets run when application starts) */
function main(){
/* declare variables */
let input_path = '';
let output_path = '';
log("\x1b[35m" + 'sleepycharlyy/tic2bar.js' + "\x1b[0m" + '\n');
/* load config */
load_config();
/* main loop */
while(true){
switch(prompt('Do you want to encode or decode? (e / d) >> ').toLowerCase()) {
case 'd':
log('\x1b[36m' + '\nYou selected decode ');
/* check if bar code or qr code */
if(type == '128'){
input_path = prompt('What is the path to the barcode you want to decode? (.png or .jpg) >> ');
} else {
input_path = prompt('What is the path to the QR code you want to decode? (.png or .jpg) >> ');
}
/* check if file path is empty */
if (input_path == undefined || input_path == " " || input_path == null){
log("The file path can't be empty!", 'error');
}
/* check if file is not a .png or .jpg file */
if (input_path.split('.').pop() != 'png' && input_path.split('.').pop() != 'jpg') {
/* is another file type */
log('This filetype is unrecognized, use .png or .jpg files!', 'error');
}
/* check if file exists */
if(!fs.existsSync(input_path)) {
log(input_path);
log("This file doesn't exist!", 'error');
}
output_path = prompt('What is the path you want to export the .tic cartridge to? >> ');
/* check if file path is empty */
if (output_path == undefined || output_path == " " || output_path == null){
log("The file path can't be empty!", 'error');
}
/* check if file is not a .tic file */
if (output_path.split('.').pop() != 'tic') {
/* is another file type */
log('This filetype is unrecognized, use .tic files!', 'error');
}
/* decode file */
log('\n');
/* check if bar code or qr code */
if(type == '128'){
bar_decode(input_path, output_path);
}else{
qr_decode(input_path, output_path);
}
break;
case 'e':
log('\x1b[36m' + '\nYou selected encode ');
input_path = prompt('What is the path to the .tic game cartridge you want to encode? >> ');
/* check if file_path is empty */
if (input_path == undefined || input_path == " " || input_path == null){
log("The file path can't be empty!", 'error');
}
/* check if file is not a .tic file */
if (input_path.split('.').pop() != 'tic') {
/* is another file type */
log('This filetype is unrecognized, use .tic files!', 'error');
}
/* check if file exists */
if(!fs.existsSync(input_path)) {
log("This file doesn't exist!", 'error');
}
/* check if bar code or qr code */
if(type == '128'){
output_path = prompt('Where do you want to export the barcode to? (.png or .jpg) >> ');
} else {
output_path = prompt('Where do you want to export the QR code to? (.png or .jpg) >> ');
}
/* check if file path is empty */
if (output_path == undefined || output_path == " " || output_path == null){
log("The file path can't be empty!", 'error');
}
/* check if file is not a .png or .jpg file */
if (output_path.split('.').pop() != 'png' && output_path.split('.').pop() != 'jpg') {
/* is another file type */
log('This filetype is unrecognized, use .png or .jpg files!', 'error');
}
/* encode file */
log('\n');
/* check if bar code or qr code */
if(type == '128'){
bar_encode(input_path, output_path);
}else{
qr_encode(input_path, output_path);
}
break;
default:
/* when the user didn't input 'e' or 'd' the whole prompt repeats */
continue;
}
break;
}
}
main();