Trying to Get a Twitter Bot to Tweet Two Images at Once - javascript
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.
Related
How to set imghash value to search for the best match?
I m using imghash to make a discord bot (npm i imghash) I found a very small error that it read image perfectly. But there is a small problem. Let imagine if the hash id is d780a1b1a9838393 and i saved hash id of that image d780a1b1a9838392 then it will not provide the response. I hashed to many images and use that for detective game in our server. Best part is that bot the images is same just a bit diff. that the quality. I want the imghash to find for the best match if the hash id did not match 100% then bot will still response with an additional text that maybe or something i set. THIS IS MY CODE I M USING TO MAKE RESPONSE let url = message.attachments.first().url request(url, async function(err, res, body) { if (err !== null) return; imghash .hash(body) .then(hash => { let result = db[hash]; if (result == undefined) { embed .setTitle('New Pokemon') .setDescription(`Hash ID: **${hash}** [message link](${message.url})`) .setImage(url) client.channels.get('691155351575199825').send(embed) client.channels.get('687233915974320148').send(url + ' ' + hash + " " + message.url) return message.channel.send("There is an error you maybe using `image address` rather than that use `Link Address`. This error may have any of these problems: \n1) You are using screenshots rather than URL. Which will not work righ now. So use a URL. \n2) You are using URL but its the image adress. Actually image address reduce the quality of image so use link address."); } message.reply(result) console.log("[" + message.guild.name + "/#" + message.channel.name + "] " + " " + message.author.tag + " " + result + " " + hash); }) })``` if you think i need to improve something for that then what changes should i made?
FileSaver.js Chrome Issue, Multiple documents
I'm using Chrome v67 and FileSaver.js. This code works in FF & Edge but not in Chrome. var ajaxSettings = { url: "my/api/method", data: JSON.stringify(myItems), success: function (data) { if (data) { var dateToAppend = moment().format("YYYYMMDD-HHmmss"); createPdf("pdfDoc_" + dateToAppend + ".pdf", data[0]); saveFile("txtDoc_" + dateToAppend + ".txt", data[1]); //sleep(2000).then(() => { // saveFile("txtDoc_" + dateToAppend + ".txt", data[1]); //}); } } } //function sleep(time) { //return new Promise((resolve) => setTimeout(resolve, time)); //} function createPdf(filename, pdfBytes) { if (navigator.appVersion.indexOf("MSIE 9") > -1) { window.open("data:application/pdf;base64," + encodeURIComponent(pdfBytes)); } else { saveFile(fileName, data); } } function saveFile(fileName, data) { var decodedByte64 = atob(data); var byteVals = new Array(decodedByte64.length); for (var i = 0; i < decodedByte64.length; i++) { byteVals[i] = decodedByte64.charCodeAt(i); } var byte8bitArray = new Uint8Array(byteVals); var blob = new Blob([byte8bitArray]); saveAs(blob, fileName); //FileSaver.js } The result of calling the API is an array with 2 byte arrays in it. The byte arrays are the documents. If I run this code, as a user would, then what happens is that the first document gets "downloaded" but the second does not. Attempting to do this a second time without refreshing the page results in no documents being "downloaded". The "download" word is in quotes because it already has been downloaded, what I'm really trying to do is generate the documents from the byte arrays. This is the strange bit ... if I open the console and place a breakpoint on the "saveFile" call and immediately hit continue when the debugger lands on the breakpoint then all is well with the world and the 2 documents get downloaded. I initially thought it was a timing issue so I put a 2 second delay on this to see if that was it but it wasn't. The only thing I've managed to get working is the breakpoint which I'm obviously not going to be able to convince the users to start doing no matter how much I want them to. Any help or pointers are much appreciated
You probably faced a message at the top left corner of your browser stating https://yoursite.com wants to Download multiple files [Block] [Allow] If you don't have it anymore, it's probably because you clicked on "Block". You can manage this restriction in chrome://settings/content/automaticDownloads.
JSON variable not retrieving updated value
I am trying to post a quote retrieved from a JSON file. My code posts a tweet every 20 seconds (for testing purposes it is 20 seconds). I can find my quote and put it in a JSON file by using the server(quoteIndex) function. The server(quoteIndex) adds a new quote to my output.json file. I know that output.json updates the {"quote": ""} part each time it finds a quote on an even index. However, in my tweetIt() function, when I assign var quoteFile = require("./output.json"), my quoteFile.quote value does not update. This is an issue because my javascript bot is a twitter bot that tweets quotes. And twitter does not allow duplicate quotes, handing me a "duplicate status" error. This is the main code // I wanted to start at the 8th index and continue with every even index. var quoteIndex = 8; // post a tweet every 20 seconds setInterval(tweetIt, 1000*20); tweetIt() function tweetIt() { // // This will start looking for quotes to post // It will put the quote in a JSON file // quoteIndex = quoteIndex + 2; console.log('value of index: ' + quoteIndex) server(quoteIndex); var js = require('json-update'); js.load('./output.json', function(err, obj) { console.log("Loaded from json:"); console.log(obj); // loads most recent quote }); // this is what I want to tweet var quoteFile = require('./output.json'); // Read from JSON file for the quote var params = { status: quoteFile.quote } // // prints same quote each time, does not update // console.log("quote to tweet: " + quoteFile.quote) // tweet a quote // // The first quote will tweet, however when the JSON file // updates, I will get a "duplicate status" error. // This is because my quoteFile still has not updated. // T.post('statuses/update', params, getData); function getData(err, data, response) { if (err) { console.log("Something went wrong!: " + err); } else { console.log("Tweeted something!"); } } } server(quoteNumber) function server(quoteNumber) { var fs = require('fs'); var request = require("request"), cheerio = require("cheerio"), url = "https://en.wikiquote.org/wiki/A_Song_of_Ice_and_Fire"; request(url, function (error, response, body) { if (!error) { var $ = cheerio.load(body); var quote; var json = {quote : ""}; $( "div.mw-parser-output ul" ).filter(function( INDEX ) { // even indexed quotes are the ones I want if (INDEX % 2 == 0 && (INDEX == quoteNumber)) { quote = $( this ).text(); json.quote = quote; // this also has the most recent quote } }); } else { console.log("We’ve encountered an error: " + error); } // // Write our quotes out to a JSON file. // fs.writeFile('output.json', JSON.stringify(json, null, 4).replace(/\\n/g, " "), function(err){ console.log('File successfully written! - Check your project directory for the output.json file'); }) }); Basically, when I run the code using node.js, the first quote will tweet because the JSON file has a quote for me. Then when it is time to find the next quote using quoteIndex = quoteIndex + 2, my JSON file updates as expected in my project folder. My main issues is that in my tweetIt() function, the quoteFile.quote is not showing the updated quote, even though the JSON file has an updated quote for me. How can I have the updated quote? Any tips would be appreciated, thanks.
Whenever you require() a file, be it a module or JSON file or native addon, it is cached once it is successfully loaded. If you need to be able to reload the JSON file, you should instead just call readFile() and JSON.parse() the resulting file data manually each time.
Avoid queries concatenation when redirecting few times in a row
So I'm working in an Express application and experiencing a strange behaviour. I'm displaying error messages when there is something that goes wrong. /app/edit/username?e=error1 It seems that after two errors happen in a row, the queries somehow concatenate: app/edit/username?e=error1?e=error2 I would like to avoid that to happen and display only error 2: app/edit/username?e=error2 I'm using Express redirect: return res.redirect('/app/edit/' + username + '?e=' + error Any idea of what could be going on? Thanks very much in advance and have a nice weekend! Pablo edit: The part of the function: User.findByIdAndUpdate(id, update, function (err, user) { if (err) { var error = dbErrorHandler(err) return res.redirect('/app/editar/' + username + '?e=' + error) } return res.redirect('/app/editar/' + username + '/?suc=upsuccess') }) and here is dbErrorHandler: function dbErrorHandler(err) { console.log('There is been an error' + err) if (String(err).indexOf('email_1') !== -1) return 'eexists' return 'unknown' } Further tips regarding my code are very welcome.
Remove the map function
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); });