Categories
Uncategorized

Palindrometer: The Twitter Bot that Finds Palindromes

I wrote a bot recently that searches tweets for palindromes. A palindrome is any word, phrase, or sequence of numbers that is the same when written backwards. The number 101, the town of Wassamassaw, SC, the word “madam”, and the band name ABBA are all palindromes. The most famous one is, “A man, a plan, a canal – Panama.”

This isn’t the first Twitter bot I’ve written, but it is the first one that I feel is interesting enough to share. You can take a look at it on Twitter to see what it is up to.

I set it up so that it only finds multi-word palindromes (so “Hannah,” “Anna,” “mom”, and “dad” are all out unless they are part of a larger palindromic phrase) and they must be 9 characters or longer, excluding spaces. That way its activity is somewhat throttled and the quality of palindromes found is higher. Theoretically. This is Twitter we’re talking about.

Why is this something that exists?

Purely for fun. Given enough time, the bot could find the next, “A man, a plan, a canal – Panama.” That would be pretty cool. Since I last checked it this morning it has retweeted tweets that include:

  • “forever of”
  • “never even”
  • “did it, I did”
  • and my favorite, “dammit I’m mad”

For now I hardcoded those into the bot so that it doesn’t repeat them, but when I get to it I will hook a database up to the bot so that it can add found phrases to the database and then check new ones against that set so it doesn’t repeat itself.

How it works

The fun part for me was writing the code that parses tweets and then finds symmetry across multiple words in the tweet. First, the bot parses each Tweet it can get (it can’t get all Tweets) by removing any punctuation, multiple spaces, and capital letters. That leaves it with just the words and numbers in the tweet.

Next it puts each word or number into an array, and from that array creates a new array of every possible combination of two or more sequential words or numbers. For example the 4 word tweet “hey what is new,” would be broken up into these 6 segments: “hey what,” “hey what is,” “hey what is new,” “what is,” “what is new,” and “is new.”

The bot then runs a function on each segment that looks for symmetry. That function, as you might have guessed, starts with the first and last character of each segment and works its way to the middle character (or pair of characters if the segment contains an even number of total characters) checking for matches. If they all match, then there is symmetry in that segment and the bot has found a palindrome.

Categories
Uncategorized

Adwords Script for Limiting Monthly Spend

August 2021 Update

Some nice people reached out to let me know that the 2019 script began failing once Google made some updates to their API. The below script should address those issues. The formatting makes it not very readable here, so if you prefer I’ve also posted it to Github.

//Author: Jake Ratliff, June 2019. Updated August 2021.

var MONTHLY_BUDGET = 50; //change this to your monthly budget max
var EXCLUDED_LABELS = '["branded"]'
//you can add any labels you may want to exclude in future to this list. I only used "branded" as an example label.
//the quotes around your label name are required, it is how this script can read your labels
//for example: '["branded", "special campaign", "holiday sale", "geotarget USA"]'

function main() {
var itsFirstOfTheMonth = ((new Date()).getDate() == 1); //you can test this any time by setting to true
var totalCostMTD = getTotalCost().toFixed(2); //you can test this at any spend level by setting to any number
console.log("Total cost this month: $" + totalCostMTD +
"; monthly budget: $" + MONTHLY_BUDGET
);
if (totalCostMTD >= MONTHLY_BUDGET) {
console.log("Total spend for campaigns not listed in EXCLUDED_LABELS has reached monthly budget");
applyLabel();
pauseCampaigns();
} else {
console.log("Total spend for campaigns not listed in EXCLUDED_LABELS is under their monthly budget - no changes.");
};
if (itsFirstOfTheMonth) {
reenableCampaigns();
};
};

function getTotalCost() {
var campIter = AdsApp.campaigns()
.withCondition('LabelNames CONTAINS_NONE ' + EXCLUDED_LABELS)
.get();
var totalCost = 0;
while (campIter.hasNext()) {
totalCost += campIter.next().getStatsFor("THIS_MONTH").getCost();
};
return totalCost;
};

function labelExists(labelToCheck) {
var labelIterator = AdsApp.labels().get();
console.log(labelIterator);
while (labelIterator.hasNext()) {
var label = labelIterator.next();
console.log(label.getName())
if (label == labelToCheck) {
return true
} else {
return false
}
}
}

function getAccountLabelNames() {
var labelNames = [];
var iterator = AdsApp.labels().get();
while (iterator.hasNext()) {
label = iterator.next().getName();
console.log(label)
labelNames.push(label);

}
return labelNames;
}

function applyLabel() {
var labelName = 'Paused by Budget Script';
var existingLabels = getAccountLabelNames();
if (existingLabels.indexOf(labelName) == -1) {
AdsApp.createLabel(labelName);
}
var campaignIterator = AdsApp.campaigns()
.withCondition('CampaignStatus = ENABLED')
.withCondition('LabelNames CONTAINS_NONE ' + EXCLUDED_LABELS)
.get();
while (campaignIterator.hasNext()) {
var campaign = campaignIterator.next();
console.log("label " + labelName + " applied to " + campaign)
campaign.applyLabel(labelName);
};
console.log('labels applied.');
};

function pauseCampaigns() {
var campaignIterator = AdsApp.campaigns()
.withCondition('CampaignStatus = ENABLED')
.withCondition('LabelNames CONTAINS_NONE ' + EXCLUDED_LABELS)
.get();
while (campaignIterator.hasNext()) {
var campaign = campaignIterator.next();
campaign.pause();
};
console.log('Campaigns not listed in EXCLUDED_LABELS paused');
};

function reenableCampaigns() {
var label = AdsApp.labels()
.withCondition('Name = "Paused by Budget Script"')
.get().next();

var campaignIterator = label.campaigns().get();

while (campaignIterator.hasNext()) {
var campaign = campaignIterator.next();
campaign.removeLabel('Paused by Budget Script');
campaign.enable();
};
console.log('First of the month: campaigns reenabled')
};

June 2019 Update

Someone reached out to me on LinkedIn today and said that they are using an Adwords script I wrote but they would like to not apply it to some campaigns. I thought that was a good idea and I wrote up an updated version that allows for just that. If you don’t need that, the original script automatically pauses all active campaigns once ad spend reaches your predefined limit. Then, on the first day of the next month, it unpauses only the campaigns that it paused in the previous month. The original script is here.

Here is the updated version:

//Author: Jake Ratliff, June 2019
var MONTHLY_BUDGET = 50; //change this to your monthly budget max
var EXCLUDED_LABELS = '["branded"]' 
//you can add other labels you may want to exclude in future to this list
//for example: '["branded", "special campaign", "holiday sale", "geotarget USA"]'

function main() {
    var itsFirstOfTheMonth = ((new Date()).getDate() == 1);
    var totalCostMTD = getTotalCost().toFixed(2);
    Logger.log("Total cost this month: $" + totalCostMTD +
        "; monthly budget: $" + MONTHLY_BUDGET
    );
    if (totalCostMTD >= MONTHLY_BUDGET) {
        Logger.log("Total spend for campaigns not listed in EXCLUDED_LABELS has reached monthly budget");
        applyLabel();
        pauseCampaigns();
    }else{
    	Logger.log("Total spend for campaigns not listed in EXCLUDED_LABELS is under their monthly budget - no changes.");
    };
    if (itsFirstOfTheMonth) {
        reenableCampaigns();
    };
};

function getTotalCost() {
    var campIter = AdWordsApp.campaigns()
    .withCondition('LabelNames CONTAINS_NONE ' + EXCLUDED_LABELS)
    .get();
    var totalCost = 0;
    while (campIter.hasNext()) {
        totalCost += campIter.next().getStatsFor("THIS_MONTH").getCost();
    };
    return totalCost;
};

function labelExists(labelToCheck) {
  var labelIterator = AdsApp.labels().get();
  Logger.log(labelIterator);
  while (labelIterator.hasNext()) {
    var label = labelIterator.next();
    Logger.log(label.getName())
    if(label == labelToCheck){
    	return true
    }else{
    	return false
    }
  }
}

function getAccountLabelNames() {
  var labelNames = [];
  var iterator = AdsApp.labels().get();
  while (iterator.hasNext()) {
    labelNames.push(iterator.next().getName());
    Logger.log("");
  }
  return labelNames;
}
  
function applyLabel() {
    var labelName = 'Paused by Budget Script';
	var existingLabels = getAccountLabelNames();
  	if (existingLabels.indexOf(labelName) == -1){
    	AdWordsApp.createLabel(labelName);
    }
    var campaignIterator = AdWordsApp.campaigns()
        .withCondition('CampaignStatus = ENABLED')
    	.withCondition('LabelNames CONTAINS_NONE ' + EXCLUDED_LABELS)
        .get();
    while (campaignIterator.hasNext()) {
        var campaign = campaignIterator.next();
      	Logger.log("label " + labelName + " applied to " + campaign)
        campaign.applyLabel(labelName);
    };
    Logger.log('labels applied.');
};

function pauseCampaigns() {
    var campaignIterator = AdWordsApp.campaigns()
        .withCondition('CampaignStatus = ENABLED')
        .withCondition('LabelNames CONTAINS_NONE ' + EXCLUDED_LABELS)
        .get();
    while (campaignIterator.hasNext()) {
        var campaign = campaignIterator.next();
        campaign.pause();
    };
    Logger.log('Campaigns not listed in EXCLUDED_LABELS paused');
};

function reenableCampaigns() {
    var label = AdWordsApp.labels()
        .withCondition('Name = "Paused by Budget Script"')
        .get().next();

    var campaignIterator = label.campaigns().get();

    while (campaignIterator.hasNext()) {
        var campaign = campaignIterator.next();
        campaign.removeLabel('Paused by Budget Script');
        campaign.enable();
    };
    Logger.log('First of the month: campaigns reenabled')
};

Original Monthly Spend Script

I want to share this Adwords script I wrote a few months ago because I thought it might help some account managers who were having a similar problem. Adwords allows you to set daily budgets for your campaigns, but there is not a way to set a monthly budget.

There are some features that come close, but in my opinion don’t quite do the trick. For example, there is Manager Defined Spend (MDS) but that is only useful for agencies that manage multiple accounts, and even then it might not be the ideal method. The shared budgets feature lets you set monthly limits for campaigns that share a budget, but what if you don’t want to use a shared budget? Nine times out of ten you will want to allot varied daily budgets to your campaigns, so that you can reward the high converters with a greater share of the budget.

I wrote the script below to add what I consider to be a basic feature to Adwords. With this bit of code, you simply provide your monthly budget and Adwords will pause all active campaigns in the account if their total spend month-to-date meets that number. Then, on the first day of the next month, it will enable those campaigns once again.

To use this script, copy and paste it into your Adwords account under Bulk Operations >> Scripts >> New Script.

//Author: Jake Ratliff
//April 14, 2016

var MONTHLY_BUDGET = YOUR_BUDGET_GOES_HERE;

//NOTE: set MONTHLY_BUDGET to a number slightly less than
//your actual monthly budget and set this script to run
//hourly. Setting this variable to less than actual budget
//will keep you from going over between hours.


//This is the main function, which Adwords calls when the
//script is run, so it must be named main. In our main
//function we are logging the total cost month-to-date,
//checking if that number is greater than the specified
//budget, and if it is, we apply a label to all active
//campaigns and then pause all active campaigns. Finally
//we check if it is the first day of the month, and if it
//is, we re-enable all the campaigns that have the label
//that we applied earlier.

function main() {
    var itsFirstOfTheMonth = ((new Date()).getDate() == 1);
    var totalCostMTD = getTotalCost().toFixed(2);
    Logger.log("Total cost this month: $" + totalCostMTD +
        "; monthly budget: $" + MONTHLY_BUDGET
    );

    if (totalCostMTD >= MONTHLY_BUDGET) {
        Logger.log("spend has reached monthly budget");
        applyLabel();
        pauseCampaigns();
    };

    if (itsFirstOfTheMonth) {
        reenableCampaigns();
    };

};

function getTotalCost() {
    var campIter = AdWordsApp.campaigns().get();
    var totalCost = 0;
    while (campIter.hasNext()) {
        totalCost += campIter.next().getStatsFor("THIS_MONTH").getCost();
    };
    return totalCost;
};

function applyLabel() {
    var labelName = 'Active Last Month';
    AdWordsApp.createLabel(labelName);

    var campaignIterator = AdWordsApp.campaigns()
        .withCondition('CampaignStatus = ENABLED')
        .get();
    while (campaignIterator.hasNext()) {
        var campaign = campaignIterator.next();
        campaign.applyLabel(labelName);
    };
    Logger.log('labels applied.');
};

function pauseCampaigns() {
    var campaignIterator = AdWordsApp.campaigns()
        .withCondition('CampaignStatus = ENABLED')
        .get();
    while (campaignIterator.hasNext()) {
        var campaign = campaignIterator.next();
        campaign.pause();
    };
    Logger.log('enabled campaigns paused');
};

function reenableCampaigns() {

    var label = AdWordsApp.labels()
        .withCondition('Name = "Active Last Month"')
        .get().next();

    var campaignIterator = label.campaigns().get();

    while (campaignIterator.hasNext()) {
        var campaign = campaignIterator.next();
        campaign.removeLabel('Active Last Month');
        campaign.enable();
    };
    Logger.log('First of the month: campaigns reenabled')
};
Categories
Uncategorized

Quizzly

Last week I launched Quizzly. It is a free web app you can use to quickly make, distribute, and save multiple choice quizzes. Log in to create a quiz. Find a topic you like and explore.

It is in Beta, so it is very close to its final form, but some new features and design changes might happen. I might even change the name. All of your quizzes will still be there.

Thanks for checking it out, have fun!

-Jake