My fs.writeFile won't log anything into my JSON file? - node.js

I am creating a Discord.js Bot currently, and I am trying to create a prefix and have it log into the JSON file, however, using fs.writeFile, it will not log anything into the file, is there something I am doing wrong?
try {
//Checks User Permissions
if(!message.member.hasPermission("MANAGE_SERVER")) return;
if(!args[0]) return;
//Calls the prefixes.json file
let prefixes = require('../prefixes.json');
//Fetches the prefix they want to change
prefixes[message.guild.id] = {
prefixes: args[0]
};
//Logs the Prefix for the Guild
fs.writeFile("../prefixes.json", JSON.stringify(prefixes), (err) => {
if(err) console.log(err);
})
//Main Embed Code
let embed = new Discord.MessageEmbed()
.setAuthor('Prefix Changed!')
.setDescription(`The new prefix for ${message.guild.name} is set to \`${args[0]}\`!`)
.setTimestamp()
.setColor(colours.main)
message.channel.send(embed)
} catch(e) {
let embed = new Discord.MessageEmbed()
.setColor(colours.red)
.setDescription(`Error:\`\`\`${e.message}\`\`\``)
message.channel.send(embed)
}};

I think you want to use fs.writeFileSync(), it's just easier unless the async version is absolutely needed. If you are trying to perform an operation on that file after fs.writeFile() is called, it's done asynchronously, so you'd have to make sure the writeFile() is done before resuming any other operations relevant to that file.

Related

How to split my chat commands into differents files?

I'm creating a basic nodejs based chatbot for discord using discordjs.
In my main script, when a 'message' event is sent, the bot checks if the message match with a specific command and, in this case call it. The functions corresponding to these commands are in methods in a "TextCommand" class, inported in the main script, but the TextCommand file is getting too big I think...
I tried to split each command in one file and export it, but I can't import it as methods into TextCommand class.
index.js
const TextCommand = require('./src/commands.js');
client.on('message', (msg) => {
if (!msg.author.bot) {
let command;
let reg;
let prefix;
if (msg.guild != undefined){ // Guild commands
db.query(`SELECT prefix FROM guilds WHERE discord_id=${msg.guild.id};`, (err, result) => {
if (err) throw err;
prefix = result[0].prefix
command = new TextCommand(prefix, msg)
reg = new RegExp('^' + prefix, 'i')
if (msg.content.startsWith(prefix + 'help')) {
command.help();
} else if (msg.content.startsWith(prefix + 'ping')) {
command.ping();
} else if (msg.content.startsWith(prefix + 'search')) {
command.search();
TextCommand file
module.exports = class TextCommand {
constructor(prefix, msg){
this.message = msg;
this.prefix = prefix;
}
ping(){
this.message.channel.send('pong !');
}
search(){
let search = this.message.content.slice(this.prefix.length + 'search '.length).replace(/ /g, '+');
this.message.channel.send(`http://google.com/search?q=${search}`);
}
I do not understand how to use class extands... but maybe it's a part of the solution, I would include my functions into TextCommands just wrinting them somewhere else (and export it)
Instead of using a class to hold every command you own, try exporting commands.
For instance, in the ready event, you'll want to search the commands folder for command files, which may follow the format of <command name>.js, so a command !foo would be foo.js in the folder commands.
To find all the files in the commands folder:
const { promisify } = require('util');
const readdir = promisify(require('fs').readdir);
client.commands = new Map();
client.on('ready', async () => {
readdir('./commands/', (error, files) => {
if (error) throw error;
files.forEach(file => {
if (!file.endsWith('.js')) return; // make sure the file is what you are looking for
try {
const properties = require(`./commands/${file}`);
client.commands.set(properties.help.name, properties);
} catch (err) {
throw err;
}
});
}
});
This basically defines a Map, extended from the client object called client.commands. This map will contain the command properties, which is used to run the command later. The readdir reads the directory of ./commands/ for any file (using the module fs), and tries to filter them so they are only javascript files (ending with .js), and then adds the commands to the map with its properties.
Later, on the message event, you'll want to test to see if the message content is the command that you have coded:
client.on('message', async (message) => {
if (!message.guild) return; // exit if the message does not have a guild
if (message.author.bot) return; // exit if the message author is a bot
// ... your database code to get the prefix ...
const args = message.content.slice(prefix.length).trim().split(/ +/g).call(Function.prototype.call, String.prototype.trim);
const command = args.shift().toLowerCase();
const cmd = client.commands.get(command);
if (!cmd) return; // the message is not a command we know of
cmd.run(client, message, args); // run the command with client object, message object and args array
});
This code does the basic checks, to make sure the bot is only responding to a user in a guild channel (you may also want to check if the message is from Discord: message.system).
After, it'll separate the content after the prefix into an array, splitting on a space, and then trimming every element of leading and trailing white space.
It'll then assign the first element of the args array to the constant command, to then be checked if that command exists in our map.
If it does, it'll run the command (apart of the module.exports), passing along the client object, message object, and the arguments array.
Finally, in a command file, such as foo.js, you'll need to have some important code to make sure the bot registers said the file is a command we want to run. This is:
module.exports.run = async (client, message, args) => {
// ... command logic
message.channel.send('Hello!');
};
module.exports.help = {
name: 'foo'
};
This is the basic layout of the command file the system is able to read. The first line exports the run process of the command, which is an async function. It also exports the help object, which includes the name of the command. It's best that this is the same as the file name since this name is what the command is responding to.
In my opinion, this is a better method than creating the class holding all the commands, but I'll keep an open mind!

JSON file can't be modified

I am currently creating a web that uses a variable that I can store in a JSON format. My plan is to modify the value of the JSON every time there's a connection to a certain route. The problem is it just won't write.
I have tried to use fs.writeFile and fs.writeFileSync but none of them seem to work.
// Code I Have tried
const kwitansi = require('./no_kwitansi.json')
app.get('', async (req, res) => {
kwitansi.no_kwitansi += await 1
await fs.writeFile('../no_kwitansi.json', JSON.stringify(kwitansi, null, 2), function (e) {
if (e) {
throw new Error
} else {
console.log('Wrote to file')
}
})
await console.log(kwitansi)
await res.send(kwitansi)
})
// An Example of my JSON File
{
"no_kwitansi":4
}
You are trying to write to a place where you do not have permission. Note that you opened ./no_kwitansi.json, but you are trying to write to ../no_kwitansi.json (one directory back). If you are sure you can replace the original file, remove the extra . in the write line.
If the error persists, you also need to be sure that you have the proper permissions to write the file. If you are using *nix or mac, you can check this link.

How to check whether a directory exists in node.js?

I'd like to save files received from json object in a REST API app and here is the code:
router.post('/addphoto', checkAuth, (req, res)=> {
let filename = Math.floor(Math.random() * 100000);
let dir = './uploads/' + req.user.id;
//Not sure about this
if (!fs.existsSync(dir)){
fs.mkdirSync(dir);
}
base64String = req.body.file;
let base64Image = base64String.split(';base64,').pop();
let filePath = dir + "/" + filename
fs.writeFile( filePath, base64Image, {encoding: 'base64'}, function(err) {
console.log('File created');
});
...
It does the job but I've read that existsSync is deprecated, and also I'm not sure if it's a good idea to use sync code inside a router which is async by nature.
So I'm wondering what is the idiomatic way to do so in such circumstances?
You can use access
fs.access(myDir, function(err) {
if (err && err.code === 'ENOENT') {
fs.mkdir(myDir); //Create dir in case not found
}
});
I've read that existsSync is deprecated
It isn't. See the manual:
fs.exists() is deprecated, but fs.existsSync() is not. The callback parameter to fs.exists() accepts parameters that are inconsistent with other Node.js callbacks. fs.existsSync() does not use a callback.
I'm not sure if it's a good idea to use sync code inside a router which is async by nature.
There's nothing intrinsically wrong about doing something synchronous inside something that is asynchronous — most JS is synchronous — but it does mean that the feature would block the event loop while looking at the file system, and looking at the file system is a relatively time-consuming operation, so it wouldn't be good for performance.
Your code might not need that level of performance, but that's a judgement call we can't make for you.
exists is right next to existsSync in the manual and says:
Deprecated: Use fs.stat() or fs.access() instead.
So pick one of those.
access has an example:
// Check if the file exists in the current directory.
fs.access(file, fs.constants.F_OK, (err) => {
console.log(`${file} ${err ? 'does not exist' : 'exists'}`);
});
You can use existsSync as it's not deprecated. it's exists that got deprecated. I've attached a screenshot and link below so you can use it without any problem.
link->
https://nodejs.org/api/fs.html#fs_fs_existssync_path
image->
Modern async/await way
const isDirExist = async path => await fs.promises.access(path).then(()=>true).catch(()=>false);
Using
const isDirExist = async path => await fs.promises.access(path).then(()=>true).catch(()=>false);
(async () => {
console.log(await isDirExist('/my/path/'));
})()
From official docs https://nodejs.org/api/fs.html#fspromisesaccesspath-mode
fs.access will throw an error if file don't exist. So you will not have a boolean to check if file exist or not, like java does from ancient ages. You should use a try/catch:
var isDirExist = false;
try{
await fs.promises.access("/foo/bar");
isDirExist = true;
}catch(e){
isDirExist = false;
}
If this looks problematic, official docs says:
Using fsPromises.access() to check for the accessibility of a file before calling fsPromises.open() is not recommended. Doing so introduces a race condition, since other processes may change the file's state between the two calls. Instead, user code should open/read/write the file directly and handle the error raised if the file is not accessible.
If you use node-fs-extra you can utilize...
fs.ensureDir(dir[,options][,callback])
Which by definition...
Ensures that the directory exists. If the directory structure does not exist, it is created.
See also fs.ensureDirSync
The below code will check if the destination exists. If it doesn't exist, it'll create the destination as a directory. It will also create the parent directories if they don't exist (because of recursive: true). It does not use Sync functions and will not block requests if used in a web server.
const fs = require('fs');
const targetDirectory = 'your/path/here';
fs.mkdir(targetDirectory, { recursive: true }, (error) => {
if (!error) {
console.log('Directory successfully created, or it already exists.');
return;
}
switch (error.code) {
case 'EEXIST':
// Error:
// Requested location already exists, but it's not a directory.
break;
case 'ENOTDIR':
// Error:
// The parent hierarchy contains a file with the same name as the dir
// you're trying to create.
break;
default:
// Some other error like permission denied.
console.error(error);
break;
}
});
See: mkdir docs.

Nodejs fs file handling: Getting error type in order to handle it more efficient

I have the very simple function reading a json file:
const loadJsonContentFromFile=function(path,callback){
fs.readFile(path, 'utf8', function (err, data) {
if (err){
return callback(err)
}
try {
const obj = JSON.parse(data);
return callback(null,obj);
} catch(error){
return callback(error);
}
});
}
But I want further indications on the err object regarding the file reading. In other words I want to know why the fs.readFile failed to read the file in order to provide into the callback a tailor made response message instead of the ones that nodejs by default provides, for example if sustem user does not have the permissions to read the file I want to provide a message like:
Your user has not the correct wrights to read the file ./somefile.txt please run sudo chmod +r ./somefile.txt in order to give the right permissions.
Whilst if the file does not exists I want to provide an error message like:
The file ./somefile.txt does not exist
It sounds trivial but I think is a good example to fine-handle an error that has been returned. In order to achieve that I want to be able to identify the error that readFile callback accepts as an argument.
In php I would use the Error object's class name in order to figure out what type of error is. But in Node.js how I can do that?
NOTE:
I know that an approach to the problem's solution is to check before reading the file if the file exists and has the correct permissions. But I believe that is not the only one solution so I am looking for an alternate one on the existing problem.
You can check against err.code and return a custom error that suits your needs.
const loadJsonContentFromFile = function(path,callback) {
fs.readFile(path, 'utf8', function(err, data) {
if(err) {
if(err.code === 'EACCESS') {
return callback(
// Or create your custom error: ForbiddenError...
new Error('Your user has not the correct permissions to read the file...')
);
}
if(err.code === 'ENOENT') {
return callback(
new Error(`The file ${path} does not exist`)
);
}
}
/** ... **/
});
}
You can check the docs for more error codes.

Creating a file only if it doesn't exist in Node.js

We have a buffer we'd like to write to a file. If the file already exists, we need to increment an index on it, and try again. Is there a way to create a file only if it doesn't exist, or should I just stat files until I get an error to find one that doesn't exist already?
For example, I have files a_1.jpg and a_2.jpg. I'd like my method to try creating a_1.jpg and a_2.jpg, and fail, and finally successfully create a_3.jpg.
The ideal method would look something like this:
fs.writeFile(path, data, { overwrite: false }, function (err) {
if (err) throw err;
console.log('It\'s saved!');
});
or like this:
fs.createWriteStream(path, { overwrite: false });
Does anything like this exist in node's fs library?
EDIT: My question isn't if there's a separate function that checks for existence. It's this: is there a way to create a file if it doesn't exist, in a single file system call?
As your intuition correctly guessed, the naive solution with a pair of exists / writeFile calls is wrong. Asynchronous code runs in unpredictable ways. And in given case it is
Is there a file a.txt? — No.
(File a.txt gets created by another program)
Write to a.txt if it's possible. — Okay.
But yes, we can do that in a single call. We're working with file system so it's a good idea to read developer manual on fs. And hey, here's an interesting part.
'w' - Open file for writing. The file is created (if it does not
exist) or truncated (if it exists).
'wx' - Like 'w' but fails if path exists.
So all we have to do is just add wx to the fs.open call. But hey, we don't like fopen-like IO. Let's read on fs.writeFile a bit more.
fs.readFile(filename[, options], callback)#
filename String
options Object
encoding String | Null default = null
flag String default = 'r'
callback Function
That options.flag looks promising. So we try
fs.writeFile(path, data, { flag: 'wx' }, function (err) {
if (err) throw err;
console.log("It's saved!");
});
And it works perfectly for a single write. I guess this code will fail in some more bizarre ways yet if you try to solve your task with it. You have an atomary "check for a_#.jpg existence, and write there if it's empty" operation, but all the other fs state is not locked, and a_1.jpg file may spontaneously disappear while you're already checking a_5.jpg. Most* file systems are no ACID databases, and the fact that you're able to do at least some atomic operations is miraculous. It's very likely that wx code won't work on some platform. So for the sake of your sanity, use database, finally.
Some more info for the suffering
Imagine we're writing something like memoize-fs that caches results of function calls to the file system to save us some network/cpu time. Could we open the file for reading if it exists, and for writing if it doesn't, all in the single call? Let's take a funny look on those flags. After a while of mental exercises we can see that a+ does what we want: if the file doesn't exist, it creates one and opens it both for reading and writing, and if the file exists it does so without clearing the file (as w+ would). But now we cannot use it neither in (smth)File, nor in create(Smth)Stream functions. And that seems like a missing feature.
So feel free to file it as a feature request (or even a bug) to Node.js github, as lack of atomic asynchronous file system API is a drawback of Node. Though don't expect changes any time soon.
Edit. I would like to link to articles by Linus and by Dan Luu on why exactly you don't want to do anything smart with your fs calls, because the claim was left mostly not based on anything.
What about using the a option?
According to the docs:
'a+' - Open file for reading and appending. The file is created if it does not exist.
It seems to work perfectly with createWriteStream
This method is no longer recommended. fs.exists is deprecated. See comments.
Here are some options:
1) Have 2 "fs" calls. The first one is the "fs.exists" call, and the second is "fs.write / read, etc"
//checks if the file exists.
//If it does, it just calls back.
//If it doesn't, then the file is created.
function checkForFile(fileName,callback)
{
fs.exists(fileName, function (exists) {
if(exists)
{
callback();
}else
{
fs.writeFile(fileName, {flag: 'wx'}, function (err, data)
{
callback();
})
}
});
}
function writeToFile()
{
checkForFile("file.dat",function()
{
//It is now safe to write/read to file.dat
fs.readFile("file.dat", function (err,data)
{
//do stuff
});
});
}
2) Or Create an empty file first:
--- Sync:
//If you want to force the file to be empty then you want to use the 'w' flag:
var fd = fs.openSync(filepath, 'w');
//That will truncate the file if it exists and create it if it doesn't.
//Wrap it in an fs.closeSync call if you don't need the file descriptor it returns.
fs.closeSync(fs.openSync(filepath, 'w'));
--- ASync:
var fs = require("fs");
fs.open(path, "wx", function (err, fd) {
// handle error
fs.close(fd, function (err) {
// handle error
});
});
3) Or use "touch": https://github.com/isaacs/node-touch
Todo this in a single system call you can use the fs-extra npm module.
After this the file will have been created as well as the directory it is to be placed in.
const fs = require('fs-extra');
const file = '/tmp/this/path/does/not/exist/file.txt'
fs.ensureFile(file, err => {
console.log(err) // => null
});
Another way is to use ensureFileSync which will do the same thing but synchronous.
const fs = require('fs-extra');
const file = '/tmp/this/path/does/not/exist/file.txt'
fs.ensureFileSync(file)
With async / await and Typescript I would do:
import * as fs from 'fs'
async function upsertFile(name: string) {
try {
// try to read file
await fs.promises.readFile(name)
} catch (error) {
// create empty file, because it wasn't found
await fs.promises.writeFile(name, '')
}
}
Here's a synchronous way of doing it:
try {
await fs.truncateSync(filepath, 0);
} catch (err) {
await fs.writeFileSync(filepath, "", { flag: "wx" });
}
If the file exists it will get truncated, otherwise it gets created if an error is raised.
This works for me.
// Use the file system fs promises
const {access} = require('fs/promises');
// File Exist returns true
// dont use exists which is no more!
const fexists =async (path)=> {
try {
await access(path);
return true;
} catch {
return false;
}
}
// Wrapper for your main program
async function mainapp(){
if( await fexists("./users.json")){
console.log("File is here");
} else {
console.log("File not here -so make one");
}
}
// run your program
mainapp();
Just keep eye on your async - awaits so everthing plays nice.
hope this helps.
You can do something like this:
function writeFile(i){
var i = i || 0;
var fileName = 'a_' + i + '.jpg';
fs.exists(fileName, function (exists) {
if(exists){
writeFile(++i);
} else {
fs.writeFile(fileName);
}
});
}

Resources