Action

Tweet Storm

Posted by agiletortoise, Last update 9 months ago

NOTE: This action no longer functions due to policy changes at Twitter that are out of our control. Visit the Twitter integration guide for details.

Divide up a draft into separate tweets by paragraph. Build a preview of the tweets as they will be posted and display it. If “Continue” tapped from preview, post each of the tweets as a threaded tweet storm.

Steps

  • script

    // set identifier for Twitter credentials you wish to use
    const twitterIdentifier = "";
    // max characters per tweet. Leave room for /n.
    const maxCharacters = 248;
    
    // Utility functions
    function divideTweets(input) {
    	var paragraphs = input.split("\n\n");
    	return paragraphs;
    }
    function countString(current, total) {
    	return ` (${current}/${total})`;
    }
    const urlRegex = /((([A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)/g;
    function isTweetValid(tweet) {
    	var unlinked = tweet.replace(urlRegex, "00000000000000000000000");
    	return unlinked.length <= maxCharacters && unlinked.length > 0;
    }
    function areAllTweetsValid(tweets) {
    	if (tweets.length == 0) { return false; }
    	for (var tweet of tweets) {
    		if (!isTweetValid(tweet)) {
    			return false;
    		}
    	}
    	return true;
    }
    
    function htmlSafe(s) {
    	return s.replace(/</g, "&lt;").replace(/\n/g, "<br>\n");
    }
    
    // build HTML preview display the resulting tweets.
    var html = ["<html><head>"];
    html.push("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">");
    html.push("<style>");
    html.push("body { background: #666; color: #444; font-family: system-ui, -apple-system; margin: 1em auto; max-width:360px; }");
    html.push("p {padding: 1em; background-color: #eee; }");
    html.push("p.error{ background-color: #FFCDDD; color: maroon; font-weight: bold; padding: 1.5em; }\n");
    html.push("p.invalid{ color: maroon; }\n");
    html.push("p.reply { border-left: 5px solid #bbb; }");
    html.push("p.preview{ font-style:italic; background-color: #444; color: #aaa; font-size: .9em; }\n");
    html.push("span.note{ background-color: maroon; color: white; font-weight: bold; border-radius:3px; padding: .25em; font-size: .8em; }\n");
    html.push("</style>");
    html.push("</head><body>");
    
    const tweets = divideTweets(draft.content);
    var isValid = areAllTweetsValid(tweets);
    
    if (!isValid) {
    	html.push("<p class='error'>");
    	html.push("This draft cannot be posted as a tweet storm. Be sure to divide the draft into paragraphs of " + maxCharacters + " characters or shorter.");
    	html.push("</p>");
    }
    else {
    	html.push("<p class='preview'>");
    	html.push("Preview tweets as they will be posted below. Tap continue to post the tweet storm, cancel to go back without posting");
    	html.push("</p>");
    }
    
    if (tweets.length > 0) {
    	var ct = 1;
    	for (var tweet of tweets) {
    		if (isTweetValid(tweet)) {
    			html.push("<p class='valid" + (ct > 1 ? " reply" : "") + "'>");
    			html.push(htmlSafe(tweet));
    			html.push(countString(ct, tweets.length));
    			html.push("</p>");
    		}
    		else {
    			html.push("<p class='invalid" + (ct > 1 ? " reply" : "") + "'>");
    			html.push("<span class='note'>" + tweet.length + " characters</span> ");
    			html.push(htmlSafe(tweet));
    			html.push(countString(ct, tweets.length));
    			html.push("</p>");
    		}
    		ct++;
    	}
    }
    else {
    	html.push("<p>Nothing to tweet</p>");
    }
    
    html.push("</body></html>");
    draft.setTemplateTag("html", html.join("\n"));
    
    
  • htmlpreview

    [[html]]
  • script

    // If we reach this script
    // We have a valid set of tweets ready
    // in the `tweets` const, we just need to post them...
    
    // a few base values
    const updateURL = "https://api.twitter.com/1.1/statuses/update.json";
    
    // sleep method to use to avoid rate limiting
    const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
    
    if (isValid) {
    	var twitter = Twitter.create(twitterIdentifier);
    	var ct = 1;
    	var inReplyTo = "";
    	
    	var hasError = false;
    	for (var tweet of tweets) {
    		const tweetContent = tweet + countString(ct, tweets.length);
    		
    		var params = {
    			"status": tweetContent
    		}
    		// if this is the first tweet
    		// set the reply information to thread tweet storm
    		if (ct != 1) {
    			params["in_reply_to_status_id"] = inReplyTo;
    			console.log(inReplyTo);
    		}
    		// make API request
    		var response = twitter.request({
    			"url": updateURL,
    			"method": "POST",
    			"parameters": params
    		});
    		if (response.statusCode == 200 || response.statusCode == 201) {
    			var id = response.responseData.id_str;
    			var sn = response.responseData.user.screen_name
    			var tweetURL = `http://twitter.com/${sn}/status/${id}`;
    			inReplyTo = id;
    			console.log("Tweet posted: " + tweetURL);
    		}
    		else {
    			console.log("Error: " + response.error);
    			hasError = true;
    			break;
    		}
    
    		ct++;
    		sleep(400); // avoid rate limiting with little pause
    	}
    	if (!hasError) {
    		console.log("Tweet storm posted");
    	}
    	else {
    		console.log("Tweet storm failed");
    		context.fail();
    	}
    }
    else {
    	context.cancel();
    }

Options

  • After Success Default
    Notification Info
    Log Level Info
Items available in the Drafts Directory are uploaded by community members. Use appropriate caution reviewing downloaded items before use.