How can I update object keys/values when if statement is triggered? - javascript

I'm building a Discord bot which will allow users to create custom commands.
It works in this way, user enters "!addcommand !commandname:command value". The program will then split the string, and add !commandname:command value to a txt file. Whenever somebody types !commandname into discord, the bot will then output "command value" into chat.
The program is supposed to check if the new command exists whenever an if statement is triggered. However, this only seems to be checking the first time the program is run, which causes new commands to not be recognized unless the program is restarted.
Note:
Client.on listens to the channel, and contents are run every time someone says something in chat.
!addcommand appears to be functioning correctly, and I'm able to confirm the lines are being written to the file as intended.
I don't know what else to try.
Main file:
//Assume that requires etc are included
client.on('message', message => {
const pingCheck = message.content.charAt(0);
const commandCheck = message.content.split(" ");
if (pingCheck === "!") {
//Populates the list of custom commands. Must be done on every check, or new commands will not be recognized.
//Currently, this seems to only update once the service/program is restarted
var commandList = customCommands.returnPhrase();
//If the commandList object contains the correct key (stored in commandCheck[0]) such as !commandname, the bot will send the value "command value" as a string to the discord chat.
if (commandList.hasOwnProperty(commandCheck[0])) {
message.channel.send(commandList[commandCheck[0]]);
}
//If the key does not exist, the program then checks predefined commands. Other commands exist here, but for the purposes of this question I'll show only the !addcommand, which is used to create a new custom command.
else {
switch (commandCheck[0]) {
case "!addcommand":
//This checks that the command is formatted properly, "!commandname:commandvalue". If it does not start with ! or contain : somewhere in the string, it's probably an invalid format.
//Technically this still allows for a !:commandvalue format. I haven't implemented a check for this yet.
if (commandCheck[1].startsWith("!") && commandCheck[1].includes(":")) {
//While loop reconstructs the command key to be passed in, ignores slot 0 as this is the !addcommand
var gs = "";
var x = 1;
while (x < commandCheck.length) {
gs += gs +commandCheck[x] + " ";
x++;
}
gs = gs.slice(0,-1)+"\r\n"; //removes the last " " from the input string, and adds line-break
addCommands.addPhrase(gs);//passes reconstructed command to be added to commandlist.txt
message.channel.send("I have added " + commandCheck[1] + " to the command list.");
break;
}
default:
message.channel.send("I dont recognize that command.");
}
}
}
});
Module which adds commands:
const fs = require('fs');
var createCommand = {
addPhrase: function(x) {
fs.appendFile("commandlist.txt", x, function(err){
if(err) throw err;
console.log(err)
});
}
}
module.exports = createCommand;
Module which populates list of custom commands:
const fs = require('fs');
var commandFile = fs.readFileSync('commandlist.txt','utf8');
var dataSplit = commandFile.split("\r\n");
var readCommand = {
returnPhrase: function(){
var splitSplit = {};
var i = 0;
//populates splitSplit with keys and values based on text file
while (i<dataSplit.length){
var newKey = dataSplit[i].split(':');
splitSplit[newKey[0]] = newKey[1];
i++
};
return splitSplit;
},
};
module.exports = readCommand;
Better readability: https://repl.it/repls/DarkvioletDeafeningAutomaticparallelization
Expected: commandList is populated everytime if statement is triggered
Actual: commandList populates first time statement is triggered

You write to the file whenever a new command comes in, however you only read once from it when the server starts, so you won't keep track of changes (untill you restart the server which will read the file again). Now you could theoretically listen for filechanges and reload then, but that is overcomplicating things, the filesystem is not meant to achieve that. Instead, just keep your commands in an object and export some methods for adding / checking:
let commands = {};
// ¹
module.exports = {
addCommand(key, value) {
commands[key] = value;
// ²
},
getValue(key) {
return commands[key];
}
};
Now when you add a command, it directly gets added to the object, and that can then directly be read out.
Now as objects aren't persisted accross restarts, you will lose all commands then. But that is easy to fix: You could just reflect the object to a file whenever it updates, and then load it on every start. Instead of creating a custom format for that, I'd just use JSON. The code above can easily be extended:
// ¹
try {
commands = JSON.parse( fs.readFileSync("commands.txt") );
} catch(e) { /* ignore if file doesnt exist yet */ }
// ²
fs.writeFile("commands.txt", JSON.stringify(commands), err => {
if(err) console.error(err);
});
How I would write the bot:
const playerCommands = require("./commands.js");
const defaultCommands = {
addCommand(...pairs) {
let result = "";
for(const pair of pairs) {
const [name, value] = pair.split(":");
playerCommands.addCommand(name, value);
result += `${name} added with value ${value}\n`;
}
return result;
}
};
client.on('message', ({ content, channel }) => {
if(content[0] !== "!") return; // ignore non-commandd
const [commandName, ...args] = content.slice(1).split(" ");
if(defaultCommands[commandName])
return channel.send(defaultCommands[commandName](...args));
const value = playerCommands.getValue(commandName);
if(value) return channel.send(value);
return channel.send("Command not found!");
});

Related

copy a file/object and change the name

I'm doing an API for a gallery; so, I create a method that let copy an image from the database.
Now, I want to add a number at the end of the copy-image name. For example:
-original image name: image
-copy image name: image(1)
-2nd copy image name: image(2)
How can I add the number to the name of copied name automatically?
'use strict'
let imageObject= require('../models/image-object');
let fs=require('fs');
let path= require('path');
let gallery_controllers={
copyImage:function(req,res){
//get the id param of the image to copy
let imageId=req.params.id;
if(imageId==null) return res.status(404).send({message:"no ID defined"});
//I search the requiere image on the database
imageObject.findById(imageId,(err,image)=>{
if(err) return res.status(500).send({message:'err to response data'});
if(!image) return res.status(404).send({message:'image not found'});
if(image){
//set a new model-object
let imageCopied= new imageObject();
imageCopied.name= image.name;
imageCopied.image=image.image;
//save image copied on the database
imageCopied.save((err,image_copied)=>{
if(err) return res.status(500).send({message:"error 500"});
if(!image_copied) return res.status(404).send({message:"error 404"});
return res.status(200).send({
image:image_copied
})
})
}
})
},
}
Here's a function that looks in the directory passed to it for files of the name file(nnn) where nnn is some sequence of digits and returns back to you the full path of the next one in sequence (the one after the highest number that already exists).
This function pre-creates a placeholder file by that name to avoid concurrency issues with multiple asynchronous operations calling this function and potentially conflicting (if it only returned the filename without creating the file). To further handle conflicts, it creates the placeholder file in a mode that fails if it already exists (so only one invocation of this function will ever create that particular file) and it automatically retries to find a new number if it gets a conflict (e.g. someone else created the next available file before we got to it). All of this logic is to avoid the subtleties of possible race conditions in creating the next filename in the sequence.
Once the caller has a unique filename that this resolves to, then it is expected that they will overwrite the placeholder contents with their own contents.
// pass directory to look in
// pass base file name so it will look for next in sequence as in "file(3)"
// returns the full path of the unique placeholder file it has created
// the caller is then responsible for that file
// calling multiple times will create a new placeholder file each time
async function findNextName(dir, base) {
let cntr = 0;
const cntr_max = 5;
const regex = new RegExp(`^${base}\\((\\d+)\\)$`);
async function run() {
const files = await fs.promises.readdir(dir);
let highest = 0;
for (let f of files) {
let matches = f.match(regex);
if (matches) {
let num = parseInt(matches[1]);
if (num > highest) {
highest = num;
}
}
}
let name = `${base}(${highest + 1})`;
// create placeholder file with this name to avoid concurrency issues
// of another request also trying to use the same next file
try {
// write to file, fail if the file exists due to a concurrency issue
const fullPath = path.resolve(path.join(dir, name));
await fs.promises.writeFile(fullPath, "placeholder", { flag: "wx" });
return fullPath;
} catch (e) {
// if this fails because of a potential concurrency issue, then try again
// up to cntr_max times to avoid looping forever on a persistent error
if (++cntr < cntr_max) {
return run();
} else {
throw e;
}
}
}
return run();
}
You could call it like this:
findNextName(".", "file").then(filename=> {
console.log(filename);
}).catch(err => {
console.log(err);
});

Discord.js - Trying to both await an async process and allow recursive calls of a wrapper

This one has a ton of moving parts that I've tried to simplify down as much as possible, and I still learning about async/await processing, so bear with me -
So I'm trying to write chat commands for a Discord chatbot with discord.js. The command that I'm having trouble with, triggered by a message beginning in !aut, is supposed to take in a string of user input (that follows the command itself) and return another string; if the user does not provide a string, I want it to grab the content of the message immediately above the message containing the command (i.e. 2nd most recent) and use that as the input string.
However, testing this latter case, it keeps throwing this error:
(node:17896) UnhandledPromiseRejectionWarning: DiscordAPIError: Cannot send an empty message
I've structured the bot with an event listener that listens for commands and calls the wrapper function execute(string, channel) if it detects one; the wrapper then calls the appropriate utility function for that command to get a string that in then passes back to the listener, which then sends the string out to the channel.
The utility function com_aut(array) is working perfectly fine; the problem is that the default empty string sComResult defined within the wrapper is being returned to the listener before it can be overwritten by com_aut:
client.on('message', pMessage => {
if (executable(pMessage.content) == true) {
let sResult = execute(pMessage.content, pMessage.channel);
pMessage.channel.send(sResult);
}
});
function execute(sMessage, pChannel = null) {
// strip off the initial ! that marks the message as a command; whatever comes immediately afterwards is the command
let args = sMessage.substring(1).trim().split(/ +/g);
let cmd = args[0];
// don't include the command itself among the rest of the arguments passed to it
args = args.splice(1);
let sComResult = "";
switch(cmd){
...
case "aut":
if (args.length < 1) {
// input string that should be in args actually isn't, so grab the content of the 2nd most recent message instead
pChannel.fetchMessages({ limit: 2 }).then(pMessages => {
sComResult = com_aut([pMessages.last().content]);
});
} else {
// input string is present in args, so just use that
sComResult = com_aut(args);
}
break;
}
return sComResult;
}
TextChannel.fetchMessages is asynchronous - or returns a Promise, at least - so I tried making the wrapper asynchronous as well so I could force it to await. Plus the necessary changes in the listener, I found that this worked:
client.on('message', pMessage => {
if (executable(pMessage.content) == true) {
execute(pMessage.content, pMessage.channel).then(sResult => { pMessage.channel.send(sResult) });
}
});
async function execute(sMessage, pChannel = null) {
// strip off the initial ! that marks the message as a command; whatever comes immediately afterwards is the command
let args = sMessage.substring(1).trim().split(/ +/g);
let cmd = args[0];
// don't include the command itself among the rest of the arguments passed to it
args = args.splice(1);
let sComResult = "";
switch(cmd){
...
case "aut":
if (args.length < 1) {
// input string that should be in args actually isn't, so grab the content of the 2nd most recent message instead
pMessages = await pChannel.fetchMessages({ limit: 2 });
sComResult = com_aut([pMessages.last().content]);
});
} else {
// input string is present in args, so just use that
sComResult = com_aut(args);
}
break;
}
return sComResult;
}
However, NOW the problem is that I can't call execute(string, channel) recursively, which I do when piping the output of one string-output command into another, string-input command. (Which is also why the wrapper exists in the first place instead of the listener just being linked directly to the utility functions) This involves an execute call within execute itself. I'll just link to pastebin at this point, but it's throwing a type error since it's not able to get a value back from the nested execute and so it ends up trying to call null.then:
(node:6796) UnhandledPromiseRejectionWarning: TypeError: pInnerPromise.then is not a function
How can I structure my code so that it properly awaits the fetchMessages query before moving on, but still allowing recursive calling of the wrapper function (or some other way to pipe)?
Your code
if (executable(sInnerCom) == true) {
// here you are using await so promise is already resolved.
let pInnerPromise = await execute(sInnerCom, pChannel);
// no need to use then, you can return result direct
pInnerPromise.then(result => { sInnerComResult = result });
}
It should be like this
if (executable(sInnerCom) == true) {
let result = await execute(sInnerCom, pChannel);
sInnerComResult = result ;
}
or like this
if (executable(sInnerCom) == true) {
sInnerComResult = await execute(sInnerCom, pChannel);
}

TypeError: Cannot read property 'id' of undefined when trying to make discord bot

I'm trying to make a simple discord bot, however whenever I run the -setcaps command I get :
TypeError: Cannot read property 'id' of undefined.
I'm not exactly sure what is causing this. I would appreciate whatever help you can provide. Not exactly sure what to add to this to provide more details, I'm using the latest stable version of node.js and editing with notpad++
// Call Packages
const Discord = require('discord.js');
const economy = require('discord-eco');
// Define client for Discord
const client = new Discord.Client();
// We have to define a moderator role, the name of a role you need to run certain commands
const modRole = 'Sentinel';
// This will run when a message is recieved...
client.on('message', message => {
// Variables
let prefix = '-';
let msg = message.content.toUpperCase();
// Lets also add some new variables
let cont = message.content.slice(prefix.length).split(" "); // This slices off the prefix, then stores everything after that in an array split by spaces.
let args = cont.slice(1); // This removes the command part of the message, only leaving the words after it seperated by spaces
// Commands
// Ping - Let's create a quick command to make sure everything is working!
if (message.content.toUpperCase() === `${prefix}PING`) {
message.channel.send('Pong!');
}
// Add / Remove Money For Admins
if (msg.startsWith(`${prefix}SETCAPS`)) {
// Check if they have the modRole
if (!message.member.roles.find("name", modRole)) { // Run if they dont have role...
message.channel.send('**You need the role `' + modRole + '` to use this command...**');
return;
}
// Check if they defined an amount
if (!args[0]) {
message.channel.send(`**You need to define an amount. Usage: ${prefix}SETCAPS <amount> <user>**`);
return;
}
// We should also make sure that args[0] is a number
if (isNaN(args[0])) {
message.channel.send(`**The amount has to be a number. Usage: ${prefix}SETCAPS <amount> <user>**`);
return; // Remember to return if you are sending an error message! So the rest of the code doesn't run.
}
// Check if they defined a user
let defineduser = '';
if (!args[1]) { // If they didn't define anyone, set it to their own.
defineduser = message.author.id;
} else { // Run this if they did define someone...
let firstMentioned = message.mentions.users.first();
defineduser = firstMentioned.id;
}
// Finally, run this.. REMEMBER IF you are doing the guild-unique method, make sure you add the guild ID to the end,
economy.updateBalance(defineduser + message.guild.id, parseInt(args[0])).then((i) => { // AND MAKE SURE YOU ALWAYS PARSE THE NUMBER YOU ARE ADDING AS AN INTEGER
message.channel.send(`**User defined had ${args[0]} added/subtraction from their account.**`)
});
}
// Balance & Money
if (msg === `${prefix}BALANCE` || msg === `${prefix}MONEY`) { // This will run if the message is either ~BALANCE or ~MONEY
// Additional Tip: If you want to make the values guild-unique, simply add + message.guild.id whenever you request.
economy.fetchBalance(message.author.id + message.guild.id).then((i) => { // economy.fetchBalance grabs the userID, finds it, and puts the data with it into i.
// Lets use an embed for This
const embed = new Discord.RichEmbed()
.setDescription(`**${message.guild.name} Stash**`)
.setColor(0xff9900) // You can set any HEX color if you put 0x before it.
.addField('Stash Owner',message.author.username,true) // The TRUE makes the embed inline. Account Holder is the title, and message.author is the value
.addField('Stash Contents',i.money,true)
// Now we need to send the message
message.channel.send({embed})
})
}
});
client.login('TOKEN HIDDEN');
I'm not sure if this was causing your error but let's give it a try. I edited the check if the user mentioned someone.
// Call Packages
const Discord = require('discord.js');
const economy = require('discord-eco');
// Define client for Discord
const client = new Discord.Client();
// We have to define a moderator role, the name of a role you need to run certain commands
const modRole = 'Sentinel';
// This will run when a message is recieved...
client.on('message', message => {
// Variables
let prefix = '-';
let msg = message.content.toUpperCase();
// Lets also add some new variables
let cont = message.content.slice(prefix.length).split(" "); // This slices off the prefix, then stores everything after that in an array split by spaces.
let args = cont.slice(1); // This removes the command part of the message, only leaving the words after it seperated by spaces
// Commands
// Ping - Let's create a quick command to make sure everything is working!
if (message.content.toUpperCase() === `${prefix}PING`) {
message.channel.send('Pong!');
}
// Add / Remove Money For Admins
if (msg.startsWith(`${prefix}SETCAPS`)) {
// Check if they have the modRole
if (!message.member.roles.find("name", modRole)) { // Run if they dont have role...
message.channel.send('**You need the role `' + modRole.name + '` to use this command...**');
return;
}
// Check if they defined an amount
if (!args[0]) {
message.channel.send(`**You need to define an amount. Usage: ${prefix}SETCAPS <amount> <user>**`);
return;
}
// We should also make sure that args[0] is a number
if (isNaN(args[0])) {
message.channel.send(`**The amount has to be a number. Usage: ${prefix}SETCAPS <amount> <user>**`);
return; // Remember to return if you are sending an error message! So the rest of the code doesn't run.
}
// Check if they defined a user
let defineduser = '';
let user = message.mentions.users.first() || msg.author;
defineduser = user.id
// Finally, run this.. REMEMBER IF you are doing the guild-unique method, make sure you add the guild ID to the end,
economy.updateBalance(defineduser + message.guild.id, parseInt(args[0])).then((i) => { // AND MAKE SURE YOU ALWAYS PARSE THE NUMBER YOU ARE ADDING AS AN INTEGER
message.channel.send(`**User defined had ${args[0]} added/subtraction from their account.**`)
});
}
// Balance & Money
if (msg === `${prefix}BALANCE` || msg === `${prefix}MONEY`) { // This will run if the message is either ~BALANCE or ~MONEY
// Additional Tip: If you want to make the values guild-unique, simply add + message.guild.id whenever you request.
economy.fetchBalance(message.author.id + message.guild.id).then((i) => { // economy.fetchBalance grabs the userID, finds it, and puts the data with it into i.
// Lets use an embed for This
const embed = new Discord.RichEmbed()
.setDescription(`**${message.guild.name} Stash**`)
.setColor(0xff9900) // You can set any HEX color if you put 0x before it.
.addField('Stash Owner', message.author.username, true) // The TRUE makes the embed inline. Account Holder is the title, and message.author is the value
.addField('Stash Contents', i.money, true)
// Now we need to send the message
message.channel.send({
embed
})
})
}
});
client.login('TOKEN HIDDEN')

How can I add strings to an array in a JSON using node.js?

I have a discord bot and I want to have an array that has the user ids of people who abuse the volume and music commands so that I can take away their abilities and give them back using commands like !nomusic and !musicback, but I have no idea how I would make it add or remove their ids from an array in the config file. My best guess is to use fs and have it push the member's id into the array, but I have no idea how I would go about doing this (I'm very new to node.js and especially fs, so sorry if this is a really easy thing to do and is really dumb to ask)
So far this is how far I've gotten(lots of the program not included so it's easier to read)
function readNoMusicJSON() {
return JSON.parse(fs.readFileSync("./nomusic.json"));
}
var badmusicusers = readNoMusicJSON();
function nomusicsfoyou(badmusicusers, userId) {
return nomusic.concat([userId]);
}
function saveNoMusicFile(badmusicusers) {
fs.writeFileSync("./nomusic.json");
}
bot.on('message', async message => {
//some code ommited due to lack of importance
var args = message.content.slice(config.prefix.length).trim().split(/ +/g);
var command = args.shift().toLowerCase();
switch(command){
case"music":
if(badmusicusers.find(id=>id == message.author.id)) return;
// more ommitted code that don't matter
break;
case "nomusic":
let sadmusicboi = message.mentions.members.first();
badmusicusers = nomusicsfoyou((badmusicusers, sadmusicboi.id));
saveNoMusicFile(badmusicusers);
break;
}
})
Suppose you have a list of bannedUsers in your config bannedUsers.json file.
["user-id-1", "user-id-2"]
On Startup of the program, you read the file into a variable:
function readBannedUsers() {
return JSON.parse(fs.readFileSync("./bannedUsers.json"));
}
var bannedUsers = readBannedUser();
Whenever you want to ban a user:
function banUser(bannedUsers, userId) {
return bannedUsers.concat([userId]);
}
function saveBannedUsers(bannedUsers) {
fs.writeFileSync("./bannedUsers.json");
}
bannedUsers = banUser(bannedUsers, "user-3");
saveBannedUsers(bannedUsers);
That's all.

How to launch a normal download from a Firefox Add-on SDK extension

I'm developing an Add-on SDK extension for Firefox. I find that I need to be able to launch a download as if the user requested it, that is, either showing the normal file save dialog or saving the file to wherever the user prefers, as it could be configured under preferences > content.
Every single post or documentation regarding downloads seem to only take in consideration the scenario where I know where to download the file, but that is not what I need in this case. In this case, it needs to be as if the user started the download.
How can this be accomplished, preferably via the methods of the SDK?
Well, you could just initiate an actual save.
Initiating a save link from your code:
In the context menu the oncommand value is gContextMenu.saveLink();. saveLink() is defined in: chrome://browser/content/nsContextMenu.js. It does some housekeeping and then calls saveHelper() which is defined in the same file. You could just call saveHelper() with appropriate arguments. It is included in panels from chrome://browser/content/web-panels.xul with:
<script type="application/javascript"
src="chrome://browser/content/nsContextMenu.js"/>
Then the gContextMenu variable declared in chrome://browser/content/browser.js as null is assigned:
gContextMenu = new nsContextMenu(this, event.shiftKey);
in the onpopupshowing event handler for context menus. It is returned to:
'gContextMenu = null;'
in the onpopuphiding event handler.
If you want to use it in your own code you can do:
let urlToSave = "http://stackoverflow.com/questions/26694442";
let linkText = "Some Link text";
// Add a "/" to un-comment the code appropriate for your add-on type.
/* Overlay and bootstrap:
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
//*/
/* Add-on SDK:
var {Cc, Ci, Cr} = require("chrome");
//*/
if (window === null || typeof window !== "object") {
//If you do not already have a window reference, you need to obtain one:
// Add a "/" to un-comment the code appropriate for your add-on type.
/* Add-on SDK:
var window = require('sdk/window/utils').getMostRecentBrowserWindow();
//*/
/* Overlay and bootstrap (from almost any context/scope):
var window=Components.classes["#mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator)
.getMostRecentWindow("navigator:browser");
//*/
}
//Create an object in which we attach nsContextMenu.js.
// It needs some support properties/functions which
// nsContextMenu.js assumes are part of its context.
let contextMenuObj = {
makeURI: function (aURL, aOriginCharset, aBaseURI) {
var ioService = Cc["#mozilla.org/network/io-service;1"]
.getService(Ci.nsIIOService);
return ioService.newURI(aURL, aOriginCharset, aBaseURI);
},
gPrefService: Cc["#mozilla.org/preferences-service;1"]
.getService(Ci.nsIPrefService)
.QueryInterface(Ci.nsIPrefBranch),
Cc: Cc,
Ci: Ci,
Cr: Cr
};
Cc["#mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader)
.loadSubScript("chrome://browser/content/nsContextMenu.js"
,contextMenuObj);
//Re-define the initMenu function, as there is not a desire to actually
// initialize a menu.
contextMenuObj.nsContextMenu.prototype.initMenu = function() { };
let myContextMenu = new contextMenuObj.nsContextMenu();
//Save the specified URL
myContextMenu.saveHelper(urlToSave,linkText,null,true,window.content.document);
Alternative to using loadSubScript to load nsContextMenu.js:
My preference is to use loadSubScript to load the saveHelper code from nsContextMenu.js. This keeps the code up to date with any changes which are made in future Firefox releases. However, it introduces the dependency that you are using a function from a non-official API. Thus, it might change in some way in future Firefox release and require changes in your add-on. The alternative is to duplicate the saveHelper() code in your extension. It is defined as the following:
// Helper function to wait for appropriate MIME-type headers and
// then prompt the user with a file picker
saveHelper: function(linkURL, linkText, dialogTitle, bypassCache, doc) {
// canonical def in nsURILoader.h
const NS_ERROR_SAVE_LINK_AS_TIMEOUT = 0x805d0020;
// an object to proxy the data through to
// nsIExternalHelperAppService.doContent, which will wait for the
// appropriate MIME-type headers and then prompt the user with a
// file picker
function saveAsListener() {}
saveAsListener.prototype = {
extListener: null,
onStartRequest: function saveLinkAs_onStartRequest(aRequest, aContext) {
// if the timer fired, the error status will have been caused by that,
// and we'll be restarting in onStopRequest, so no reason to notify
// the user
if (aRequest.status == NS_ERROR_SAVE_LINK_AS_TIMEOUT)
return;
timer.cancel();
// some other error occured; notify the user...
if (!Components.isSuccessCode(aRequest.status)) {
try {
const sbs = Cc["#mozilla.org/intl/stringbundle;1"].
getService(Ci.nsIStringBundleService);
const bundle = sbs.createBundle(
"chrome://mozapps/locale/downloads/downloads.properties");
const title = bundle.GetStringFromName("downloadErrorAlertTitle");
const msg = bundle.GetStringFromName("downloadErrorGeneric");
const promptSvc = Cc["#mozilla.org/embedcomp/prompt-service;1"].
getService(Ci.nsIPromptService);
promptSvc.alert(doc.defaultView, title, msg);
} catch (ex) {}
return;
}
var extHelperAppSvc =
Cc["#mozilla.org/uriloader/external-helper-app-service;1"].
getService(Ci.nsIExternalHelperAppService);
var channel = aRequest.QueryInterface(Ci.nsIChannel);
this.extListener =
extHelperAppSvc.doContent(channel.contentType, aRequest,
doc.defaultView, true);
this.extListener.onStartRequest(aRequest, aContext);
},
onStopRequest: function saveLinkAs_onStopRequest(aRequest, aContext,
aStatusCode) {
if (aStatusCode == NS_ERROR_SAVE_LINK_AS_TIMEOUT) {
// do it the old fashioned way, which will pick the best filename
// it can without waiting.
saveURL(linkURL, linkText, dialogTitle, bypassCache, false,
doc.documentURIObject, doc);
}
if (this.extListener)
this.extListener.onStopRequest(aRequest, aContext, aStatusCode);
},
onDataAvailable: function saveLinkAs_onDataAvailable(aRequest, aContext,
aInputStream,
aOffset, aCount) {
this.extListener.onDataAvailable(aRequest, aContext, aInputStream,
aOffset, aCount);
}
}
function callbacks() {}
callbacks.prototype = {
getInterface: function sLA_callbacks_getInterface(aIID) {
if (aIID.equals(Ci.nsIAuthPrompt) || aIID.equals(Ci.nsIAuthPrompt2)) {
// If the channel demands authentication prompt, we must cancel it
// because the save-as-timer would expire and cancel the channel
// before we get credentials from user. Both authentication dialog
// and save as dialog would appear on the screen as we fall back to
// the old fashioned way after the timeout.
timer.cancel();
channel.cancel(NS_ERROR_SAVE_LINK_AS_TIMEOUT);
}
throw Cr.NS_ERROR_NO_INTERFACE;
}
}
// if it we don't have the headers after a short time, the user
// won't have received any feedback from their click. that's bad. so
// we give up waiting for the filename.
function timerCallback() {}
timerCallback.prototype = {
notify: function sLA_timer_notify(aTimer) {
channel.cancel(NS_ERROR_SAVE_LINK_AS_TIMEOUT);
return;
}
}
// set up a channel to do the saving
var ioService = Cc["#mozilla.org/network/io-service;1"].
getService(Ci.nsIIOService);
var channel = ioService.newChannelFromURI(makeURI(linkURL));
if (channel instanceof Ci.nsIPrivateBrowsingChannel) {
let docIsPrivate = PrivateBrowsingUtils.isWindowPrivate(doc.defaultView);
channel.setPrivate(docIsPrivate);
}
channel.notificationCallbacks = new callbacks();
let flags = Ci.nsIChannel.LOAD_CALL_CONTENT_SNIFFERS;
if (bypassCache)
flags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
if (channel instanceof Ci.nsICachingChannel)
flags |= Ci.nsICachingChannel.LOAD_BYPASS_LOCAL_CACHE_IF_BUSY;
channel.loadFlags |= flags;
if (channel instanceof Ci.nsIHttpChannel) {
channel.referrer = doc.documentURIObject;
if (channel instanceof Ci.nsIHttpChannelInternal)
channel.forceAllowThirdPartyCookie = true;
}
// fallback to the old way if we don't see the headers quickly
var timeToWait =
gPrefService.getIntPref("browser.download.saveLinkAsFilenameTimeout");
var timer = Cc["#mozilla.org/timer;1"].createInstance(Ci.nsITimer);
timer.initWithCallback(new timerCallback(), timeToWait,
timer.TYPE_ONE_SHOT);
// kick off the channel with our proxy object as the listener
channel.asyncOpen(new saveAsListener(), null);
}
Like #canuckistani said, use the Downloads.jsm
let { Downloads } = require("resource://gre/modules/Downloads.jsm");
let { OS } = require("resource://gre/modules/osfile.jsm")
let { Task } = require("resource://gre/modules/Task.jsm");
Task.spawn(function () {
yield Downloads.fetch("http://www.mozilla.org/",
OS.Path.join(OS.Constants.Path.tmpDir,
"example-download.html"));
console.log("example-download.html has been downloaded.");
}).then(null, Components.utils.reportError);

Categories