I'm working on a twitter bot that has the goal of posting two images along with a string of text. I'm using node.js (for the first time, I should probably add) and the Twit package.
I'm having a variety of issues, many of which are likely just due to me being a novice, but I genuinely can't figure out how to make the dang thing output correctly. I've managed to get text and a single image to output, but I'm trying to spit out two images at once.
The main bot chunk uses the following code to build and schedule a tweet:
function mainPostBot() {
console.log("Now assembling a new tweet.");
var leftCard = getRandomNumber(1, 36);
console.log("The left card is #" + leftCard + ", " + cardNames[leftCard] + ".");
// Generates a random number for the left card.
var leftImagePath = path.join(__dirname, '/leftImage/' + imageArray[leftCard]);
console.log("The left image's path is " + leftImagePath);
// Gives the file path to access the correct image for the left.
var rightCard = getRandomNumber(1, 36);
console.log("The right card is #" + rightCard + ", " + cardNames[rightCard] + ".");
// Generates a random number for the right card.
while (leftCard == rightCard) {
var rightCard = getRandomNumber(1, 36);
console.log("Whoops! The right card is now #" + rightCard + ", " + cardNames[rightCard] + ".");
// Generates a random number for the right card in the case of doubles.
}
var rightImagePath = path.join(__dirname, '/rightImage/' + imageArray[rightCard]);
console.log("The right image's path is " + rightImagePath);
// Gives the file path to access the correct image for the left.
console.log('Encoding the images...');
var b64contentLeft = fs.readFileSync(leftImagePath, { encoding: 'base64' });
var b64contentRight = fs.readFileSync(rightImagePath, { encoding: 'base64' });
var bothImages = (b64contentLeft + "," + b64contentRight);
// This encodes the images in base64, which twitter needs. I guess. I dunno, man.
var tweetText = (jsUcfirst(cardNames[leftCard]) + ' and ' + cardNames[rightCard] + '. (#' + leftCard + " " + cardCorrespond[leftCard] + "/#" + rightCard + " " + cardCorrespond[rightCard] + ")");
// This constructs the grammar of the tweet.
// jsUcfirst capitalizes the first letter of a string so it lets me cheat a sentence start.
var tweetTime = getRandomNumber(1000*60*60*4, 1000*60*60*24*3+1);
// Generates an amount of time before the next tweet.
sendTweet(tweetText, bothImages, tweetTime);
setTimeout(mainPostBot, tweetTime);
}
mainPostBot();
cardNames, cardCorrespond, and imageArray are just big arrays at the top of the program that list the names of the images, some info about them, and their filenames, respectively:
var cardNames = new Array(
"the Fool", //This one will never be called bc of the number generator and it's fun bc, y'know, Tarot
"the Rider","the Clover","the Ship","the House","the Tree","the Clouds","the Snake","the Coffin","the Bouquet","the Scythe","the Whip", //"the Nae Nae",
"the Birds","the Child","the Fox","the Bear","the Stars","the Stork","the Dog","the Tower","the Garden","the Mountain","the Crossroads",
"the Mice","the Heart","the Ring","the Book","the Letter","the Gentleman","the Lady","the Lily","the Sun","the Moon","the Key","the Fish",
"the Anchor","the Cross"
);
var cardCorrespond = new Array(
" ","9♥","6♦","10♠","K♥","7♥","K♣","Q♣","9♦","Q♠","J♦","J♣","7♦","J♠","9♣","10♣","6♥","Q♥","10♥",
"6♠","8♠","8♣","Q♦","7♣","J♥","A♣","10♦","7♠","A♥","A♠","K♠","A♦","8♥","8♦","K♦","9♠","6♣"
);
var imageArray = new Array(
" ","01.png","02.png","03.png","04.png","05.png","06.png","07.png","08.png","09.png","10.png","11.png","12.png","13.png",
"14.png","15.png","16.png","17.png","18.png","19.png","20.png","21.png","22.png","23.png","24.png","25.png","26.png",
"27.png","28.png","29.png","30.png","31.png","32.png","33.png","34.png","35.png","36.png"
);
And once mainPostBot has the tweet fully constructed, it's delivered to sendTweet:
function sendTweet(text, images, time){
console.log('Uploading the images...');
T.post('media/upload', { media_data: images }, function (err, data, response){
if (err){
console.log("There's an issue uploading the images.");
console.log(err);
} else {
console.log('Images uploaded!');
console.log("Now tweeting...")
T.post('statuses/update', {
status: text,
media_ids: new Array(data.media_id_string)
}, function(err, data, response){
if (err) {
console.log("An error has occurred during posting.");
console.log(err);
} else {
console.log("Post successful!");
console.log("The tweet says:" + text);
console.log("The next tweet will send in " + msToTime(time) + "!");
}
});
}
});
}
Any ideas? I'm open to using other npm packages, for sure, but I just can't figure out why this doesn't work as it is. Thanks for reading, and let me know if you need any other bits of the code.
EDIT 1: My roommate who also dabbles in this sort of stuff found a potentially useful link on github for another package, node-twitter. In that link, a poster explains that the images should be delivered as a string, separated by commas, so I added some edits to mainPostBot and sendTweet, mostly in the passing of b64 image data.
EDIT 2: Those edits are now reflected in the code above, as well as some other fixes I've made to the project as a whole. I got to a point where things are running smoothly again (found a missing bracket, I suck at this coding stuff), and there's tweets posting successfully, but just as before I'm not getting the second image through. Roommate who helped earlier is suggesting to just pump out static single images for every possible card combination but there's gotta be a more elegant solution. Again, any ideas could save a week of my weird bedroom tinkering, and I appreciate any eyes on this.
It took a lot of tinkering, but I figured it out. Each image has to be uploaded to twitter individually, so after loading the image, I save it's data.media_id_string to a variable, and then load those values into the tweet in an array.
I've removed the line from mainPostBot where I combined b64contentLeft and b64contentRight and added it into the sendTweet code, using the returned data strings. Now, I call sendTweet() with:
sendTweet(tweetText, b64contentLeft, b64contentRight, tweetTime);
And sendTweet() is now looking like this:
function sendTweet(text, leftimage, rightimage, time){
console.log('Uploading the images...');
T.post('media/upload', { media_data: leftimage }, function (err, data, response){
if (err){
console.log("There's an issue uploading the left image.");
console.log(err);
} else {
console.log('Left image uploaded!');
var leftID = data.media_id_string;
T.post('media/upload', { media_data: rightimage }, function (err, data, response){
if (err){
console.log("There's an issue uploading the right image.");
console.log(err);
} else {
console.log('Right image uploaded!');
var rightID = data.media_id_string;
var bothImages = ( leftID + "," + rightID );
console.log("Now tweeting...")
T.post('statuses/update', {
status: text,
media_ids: new Array(bothImages)
}, function(err, data, response){
if (err) {
console.log("An error has occurred during posting.");
console.log(err);
} else {
console.log("Post successful!");
console.log("The tweet says: " + text);
console.log("The next tweet will send in " + msToTime(time) + "!");
}
});
}
});
}
});
}
Essentially, if the left image uploads correctly, it'll save that ID, then try for the right image. IF that's successful, it'll save that ID as well, then combine the two into a string separated with a comma, which is loaded into the media_ids array as bothImages.
This was kind of a nightmare to solve, but I wanted to make sure it's documented in case anyone else stumbles here looking for the same answer.
EDIT: The following was not my true issue. See answer.
I'm working on a simple html/javascript chat client using Google firebase. The following code is intended to be a rudimentary system of registering and logging in users, wherein the function is provided with an array ($usr) containing a username and password at the 1 and 2 positions.
The local username and password $usr[1-2] are then checked against the database's result (getuser variable, structured as user obj) to determine whether or not a username has already been taken or if the user's credentials are valid. Please note that this is a personal project, the data is not intended to be sensitive.
//Registers user credentials with input [cmd, user, pass]
var auth = function ($usr) {
var db = firebase.database().ref('chatapp/users/' + $usr[1]);
var getuser;
user = {'name': $usr[1], 'pass': $usr[2]};
db.once('value').then(function(snapshot) {
getuser = snapshot.val();
if ($usr[0] === "/register") {
if (getuser.name !== $usr[1]) {
db.set(user);
notify('Registered ' + $usr[1] + ' with pass "' + $usr[2] + '"');
} else {
notify('Username "' + $usr[1] + '" already taken, try again');
}
} else if ($usr[0] === "/login") {
if (getuser.name !== $usr[1] || getuser.pass !== $usr[2]) {
notify('Invalid credentials ' + $usr[1] + ':' + $usr[2]);
} else {
notify($usr[1] + ' logged in');
}
}
});
};
The issue comes into play at db.once(). The data is being retrieved but is delayed. If a user is attempting to register (getuser.name !== $usr1) will always return True because the variable is set to undefined. However, the login command works flawlessly because by then getuser has been set to the value retrieved from Firebase.
I have tried using .once() only to set the variable, or as a function returning snapshot.val(). I have tried including all of my code within the callback for .once() and using snapshot.val()[name] and [pass] rather than storing it to a variable. The only solution to this seems to be a manual break in the program flow.
In addition, I've also found that using getuser[name] does not work in instances where getuser.name does work, which makes no sense and further infuriates me. Please help, it's 2:12am here.
Here is the official documentation.
Here is a relevant Stackoverflow question, which may be the solution I'm looking for but I don't understand it.
What really confounds me is that the function following .then is supposedly reliant on the data being confirmed, which obviously isn't the case.
This is the code that worked for me:
//Registers user credentials with input [cmd, user, pass]
var auth = function ($usr) {
var db = firebase.database().ref('chatapp/users/' + $usr[1]);
var getuser;
user = {'name': $usr[1], 'pass': $usr[2]};
db.once('value').then(function(snapshot) {
getuser = snapshot.val();
if ($usr[0] === "/register") {
if (getuser === null) {
db.set(user);
notify('Registered ' + $usr[1] + ' with pass "' + $usr[2] + '"');
} else {
notify('Username "' + $usr[1] + '" already taken, try again');
}
} else if ($usr[0] === "/login") {
if (getuser.name !== $usr[1] || getuser.pass !== $usr[2]) {
notify('Invalid credentials ' + $usr[1] + ':' + $usr[2]);
} else {
notify($usr[1] + ' logged in');
}
}
});
};
The question was not actually my issue. What led me to believing that retrieval of the data was being delayed is that my code had initially been outside of the .once() method's callback function. If you use .once()'s callback function to write snapshot.val() to a variable, and then write the rest of your code outside of the callback function, the data is written asynchronously and your variable will be set to undefined until the true value can be retrieved from the server. I believe that a workaround for this is calling a function with the parameter snapshot.val() from within the callback, or simply writing your code within the callback (above).
The actual issue with my code was with the line if (getuser.name !== $usr[1]). This causes an error if the value of getuser is null. The fixed code is above.
I have the following javascript promise, I'm looping through a list of documents, upload them one by one to Dropbox (API call), get back a shared link for each one of the documents, save them in an array and then generate an email with these links.
docs = self.checkedDocs();
body = "Please click on the link(s) below to view your document(s): ";
$.when.apply($, docs.map(function (doc) {
return self.service.getDropboxLink(doc).then(function (dropboxLink) {
return lineBreak + doc.documentDescription() + ": " + dropboxLink;
});
})).done(function () {
var attachment = [].join.call(arguments, '');
formatEmail(attachment, body);
});
What I'm trying to do is the exact same thing but for only one document, I understand that I don't need the map anymore but I'm not sure how to do it.
Could you help?
The $.when construct is used precisely because you want to wait for several promises. With only one document, most of the complexity goes away:
self.service.getDropboxLink(doc).then(function(dropboxLink) {
var attachment = doc.documentDescription() + ": " + dropboxLink;
openEmail("Please click on the link below to view your document: ", attachment);
});