<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0"><channel><atom:link rel="hub" href="http://tumblr.superfeedr.com/" xmlns:atom="http://www.w3.org/2005/Atom"/><description></description><title>Automate Everything w/ Bash, Linux &amp; Command Line</title><generator>Tumblr (3.0; @automateeverything)</generator><link>http://adambuchanan.me/</link><item><title>Pinterest Pin It Button/Bookmarklet Hybrid</title><description>&lt;p&gt;AKA - Pinterest &amp;#8220;Pin It&amp;#8221; Button Implementation Revisit&lt;/p&gt;

&lt;p&gt;I wrote a post about a year ago about how to install the Pinterest Pin It button. Why would I have to do that? Well, because the code generated in the developer docs was incomplete, confusing and led to many many broken implementations on sites. I know that&amp;#8217;s true because of how popular that post was.&lt;/p&gt;

&lt;p&gt;Read the old post &lt;a href="http://automateeverything.tumblr.com/post/19106283833/pinterest-pin-it-button-implementation-problems-and" target="_blank"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;Pin It - A Year Later&lt;/h2&gt;

&lt;p&gt;Well, I&amp;#8217;m writing this post because many of the same issues still exist. They have done a better job of making sure that people understand how to install the code, but they&amp;#8217;ve still allowed it to be easy for people to install it incorrectly on their sites. The biggest problem is when the &lt;code&gt;media&lt;/code&gt; parameter isn&amp;#8217;t tagged properly in the button &lt;code&gt;href&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The original version would just fail if an image wasn&amp;#8217;t specified. The new version allows you to pin the page, with a broken image. How much sense does that make?&lt;/p&gt;

&lt;h2&gt;The Pin It Bookmarklet Works&amp;#8230;&lt;/h2&gt;

&lt;p&gt;What makes all this worse is that the Pinterest bookmarklet that they&amp;#8217;ve had since the very beginning has more functionality in it than the button does. Ever notice that? It&amp;#8217;s really mind-boggling.&lt;/p&gt;

&lt;p&gt;Why wouldn&amp;#8217;t they just mimic the functionality of the bookmarklet with the Pin It button? When you use the bookmarklet it gives you a gallery of images for you to choose from to use in your pin. That makes the bookmarklet usable on many different types of pages with no configuration required. Whereas the button has to have one image hard coded to it. This makes zero sense.&lt;/p&gt;

&lt;p&gt;So I thought&amp;#8230; Why not just rewrite the button functionality so that it calls the Javascript from the bookmarklet instead of the code the button would normally run? Would that work? YES IT DOES!&lt;/p&gt;

&lt;h2&gt;The Pin It Button - Pin It Bookmarklet Hybrid&lt;/h2&gt;

&lt;p&gt;The implementation is really simple. Just start out by adding the following code, as recommended by Pinterest, on the page where you want the button to appear. The one change is that I&amp;#8217;ve added an ID to a surrounding &lt;code&gt;&amp;lt;span&amp;gt;&lt;/code&gt; tag.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;span id="pin-it"&amp;gt;&amp;lt;a href="//pinterest.com/pin/create/button/"&amp;gt;&amp;lt;img src="//assets.pinterest.com/images/pidgets/pin_it_button.png" alt="image" /&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/span&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Then, just add the following script to your page. Make sure you have the jQuery library on the page as well.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;script type="text/javascript"&amp;gt;
    jQuery.getScript('//assets.pinterest.com/js/pinit.js', function() {
        jQuery('#pin-it a').attr('href', '#');
        jQuery('#pin-it a').attr('onclick', "javascript:void((function(d){e=d.createElement('script');e.setAttribute('type','text/javascript');e.setAttribute('charset','UTF-8');e.setAttribute('src','//assets.pinterest.com/js/pinmarklet.js?r='+Math.random()*99999999);d.body.appendChild(e);}(document)));");
    });
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;That&amp;#8217;s it. You should now get the bookmarklet action when the button is clicked, which will show a gallery of images on the page to choose from to use in your pin.&lt;/p&gt;

&lt;p&gt;Here&amp;#8217;s an example implemented right here:
&lt;span id="pin-it"&gt;&lt;a href="//pinterest.com/pin/create/button/" target="_blank"&gt;&lt;img src="//assets.pinterest.com/images/pidgets/pin_it_button.png" alt="image"/&gt;&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;

&lt;script type="text/javascript"&gt;
    jQuery.getScript('//assets.pinterest.com/js/pinit.js', function() {
        jQuery('#pin-it a').attr('href', '#');
        jQuery('#pin-it a').removeAttr('target');
        jQuery('#pin-it a').attr('onclick', "javascript:void((function(d){e=d.createElement('script');e.setAttribute('type','text/javascript');e.setAttribute('charset','UTF-8');e.setAttribute('src','//assets.pinterest.com/js/pinmarklet.js?r='+Math.random()*99999999);d.body.appendChild(e);}(document)));");
    });
&lt;/script&gt;&lt;p&gt;Anyway&amp;#8230; Happy pinning!&lt;/p&gt;</description><link>http://adambuchanan.me/post/43695084420</link><guid>http://adambuchanan.me/post/43695084420</guid><pubDate>Thu, 21 Feb 2013 22:08:00 -0500</pubDate><category>Pinterest</category><category>jquery</category><category>bookmarklet</category></item><item><title>Big Data, Privacy &amp; Ad Targeting</title><description>&lt;p&gt;I think it&amp;#8217;s important to talk about my perspective and potential bias. I&amp;#8217;m a digital marketer. It&amp;#8217;s what I do. But, sometimes I feel like &lt;a href="http://en.wikipedia.org/wiki/Two-Face" target="_blank"&gt;Harvey Dent&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src="http://dl.dropbox.com/u/56691816/automateeverything/img/Harvey-Dent-Two-Face.jpg"/&gt;
{&lt;a href="http://wac.450f.edgecastcdn.net/80450F/guyspeed.com/files/2012/04/Harvey-Dent-Two-Face.jpg" target="_blank"&gt;Image Source&lt;/a&gt;}&lt;/p&gt;

&lt;p&gt;I use cookie based tracking data every day for website optimization, ad targeting and business intelligence. There&amp;#8217;s no way to deny how powerful an understanding of the information available, even with a vanilla install of Google Analytics, can be to your business. There are often very enlightening stories about what website users want, need or don&amp;#8217;t understand that will help you improve your product. The structure, quantity, organization and analysis of this data is getting better at a fanatic pace. I feel like the big gap between how this data is used and how the general population speculates about it&amp;#8217;s use is just too wide.&lt;/p&gt;

&lt;h2&gt;Or, Popular Are Privacy Concerns Right On Target?&lt;/h2&gt;

&lt;p&gt;I continually ask myself that question. As a consumer, I quit using Facebook. It wasn&amp;#8217;t all about privacy but it had a role in my decision. But Facebook isn&amp;#8217;t the only offender. Not by a long shot.&lt;/p&gt;

&lt;p&gt;Recently there have been a few time where I&amp;#8217;ve really had to stop, think and question my perspective on ethics surrounding online tracking technology. A couple of those prompted this post.&lt;/p&gt;

&lt;h2&gt;De-Anonymizing Tracking Data&lt;/h2&gt;

&lt;p&gt;What does that mean? Well, all the tracking data provided in common analytics packages like Google Analytics is anonymous until the visitor fills out a form and tells you who they are. This level of privacy is by design.&lt;/p&gt;

&lt;p&gt;There are companies springing up that seek to change that. They pool data together from sites across their network partners that track the behavior of a visitor. When that visitor identifies him/herself on site A, then all the partner sites will know who that anonymous visitor was without the visitor actually telling site B, C or D.&lt;/p&gt;

&lt;p&gt;Here&amp;#8217;s the &lt;a href="http://42floors.com/blog/youre-not-anonymous-i-know-your-name-email-and-company/" target="_blank"&gt;post I read&lt;/a&gt; that explains this much more effectively than I can. It&amp;#8217;s creepy and I think it&amp;#8217;s unethical.&lt;/p&gt;

&lt;p&gt;It isn&amp;#8217;t just new emerging businesses or technologies that can be creepy/evil. In fact, the bigger data players like Google have the ability to make these connections now. But are they?&lt;/p&gt;

&lt;h2&gt;When Ads Are Too Personal&lt;/h2&gt;

&lt;p&gt;Until now, the news of my wife and I separating hasn&amp;#8217;t been public. The decision was made about 1 month ago. My communication of that with friends, family and co-workers has been limited to word of mouth, GTalk and SMS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Imagine my surprise when I saw this ad at the top of my GMail account last night&amp;#8230;&lt;/strong&gt;
&lt;img src="http://dl.dropbox.com/u/56691816/automateeverything/img/creepy.gmail.ad.jpg"/&gt;&lt;/p&gt;

&lt;p&gt;If you cannot imagine - I was creeped the fuck out.&lt;/p&gt;

&lt;p&gt;After the shock of it wore off I started to think about how it was possible for this ad to be so spot on. To be honest, I have no way of knowing. And because I don&amp;#8217;t know I&amp;#8217;m creeped out. I don&amp;#8217;t like it. Instead of ads being relevant, useful and persuasive, this ad gave me chills (not the good kind).&lt;/p&gt;

&lt;p&gt;So, how? One can only speculate. Perhaps the add was just a coincidence. Maybe GTalk conversations are monitored for content based targeting just like GMail messages are. There could be any number of reasons.&lt;/p&gt;

&lt;p&gt;The point of this post is that it doesn&amp;#8217;t matter how tracking and targeting works online. I believe people prefer ads that are relevant to their interests, but they need to understand how and why ads can be so accurate at times. They also need to have the tools necessary to opt-in or out of this targeting. Without the transparency people will be left to speculate and become more upset about how their privacy is being abused online.&lt;/p&gt;</description><link>http://adambuchanan.me/post/37920736645</link><guid>http://adambuchanan.me/post/37920736645</guid><pubDate>Fri, 14 Dec 2012 14:03:26 -0500</pubDate><category>analytics</category><category>privacy</category><category>ad targeting</category></item><item><title>IBM Model M Keyboard Collection - On Sale</title><description>&lt;p&gt;It&amp;#8217;s time. I&amp;#8217;m going to part ways with my small collection of buckling spring IBM Model M keyboards (the original clicky keyboard).&lt;/p&gt;

&lt;p&gt;Why? Well, because I have too many keyboards and I have an upcoming project and want to replenish my PayPal account. I&amp;#8217;m going to list them on eBay but I thought I&amp;#8217;d list them here just because. So, here they are&amp;#8230;&lt;/p&gt;

&lt;h2&gt;IBM Model M, Part No 1391401 - Produced on April 8, 1988&lt;/h2&gt;

&lt;p&gt;&lt;a href="http://cgi.ebay.com/ws/eBayISAPI.dll?ViewItem&amp;amp;item=110960253978" target="_blank"&gt;Bid on this keyboard&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is considered a &amp;#8220;white&amp;#8221; label IBM Model M keyboard and is fully functional. It has very low yellowing and types very nicely. I&amp;#8217;ve used it as my main keyboard for the past two plus years and it&amp;#8217;s  been very reliable. It has the curly style removable cable and is in great condition. The feet on the back are also in great condition, which is more rare because they&amp;#8217;re known to be a weak point. I don&amp;#8217;t use them so I haven&amp;#8217;t added any wear during my time using it.&lt;/p&gt;

&lt;p&gt;They buyer of this keyboard is going to get a very well taken care of keyboard that is very likely to out last the machine he/she will use it on.&lt;/p&gt;

&lt;p&gt;Here are a couple of images:&lt;/p&gt;

&lt;p&gt;&lt;img src="http://dl.dropbox.com/u/56691816/model-m/2012-10-03%2021.00.39.jpg"/&gt;&lt;/p&gt;

&lt;p&gt;An image of the label - part number and production date.&lt;/p&gt;

&lt;p&gt;&lt;img src="http://dl.dropbox.com/u/56691816/model-m/2012-10-03%2021.01.08.jpg"/&gt;&lt;/p&gt;

&lt;p&gt;I will post back a&lt;a href="http://cgi.ebay.com/ws/eBayISAPI.dll?ViewItem&amp;amp;item=110960253978" target="_blank"&gt; link to the auction&lt;/a&gt; as soon as it&amp;#8217;s up. If you&amp;#8217;re interested in purchasing it outside of the auction then please comment below or reach out to me on Google+ and we&amp;#8217;ll see if we can work out a deal.&lt;/p&gt;

&lt;h2&gt;IBM Model M, Part No 52G9685 - Produced on January 4, 1996&lt;/h2&gt;

&lt;p&gt;&lt;a href="http://www.ebay.com/itm/110960255662" target="_blank"&gt;Bid on this keyboard&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This keyboard is also technically a IBM Model M, but in the later years these keyboards were produced by Lexmark for IBM, as you&amp;#8217;ll see in the images of the back label below. With that said, this keyboard works very well. There are three main differences between this keyboard and the older &amp;#8220;white&amp;#8221; label Model Ms:&lt;/p&gt;

&lt;ol&gt;&lt;li&gt;The key caps are not removable. Most of the time this isn&amp;#8217;t a big deal, in fact it makes the caps much more difficult to lose. It also makes them a lot harder to clean.&lt;/li&gt;
&lt;li&gt;The cable isn&amp;#8217;t detachable. This really only becomes a problem if the cable it&amp;#8217;s self breaks. &lt;/li&gt;
&lt;li&gt;While the keypress is still very clicky, it&amp;#8217;s also a bit more soft. This is either appealing or not. It really just depends on your personal typing preference. &lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;While this keyboard is a bit less rare than the other two I&amp;#8217;m selling, it is bound to have fewer keystrokes and is almost a decade newer. It&amp;#8217;s a very reliable keyboard and all the keys feel consistent type nicely. I&amp;#8217;ve used this keyboard for the past year and a half at work. The one thing that&amp;#8217;s wrong with it is that one of the feet is broken. It never bothered me because as I mentioned above, I don&amp;#8217;t use the feet.&lt;/p&gt;

&lt;p&gt;Here are a couple pictures:&lt;/p&gt;

&lt;p&gt;&lt;img src="http://dl.dropbox.com/u/56691816/model-m/2012-10-03%2021.22.54.jpg"/&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="http://dl.dropbox.com/u/56691816/model-m/2012-10-03%2021.23.12.jpg"/&gt;&lt;/p&gt;

&lt;p&gt;Again, I&amp;#8217;ll post a link to it once&lt;a href="http://www.ebay.com/itm/110960255662" target="_blank"&gt; it&amp;#8217;s up on eBay&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;IBM Model M, Part No 1391401 - Produced on June 7, 1987&lt;/h2&gt;

&lt;p&gt;&lt;a href="http://cgi.ebay.com/ws/eBayISAPI.dll?ViewItem&amp;amp;item=110960256985" target="_blank"&gt;Bid on this keyboard&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the final IBM Model M I have to offer. It&amp;#8217;s the oldest one and share many of the same features of the first one posted above. It&amp;#8217;s considered a &amp;#8220;white&amp;#8221; label and is almost as old as I am&amp;#8230;&lt;/p&gt;

&lt;p&gt;It has the curly detachable cable that&amp;#8217;s in great shape. It has fully removable key caps for easy cleaning. It has strong buckling spring keys that are very clicky. It&amp;#8217;s exterior is in great condition. The only blemish on this one is that the enter key on the number pad isn&amp;#8217;t balanced properly because one of the support pieces is missing. It&amp;#8217;s very likely that it can be repaired later, but even in it&amp;#8217;s current state it can be used. Other than that, this keyboard is in great shape despite it being 25 years old!&lt;/p&gt;

&lt;p&gt;I haven&amp;#8217;t used this one much at all. For the past six months or so it has just been sitting in my closet. It&amp;#8217;s time to go. This keyboard was meant to be used.&lt;/p&gt;

&lt;p&gt;Here are a couple images:&lt;/p&gt;

&lt;p&gt;&lt;img src="http://dl.dropbox.com/u/56691816/model-m/2012-10-03%2021.39.20.jpg"/&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="http://dl.dropbox.com/u/56691816/model-m/2012-10-03%2021.39.51.jpg"/&gt;&lt;/p&gt;

&lt;p&gt;Once again, I&amp;#8217;ll post the link when &lt;a href="http://cgi.ebay.com/ws/eBayISAPI.dll?ViewItem&amp;amp;item=110960256985" target="_blank"&gt;it&amp;#8217;s up on eBay&lt;/a&gt;. If you&amp;#8217;d like to reach out and make an offer ahead of time please feel free to contact me on Google+. There&amp;#8217;s a link in the left navigation of this site.&lt;/p&gt;

&lt;p&gt;Please let me know if you have any questions about any of the keyboards listed here.&lt;/p&gt;</description><link>http://adambuchanan.me/post/32846148687</link><guid>http://adambuchanan.me/post/32846148687</guid><pubDate>Wed, 03 Oct 2012 21:49:00 -0400</pubDate><category>clicky keyboard</category><category>ibm model m</category><category>for sale</category></item><item><title>Magento: Automatically Apply a Coupon Code w/ jQuery</title><description>&lt;p&gt;I started working on planning a promotion a couple of days ago that would require a discount to be applied automatically. The site runs on Magento, which has very sophisticated coupon code and discounting tools. But this time, none of the built in options quite suited my needs.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;tl;dr;&lt;/b&gt; - I needed to apply a coupon code to a customer&amp;#8217;s cart, before they actually had anything in their cart and without the visitor doing anything more than visiting a specific landing page. I was able to accomplish my goal with a little creativity, jQuery and determination. &lt;b&gt;And, I don&amp;#8217;t know PHP so this is a front end solution using jQuery and cookies.&lt;/b&gt; Read on for the code and how it works&amp;#8230;&lt;/p&gt;

&lt;h2&gt;How to Setup a Discount in Magento&lt;/h2&gt;

&lt;p&gt;The first thing you&amp;#8217;ll need to do is setup a coupon code with an associated discount of some type in Magento. It doesn&amp;#8217;t really matter what type of coupon it is, you just need something to test with. I&amp;#8217;m not going to explain how to do that here but for demonstration&amp;#8217;s sake, I&amp;#8217;ll assume that the coupon code you&amp;#8217;ve chosen is &lt;code&gt;testcouponcode&lt;/code&gt; and it gives the customer 20% off.&lt;/p&gt;

&lt;p&gt;Don&amp;#8217;t be confused. Even though we&amp;#8217;re setting up a coupon code, we&amp;#8217;re not going to require any action  from the visitor. We&amp;#8217;re just going to use the back-end coupon capabilities of Magento to do the discounting in the shopping cart.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Before we go any further, make sure you manually test the coupon code you setup by going into the cart and applying it the old fashioned way.&lt;/b&gt; Trust me that if you made a mistake, it&amp;#8217;s much easier to discover and correct it at this stage.&lt;/p&gt;

&lt;h2&gt;A Few More Notes About Coupons in Magento&lt;/h2&gt;

&lt;ul&gt;&lt;li&gt;When the customer enters a coupon code and clicks the apply coupon button, the code makes a HTTP POST to &lt;code&gt;/checkout/cart/couponPost/&lt;/code&gt; with the following data - &lt;code&gt;remove=0&amp;amp;coupon_code=testcouponcode&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;A coupon code cannot be applied to the shopping cart if there are no items in the visitor&amp;#8217;s cart. &lt;/li&gt;
&lt;li&gt;In my case, if the customer doesn&amp;#8217;t checkout during the same session then I don&amp;#8217;t want the discount to apply.&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;With those three things in mind, let&amp;#8217;s dive into how I&amp;#8217;ve solved this problem.&lt;/p&gt;

&lt;h2&gt;&amp;#8220;Apply&amp;#8221; Discount on the Landing Page, well Kinda&amp;#8230;&lt;/h2&gt;

&lt;p&gt;Remember the introduction - I want to apply a discount based on the landing page. However, in Magento, this is technically impossible. You cannot apply the coupon code to trigger the discount right away on the landing page. Since it&amp;#8217;s the first page your visitor will have hit, the visitor will have zero items in their cart and that&amp;#8217;s the problem.&lt;/p&gt;

&lt;p&gt;No worries though, there&amp;#8217;s a way around that. All you have to do is create a session cookie that can be referenced if the visitor does end up putting items into his/her cart. The trigger will be the same (the landing page), but applying the discount will be technically deferred.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;To make the order of events easy to understand, here&amp;#8217;s a list:&lt;/b&gt;&lt;/p&gt;

&lt;ol&gt;&lt;li&gt;Visitor hits the special landing page.&lt;/li&gt;
&lt;li&gt;Javascript on the page sets a special session cookie, signifying that a discount should be applied once an item has been added to the cart. This cookie will automatically be deleted when the session ends.&lt;/li&gt;
&lt;li&gt;Once the visitor views his/her cart (and there are one or more items in it), Javascript on the shopping cart page will make a POST to apply the coupon code that was specified back on that special landing page. This POST will happen in the background and then refresh the page quickly.&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;Pretty simple right? Based on those three events, there will be two different blocks of code needed to make this work. One needs to be on the landing page and the other needs to be on the shopping cart view page.&lt;/p&gt;

&lt;h2&gt;jQuery for the Landing Page&lt;/h2&gt;

&lt;p&gt;Remember that the only goal for the code on this page is to set a session cookie that can be read and referenced later. I&amp;#8217;m a newb when it comes to Javascript and for most things I use jQuery. Unfortunately there&amp;#8217;s no built in way to write or read cookies in jQuery. The good news is that there&amp;#8217;s a really great plugin for it and using the plugin is as easy as downloading it and including it in the HTML of your site. You can get it &lt;a href="http://code.google.com/p/cookies/" target="_blank"&gt;here&lt;/a&gt;, hosted on Google Code.&lt;/p&gt;

&lt;p&gt;Here&amp;#8217;s the code:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;script type="text/javascript" src="/path/to/js/jquery.cookies.2.2.0.min.js"&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script type="text/javascript"&amp;gt;
    jQuery.cookies.set('nameOfCookie', 'testcouponcode', '/');
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;It&amp;#8217;s really simple. All you have to do is name the cookie, give it a value and set the path that the cookie should be valid for. Setting a path of &lt;code&gt;/&lt;/code&gt; means that it can be read or modified anywhere on the domain. I recommend making the value of the cookie equal to the actual coupon code. It&amp;#8217;s most simple that way and I don&amp;#8217;t think there&amp;#8217;s any reason to try to hide it.&lt;/p&gt;

&lt;p&gt;Once you&amp;#8217;ve downloaded the cookies plugin and included this code on your page, it&amp;#8217;s a good idea to load the page and inspect your cookies to make sure the cookie was actually set. If it was, move on to the next step.&lt;/p&gt;

&lt;h2&gt;jQuery for the Shopping Cart View Page&lt;/h2&gt;

&lt;p&gt;This is the code you&amp;#8217;ll need to put on the cart view page. The simplest way to implement this is to put it just before the closing &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; tag on the cart view page.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;script type="text/javascript"&amp;gt;
    var emptyCart = jQuery('.cart-empty').text();
    if ( (emptyCart == "") ) {
        jQuery.getScript('/path/to/js/jquery.cookies.2.2.0.min.js', function() {
            var couponCode = jQuery.cookies.get('nameOfCookie');
            if ( (/../i.test(couponCode)) &amp;amp;&amp;amp; (!!couponCode) ) {
                var postData = "remove=0&amp;amp;coupon_code=" + couponCode;
                jQuery.post("/checkout/cart/couponPost/", postData, function() {
                    jQuery.cookies.del('nameOfCookie');
                    document.location = '/checkout/cart/';
                });
            };
        });
    };
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If you cannot add code to this page, you can place it in a file and include it in the head of the page. If you do that though, you&amp;#8217;ll want to slightly modify it to execute after the page load is complete. You&amp;#8217;ll also want to make sure that it only executes on the &lt;code&gt;/checkout/cart/&lt;/code&gt; page. It would look something like this&amp;#8230;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;window.onload = function() {
    var emptyCart = jQuery('.cart-empty').text();
    if ( (/\/checkout\/cart\//i.test(page)) &amp;amp;&amp;amp; (emptyCart == "") ) {
        jQuery.getScript('/path/to/js/jquery.cookies.2.2.0.min.js', function() {
            var couponCode = jQuery.cookies.get('nameOfCookie');
            if ( (/../i.test(couponCode)) &amp;amp;&amp;amp; (!!couponCode) ) {
                var postData = "remove=0&amp;amp;coupon_code=" + couponCode;
                jQuery.post("/checkout/cart/couponPost/", postData, function() {
                    jQuery.cookies.del('nameOfCookie');
                    document.location = '/checkout/cart/';
                });
            };
        });
    };
};
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Here&amp;#8217;s a quick explanation of what the code does:&lt;/p&gt;

&lt;ol&gt;&lt;li&gt;If you remember, a coupon code cannot be applied to a cart if there aren&amp;#8217;t any items in the cart. The first part of the script grabs the text from the headline with a class equal to &lt;code&gt;.cart-empty&lt;/code&gt; and it&amp;#8217;s stored in the variable &lt;code&gt;emptyCart&lt;/code&gt;. &lt;/li&gt;
&lt;li&gt;The following &lt;code&gt;IF&lt;/code&gt; statement checks to make sure the &lt;code&gt;emptyCart&lt;/code&gt; variable is empty. If it isn&amp;#8217;t empty then that means the cart is empty and the rest of the code should not execute. If you used the second version, there&amp;#8217;s also a condition that makes sure that the current document is the cart view page.&lt;/li&gt;
&lt;li&gt;Next, we need to make sure that the jQuery cookies plugin has been loaded.&lt;/li&gt;
&lt;li&gt;After the cookie plugin has loaded, the variable of &lt;code&gt;couponCode&lt;/code&gt; gets set to the value of the coupon code from the landing page.&lt;/li&gt;
&lt;li&gt;Then there&amp;#8217;s another &lt;code&gt;IF&lt;/code&gt; statement that checks to see if a coupon code exists. &lt;/li&gt;
&lt;li&gt;If the coupon code does exist, then the next part makes a POST to &lt;code&gt;/checkout/cart/couponPost/&lt;/code&gt; with a query string equal to &lt;code&gt;remove=0&amp;amp;coupon_code=testcouponcode&lt;/code&gt; which will actually activate the discount for the customer&amp;#8217;s shopping cart. &lt;/li&gt;
&lt;li&gt;If that POST is successful, the cookie gets deleted and the page refreshes. The refresh is needed to let the customer see the discount in the sub-total. It&amp;#8217;s unfortunate that the page refresh is necessary. I just haven&amp;#8217;t been able to figure out a way to get around it. If you know how, please feel free to comment.&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;I hope this has been helpful. Now, go forth and rockout some new landing pages!&lt;/p&gt;</description><link>http://adambuchanan.me/post/31762315475</link><guid>http://adambuchanan.me/post/31762315475</guid><pubDate>Mon, 17 Sep 2012 20:09:52 -0400</pubDate><category>coupon code</category><category>jquery</category><category>magento</category><category>landing pages</category></item><item><title>Fixing Magento's Order Tracking in Customer Accounts</title><description>&lt;p&gt;Fixing may be too strong of phrasing. There&amp;#8217;s nothing &amp;#8220;broken&amp;#8221; about how order tracking works inside customer account in Magento. However, it also isn&amp;#8217;t very usable for several reasons. Let me explain.&lt;/p&gt;

&lt;h2&gt;The Issues&lt;/h2&gt;

&lt;p&gt;Here are the issues I have with how Magento presents tracking information in customer accounts.&lt;/p&gt;

&lt;ol&gt;&lt;li&gt;&lt;p&gt;The link for the tracking number isn&amp;#8217;t very well presented. If I placed an order and I&amp;#8217;m logging into my account to check on it, I&amp;#8217;m going to either be checking on the status or I&amp;#8217;m trying to track the package. Presenting those two pieces of data should be top priority.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When you find the link to get your tracking number and click on it, you&amp;#8217;ll see that another pop up window opens just to show you the shipping carrier, method and tracking number. It seems like a big waste of a click to me. Why not show that information on the order detail page? Or, if you want to have a link to it, why not link directly out to the shipper&amp;#8217;s tracking page?&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;I set out to fix these to issues on sites that I manage.&lt;/p&gt;

&lt;h2&gt;Uh Oh, I Don&amp;#8217;t Know PHP or Magento&lt;/h2&gt;

&lt;p&gt;It was very important to me to be able to fix this but I don&amp;#8217;t know a thing about PHP or the core functionality of Magento. I wasn&amp;#8217;t going to let that stop me though. I tried looking around in the interwebs to see if anyone else had already solved this problem and I didn&amp;#8217;t find much. I also looked for extensions on Magento Connect, but no luck there either.&lt;/p&gt;

&lt;p&gt;After reviewing the problem and thinking about the existing skills that I posses, I decided it didn&amp;#8217;t matter. All the data I need to solve those problems was available to me on the client side. And, since that&amp;#8217;s true, I knew I could get what I needed by using jQuery.&lt;/p&gt;

&lt;h2&gt;Using jQuery on Magento Sites&lt;/h2&gt;

&lt;p&gt;I&amp;#8217;ve been learning jQuery for about 6 months or so. I learn a little more each time I have a project or excuse to use it. Since I&amp;#8217;ve started to use Magento, there has been a few circumstances where I wanted to use jQuery. The problem is that by default jQuery conflicts with Prototype (another Javascript framework). It took me a while to figure out what was going on but in the end the solution was simple.&lt;/p&gt;

&lt;p&gt;If Prototype.js is in use on a site, write your jQuery selectors like this &lt;code&gt;jQuery('#someId').method()&lt;/code&gt; rather than &lt;code&gt;$('#someId').method()&lt;/code&gt;. The reason is that both Prototype and jQuery use that same &lt;code&gt;$&lt;/code&gt; as a variable.&lt;/p&gt;

&lt;h2&gt;orderTracking.js&lt;/h2&gt;

&lt;p&gt;First thing&amp;#8217;s first - here&amp;#8217;s the script:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;jQuery(document).ready(function () {
    if ( jQuery('h2.table-caption a').length &amp;gt; 0 ) {
        var onClickActionObject = jQuery('h2.table-caption a').attr("onclick") + "";
        var onClickAction = JSON.stringify(onClickActionObject);
        var URLstripped = onClickAction.replace(/\"function.*\/\/www/, "https://secure").replace(/','.*/, "");
        jQuery.ajax({
            url: URLstripped,
            type: 'GET',
            dataType: 'text',
            success: function ( code ) {
                var method = jQuery(code).find('.label').text();
                var trackingNumber = jQuery(code).find('.value').text();
                if (method.replace(/ .*/,"") == "USPS") {
                    var carrierURL = "https://tools.usps.com/go/TrackConfirmAction!execute.action?formattedLabel=";
                    var trackingURL = carrierURL + trackingNumber;
                };
                if (method.replace(/ .*/,"") == "UPS") {
                    var carrierURL = "http://wwwapps.ups.com/WebTracking/track?track=yes&amp;amp;trackNums=";
                    var trackingURL = carrierURL + trackingNumber;
                };
                jQuery('h2.table-caption span, h2.table-caption a').remove();
                jQuery('.col-2 .box .box-content:first').after('&amp;lt;a id="external-tracking-link" target="_blank" style="color:#973C3E; font-weight:bolder;" href="' + trackingURL + '"&amp;gt;Track Your Shipment&amp;lt;/a&amp;gt;');
            }
        });
    };
});
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Save the script and then include it in the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; of your Magento site.&lt;/p&gt;

&lt;p&gt;Now, let me explain what&amp;#8217;s going on step by step:&lt;/p&gt;

&lt;ol&gt;&lt;li&gt;&lt;p&gt;The first step is to make sure the script doesn&amp;#8217;t do anything until the page content has loaded. The first line in the script performs that task. Here&amp;#8217;s the snippet &lt;code&gt;jQuery(document).ready(function () { ... });&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Since this script won&amp;#8217;t serve any purpose except for when the customer is logged into their account, we don&amp;#8217;t want the full script to execute on every page. There are multiple ways of doing this. One way would be to only include the script on the account pages but since I don&amp;#8217;t know anything about the Magento&amp;#8217;s core files I couldn&amp;#8217;t go that route. I decided to include a simple &lt;code&gt;if&lt;/code&gt; statement that would check to make sure that it doesn&amp;#8217;t execute if not on the order details page. This is the test I used for my store &lt;code&gt;if ( jQuery('h2.table-caption a').length &amp;gt; 0 ) { ... };&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &amp;#8220;Track your order&amp;#8221; link in the customer&amp;#8217;s account has an &lt;code&gt;onclick&lt;/code&gt; attribute assigned to it. When the customer clicks it, the popup window presents the carrier, shipping method and tracking number. We&amp;#8217;ll need that information so we need to record that URL into a variable. In the script, that URL gets assigned to the &lt;code&gt;URLstripped&lt;/code&gt; variable.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Now, we need to get that tracking information by executing a AJAX request in the background and parse out the pieces we need. Lines 6-20 do the heavy lifting there. It&amp;#8217;s important to note that we ship using either USPS or UPS. Therefore this script will only work for those two shipping carriers. You should be able to add in others pretty easily.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The last step is to take the tracking number and rewrite the tracking link, style it and put it in a different spot on the page. That&amp;#8217;s handled by lines 21 and 22.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;Now, when the tracking link is clicked on the carrier&amp;#8217;s website will open in a new tab/window with the tracking information for the customer&amp;#8217;s package already displayed.&lt;/p&gt;

&lt;p&gt;Let me know if you have any questions or have ideas for accomplishing this task in a better way.&lt;/p&gt;

&lt;p&gt;Thanks for reading.&lt;/p&gt;</description><link>http://adambuchanan.me/post/29825529586</link><guid>http://adambuchanan.me/post/29825529586</guid><pubDate>Mon, 20 Aug 2012 08:57:11 -0400</pubDate><category>jquery</category><category>magento</category></item><item><title>Fixing Google Analytics Self Referrals in Magento Enterprise</title><description>&lt;p&gt;If you have a website and it runs well, your next priority should be to make sure your analytics setup is sound. This was a challenge I just had to conquer myself and sometimes it isn&amp;#8217;t as easy as it seems like it should be. Here&amp;#8217;s my tale&amp;#8230;&lt;/p&gt;

&lt;h2&gt;Setting up Google Analytics in Magento Enterprise&lt;/h2&gt;

&lt;p&gt;Configuring Google Analytics in Magento couldn&amp;#8217;t be easier. There&amp;#8217;s a config page, you put in your profile number, activate it and then you&amp;#8217;re done. Except in my case we weren&amp;#8217;t.&lt;/p&gt;

&lt;p&gt;We have a bit of a unique setup. We have a sub-domain that we need to track as well and for us, there&amp;#8217;s no need to have the data separated into a separate profile because the sub-domain is only used for the cart. We have the main &lt;code&gt;http://www.&lt;/code&gt; sub-domain as well as the &lt;code&gt;https://secure.&lt;/code&gt; sub-domain.&lt;/p&gt;

&lt;h2&gt;What is a self referral?&lt;/h2&gt;

&lt;p&gt;It&amp;#8217;s pretty simple. When your visitor traffic is properly tracking source by source but you also have traffic and conversions tracking under the same domain name as what you&amp;#8217;re trying to track. Because the only different sub-domain we have is in our cart, this meant that only our conversion data was wrong. Fortunately (or so I thought), Google provides special code to use when you need to track sub-domains.&lt;/p&gt;

&lt;h2&gt;Google&amp;#8217;s Recommended Code for Sub-Domains&lt;/h2&gt;

&lt;p&gt;This is the code that Google recommends you use when tracking a site that has one or more sub-domains.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;script type="text/javascript"&amp;gt;
    var _gaq = _gaq || [];
    _gaq.push(['_setAccount', 'UA-123456-7']);
    _gaq.push(['_setDomainName', 'domain.com']);
    _gaq.push(['_trackPageview']);

    (function() {
        var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
        ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
        var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
    })();
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This is the newest asynchronous Google Analytics tracking code. The only part that&amp;#8217;s different from the standard install is &lt;code&gt;_gaq.push(['_setDomainName', 'domain.com']);&lt;/code&gt;. However, in Magento, there&amp;#8217;s no way to define that variable without modifying core PHP files. But that&amp;#8217;s exactly what we had to do to get that setup properly.&lt;/p&gt;

&lt;p&gt;After this was installed and we verified that it was rendering properly on both sub-domains, we waited a day and reviewed our Google Analytics account. Still, this didn&amp;#8217;t fix our problem. We were still tracking self referrals and there was no change at all.&lt;/p&gt;

&lt;p&gt;At this point, I went back to the drawing board which pretty much means that I went back to Google searches.&lt;/p&gt;

&lt;p&gt;I found a few other forum threads talking about another snippet that could be added that will help fix these issues. That&amp;#8217;s when I decided that Google&amp;#8217;s recommendation for code was going out the window.&lt;/p&gt;

&lt;h2&gt;The Fix&lt;/h2&gt;

&lt;p&gt;This is what worked for me. If you&amp;#8217;re facing similar problems with self referrals, I hope it works for you too. However, the only real way to get things working is trial and error. Trial and error never fails forever.&lt;/p&gt;

&lt;p&gt;The code:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;script type="text/javascript"&amp;gt;
    var _gaq = _gaq || [];
    _gaq.push(['_setAccount', 'UA-123456-7']);
    _gaq.push(['_setDomainName', 'domain.com']);
    _gaq.push(['_trackPageview']);
    _gaq.push(['_addIgnoredRef', 'domain.com']);

    (function() {
        var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
        ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
        var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
    })();
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The addition is &lt;code&gt;_gaq.push(['_addIgnoredRef', 'domain.com']);&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Honestly, that&amp;#8217;s all it took. It all seems simple now but at the time it was incredibly frustrating. As soon as the code went live and I inspected the GET requests for &lt;code&gt;__utm.gif&lt;/code&gt; I knew that it was working properly and the problem had been solved.&lt;/p&gt;

&lt;h2&gt;Verifying Referral Data in Firebug/Chrome&lt;/h2&gt;

&lt;p&gt;In all reality, if you know what to look for you don&amp;#8217;t have to wait a day to check Google Analytics to see if your data was tracked properly. All you really have to do is inspect the GET requests that the Google Analytics Javascript makes at page load. Here&amp;#8217;s a screenshot of what you need to watch out for (screenshot of this blog)&amp;#8230;&lt;/p&gt;

&lt;p&gt;&lt;img src="http://dl.dropbox.com/u/56691816/automateeverything/img/ga-example.png" alt="Google Analytics Example"/&gt;&lt;/p&gt;

&lt;p&gt;Click on the GET request for &lt;code&gt;__utm.gif&lt;/code&gt; and then you&amp;#8217;ll get the detail for that request. Pay special attention to the section under &amp;#8220;Query String Parameters&amp;#8221;, specifically the parameter called &lt;code&gt;utmcc&lt;/code&gt;. That parameter is what recalls the tracking data stored in cookies. Here&amp;#8217;s the value from that parameter:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;_utma=189990958.53866207.1339112813.1345157959.1345242492.53;+__utmz=189990958.1345242492.53.25.utmcsr=assets.tumblr.com|utmccn=(referral)|utmcmd=referral|utmcct=/iframe.html;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Within that value, check out the parameter &lt;code&gt;utmcsr&lt;/code&gt;, which is equal to the referral source. When you&amp;#8217;re evaluating this on your own domain it&amp;#8217;s important to look at this on the first page you visit when transitioning from one sub-domain to the other. That&amp;#8217;s where the cookie information will get all screwed up. If the referrer is equal to your domain, then you still have a problem. If it is equal to the original referrer like direct, organic, etc, then you&amp;#8217;ve solved the problem.&lt;/p&gt;

&lt;p&gt;If I&amp;#8217;m being honest I still do not understand why the Google Analytics code that&amp;#8217;s recommended by Google in my account wasn&amp;#8217;t enough. It&amp;#8217;s really frustrating. If anyone has additional insight I&amp;#8217;d love to understand.&lt;/p&gt;

&lt;p&gt;I hope this has been informative. Please leave a comment if you have a question and I&amp;#8217;ll do my best to answer or provide ideas.&lt;/p&gt;</description><link>http://adambuchanan.me/post/29688061812</link><guid>http://adambuchanan.me/post/29688061812</guid><pubDate>Sat, 18 Aug 2012 09:16:04 -0400</pubDate><category>google analytics</category><category>magento</category><category>self referrals</category></item><item><title>NexTag.com Keyword Suggest Scraper</title><description>&lt;p&gt;In writing the short script for this post I learned that NexTag.com is the holy grail of keyword suggest results. Most sites limit there suggestions to about 10, where as NexTag.com returns 100! That&amp;#8217;s truly amazing and great for us.&lt;/p&gt;

&lt;p&gt;Let&amp;#8217;s cut to the chase. Here&amp;#8217;s the script:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;#/bin/bash

# This script will scrape NexTag.com's keyword suggestion tools.

query=$1
f_query=$(echo "$query" | sed 's/ /%20/g')
curl -s "http://www.nextag.com/buyer/opensearch.jsp?suggest=complete&amp;amp;perpage=100&amp;amp;search=$f_query" | sed 's/\[//g;s/\]//g;s/","/\n/g;s/"//g'
echo
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You run it like this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;bash nextag.com.sh "shoes"
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Then enjoy the results!&lt;/p&gt;

&lt;p&gt;If you have any questions or feedback please leave a comment. Otherwise, happy automation.&lt;/p&gt;</description><link>http://adambuchanan.me/post/29561599946</link><guid>http://adambuchanan.me/post/29561599946</guid><pubDate>Thu, 16 Aug 2012 13:59:52 -0400</pubDate><category>keyword research</category><category>nextag.com</category><category>shopping engines</category><category>scrape keyword suggest</category></item><item><title>Shopping.com Keyword Suggest Scraper</title><description>&lt;p&gt;This is a really quick post about another keyword tool. As the title suggests, this is a tool to scrape keyword suggest on Shopping.com. The script is very simple and is just an adaptation to the tools discussed &lt;a href="http://automateeverything.tumblr.com/post/22700447516/blekko-keyword-suggest-scraper" target="_blank"&gt;here&lt;/a&gt;, &lt;a href="http://automateeverything.tumblr.com/post/22777566761/scraping-keyword-suggest-on-thefind-com" target="_blank"&gt;here&lt;/a&gt;, &lt;a href="http://automateeverything.tumblr.com/post/21057959906/google-keyword-suggest-scraper-tool" target="_blank"&gt;here&lt;/a&gt; and &lt;a href="http://automateeverything.tumblr.com/post/19581427372/16-lines-of-bash-keyword-research-glory" target="_blank"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This is the full script&amp;#8230;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;#/bin/bash

# This script will scrape Shopping.com's keyword suggestion tools.

query=$1
f_query=$(echo "$query" | sed 's/ /%20/g')
curl -s "http://www.shopping.com/ajaxSearchAssistant?q=$f_query" | grep "&amp;lt;S&amp;gt;" | cut -d'&amp;gt;' -f2 | cut -d'&amp;lt;' -f1
echo
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Then you run it like this&amp;#8230;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;bash shopping.com.sh "keyword"
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Using niche sites like Shopping.com (or any other shopping engine) is a good idea when doing keyword research specifically for campaigns trying to sell very common consumer products. Shopping engines like Shopping.com can be a very helpful resource.&lt;/p&gt;

&lt;p&gt;Happy automation!&lt;/p&gt;</description><link>http://adambuchanan.me/post/29517326018</link><guid>http://adambuchanan.me/post/29517326018</guid><pubDate>Wed, 15 Aug 2012 20:45:00 -0400</pubDate><category>keyword research</category><category>shopping engines</category><category>scrape keyword suggest</category></item><item><title>Das Keyboard vs. Razer Blackwidow vs. IBM Model M</title><description>&lt;p&gt;A colleague introduced me to mechanical keyboards about three years ago. I&amp;#8217;m certainly not a grey beard and my first computer was a laptop with Windows XP installed. Needless to say, I didn&amp;#8217;t have any experience with mechanical keywords and didn&amp;#8217;t have any &amp;#8220;good ol days&amp;#8221; nostalgic feelings about how keyboards used to be made with quality materials for touch typists. So, when this guy started blabbing about how awesome mechanical keyboards are, it was easy for me to tune him out.&lt;/p&gt;

&lt;p&gt;I thought I loved the feeling of typing on a low-profile laptop style keyboard until, on one day, this guy brought in his humongous Silver Label IBM Model M keyboard and let me give it a try. Again, upon first glance I turned up my nose. However, after a couple hours of use I started to understand what all the hype was about. I really enjoyed typing on that keyboard. I felt like I could type faster and I felt my pinky finger reaching for the backspace key less often. It&amp;#8217;s worth mentioning that my office mates didn&amp;#8217;t appreciate the extra noise very much (I didn&amp;#8217;t really care).&lt;/p&gt;

&lt;p&gt;After that, it was my mission to get a mechanical keyboard for myself. I quickly discovered that old Model M keyboards aren&amp;#8217;t very easy to source on-demand. Long story short, I was able to source two Model M keyboards locally and restore them to perfect working condition. I&amp;#8217;ve been using one at work and one at home for the past couple years. They&amp;#8217;ve treated me well and have me hooked on mechanical key switches.&lt;/p&gt;

&lt;h2&gt;My Newest Mechanical Keyboards&lt;/h2&gt;

&lt;p&gt;The only thing pushing me to shelve my IBM keyboards is the fact that PS/2 connectors aren&amp;#8217;t installed on new machines anymore. You can buy a converter to go from PS/2 to USB. I&amp;#8217;ve been using one of those to connect to a laptop for the past year and a half, but sometimes it drops keystrokes which is not acceptable. For that reason, I decided to move on and purchase a couple of modern day mechanical keyboards.&lt;/p&gt;

&lt;h3&gt;Razer Blackwidow Ultimate&lt;/h3&gt;

&lt;p&gt;The first modern mechanical keyboard I purchased was the Razor Blackwidow Ultimate. I&amp;#8217;ve read good things about this keyboard and was attracted to the dedicated macro keys. But, to be honest, the final motivating factor for pulling the purchasing trigger was the fact that I had a Best Buy gift card and needed to spend it. I purchased it for home use with Ubuntu Linux.&lt;/p&gt;

&lt;p&gt;Quick note: The dedicated macro keys on that keyboard aren&amp;#8217;t recognized by the kernel by default. Some guy wrote a Python script that enables them. You can get the script &lt;a href="http://finch.am/projects/blackwidow/" target="_blank"&gt;here&lt;/a&gt;. After you download it, run it like this (root permissions are required):&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;sudo python blackwidow_enable.py
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You should now be able to program the macro keys.&lt;/p&gt;

&lt;p&gt;After overcoming that little hurdle, this keyboard has been great. It has a shorter keystroke than the old Model M but is has a very similar audible click and smooth tactile click feel. It&amp;#8217;s been great.&lt;/p&gt;

&lt;h3&gt;Das Keyboard Professional S&lt;/h3&gt;

&lt;p&gt;The second modern mechanical keyboard I purchased was the Das Keyboard. I purchased this one for use at work. This one is much less &amp;#8220;gamer&amp;#8221; than the Razer, but it truly delivers with performance. It doesn&amp;#8217;t have the dedicated macro keys that the Razer does but it has the Cherry MX Blue key switches and that&amp;#8217;s what really matters.&lt;/p&gt;

&lt;p&gt;The feel is amazing. And, let me say this - the Das has better feel than the Razer Blackwidow and the IBM Model M. I&amp;#8217;ve also used Filco keyboards in the past (never owned one) and I would argue that the Das even types nicer than the Filcos. The difference is very subtle and therefore I cannot even describe why it is. Typing on the Das keyboard is just a unique experience that you won&amp;#8217;t understand until you do it.&lt;/p&gt;

&lt;p&gt;So, do it. Get yourself a modern mechanical keyboard today. If you type all day long you won&amp;#8217;t regret investing in the perfect keyboard.&lt;/p&gt;</description><link>http://adambuchanan.me/post/29434010076</link><guid>http://adambuchanan.me/post/29434010076</guid><pubDate>Tue, 14 Aug 2012 18:00:52 -0400</pubDate><category>keyboards</category></item><item><title>Strange URLs Appearing in Google After Redesign</title><description>&lt;p&gt;A couple of weeks ago I wrote a post showing some efficient ways to verify your &lt;a href="http://automateeverything.tumblr.com/post/27940608573/verifying-301-redirects-during-site-redesign" target="_blank"&gt;server side redirects&lt;/a&gt;. In that post, I describe several ways to get lists of all the different canonical URLs that Google and other search engines may be aware of so that you can setup redirects ahead of the redesigned site launch.&lt;/p&gt;

&lt;p&gt;I wrote that post because I was managing two such relaunches at that time. Last week on Tuesday, those two sites went live. Since then I&amp;#8217;ve been monitoring Google Position reports daily to see how my positions and pages have been changing. Since we made the decision to change several of our URLs, it&amp;#8217;s also important to make sure the changes are being understood properly. Here are some of the thing&amp;#8217;s I&amp;#8217;ve been paying attention to:&lt;/p&gt;

&lt;ol&gt;&lt;li&gt;Individual keyword position swings, like changes of 3 spots or more. &lt;/li&gt;
&lt;li&gt;Have the 301 redirects from old URLs to the new URLs been understood? Meaning, when the old URL isn&amp;#8217;t positioning anymore, has the new equivalent replaced it?&lt;/li&gt;
&lt;li&gt;Are category pages still positioning for my head keywords? Are product pages or content pages still positioning for the longer-tail keywords?&lt;/li&gt;
&lt;li&gt;Are all the new URLs being indexed?&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;Everything is looking great. Our positions are holding through the transition and each day I see a few more positioning URLs transition over from the old URLs to the new ones.&lt;/p&gt;

&lt;h2&gt;Then, the Google Surprise&lt;/h2&gt;

&lt;p&gt;Then I started to see something strange. I started to see URLs appear to be positioning that I&amp;#8217;ve never seen before. The URL structure looked familiar enough, but the actual URL hasn&amp;#8217;t ever shown up in Google&amp;#8217;s index before (that I recorded). In each of these instances, the positioning URL was either the second URL for our domain positioning for that keyword.&lt;/p&gt;

&lt;p&gt;To recap, the URL that was positioning for the old site has now been replaced with an old canonical version that doesn&amp;#8217;t exist on the new site and never positioned when the old site was live. This is really strange behavior, but I&amp;#8217;m not really surprised because our old site had ridiculous amounts of canonical URLs. It would be sickening for any knowledgeable SEO.&lt;/p&gt;

&lt;p&gt;I found a handful of occurrences like this that fit the same pattern. To double check that I wasn&amp;#8217;t mistaken, I double checked the server responses for those pages and found that they are indeed returning 404 (as they should). Again, this doesn&amp;#8217;t really bother me because overall SEO performance has been great and each of these instances are just secondary positions. It&amp;#8217;s just strange and I haven&amp;#8217;t ever seen it before.&lt;/p&gt;

&lt;h2&gt;Next Steps&lt;/h2&gt;

&lt;p&gt;I honestly think that things will sort themselves out within a week or two. In the meantime, I&amp;#8217;m going to continue with my daily position reports. When I find strange situations like this where new URLs show up, I&amp;#8217;ll just create 301 redirects for them to the most relevant page on the new site. It shouldn&amp;#8217;t take too long for Google to figure it out.&lt;/p&gt;</description><link>http://adambuchanan.me/post/29158884928</link><guid>http://adambuchanan.me/post/29158884928</guid><pubDate>Fri, 10 Aug 2012 20:31:05 -0400</pubDate><category>google</category><category>seo</category><category>redesign</category><category>canonical url</category></item><item><title>Fun with TextBelt - Public SMS API</title><description>&lt;p&gt;The first time I heard about &lt;a href="http://textbelt.com/" target="_blank"&gt;TextBelt&lt;/a&gt; was months and months ago. I tried it out right away and it worked very well. At the time I didn&amp;#8217;t really have an excuse to build a tool around it. In fact, I still don&amp;#8217;t but I had a few ideas and just happen to think this kind of thing is fun.&lt;/p&gt;

&lt;p&gt;So, let&amp;#8217;s first take a look at how to use it from the Linux command line.&lt;/p&gt;

&lt;h2&gt;How to use TextBelt&lt;/h2&gt;

&lt;p&gt;The text belt has a really clear tutorial that should get you started. All you really need to know is the following command:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;curl "http://textbelt.com/text" -d number=1234567890 -d "message=this is where you put the body of the sms"
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;That&amp;#8217;s it. Once you successfully send your text message you should get back the following response in your terminal:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;{"success":true}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If you get that message back you should receive the sms very shortly. I did determine that sending these to Google Voice accounts does not work. I&amp;#8217;m not sure why and I didn&amp;#8217;t really look into it. My guess is that Google Voice silently drops the request because TextBelt reports that the message was successfully sent. You can also get error messages, though I&amp;#8217;ve only received one and it is as follows:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;{"success":false,"message":"Exceeded quota for this phone number."}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;TextBelt does have some pretty tight quotas. You cannot send more than three messages to any one phone number in a three minute period. You are also limited to 75 outgoing messages from any IP address per day. It does say on their site that you can request increased limits if you have a real need for them.&lt;/p&gt;

&lt;h2&gt;Interesting Ways to use TextBelt&lt;/h2&gt;

&lt;p&gt;Now that you know how to use TextBelt, lets look at some of the interesting ways to use it.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Watch a log file for changes, SMS yourself the updates&lt;/b&gt;&lt;/p&gt;

&lt;p&gt;You may or may not know about the tool called &lt;code&gt;tail&lt;/code&gt;. Basically &lt;code&gt;tail&lt;/code&gt; is a command line tool that writes the last set of lines to the command line. Here&amp;#8217;s the most basic way to use it:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;tail logfile.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;When you run &lt;code&gt;tail&lt;/code&gt; like this it will exit after printing the last ten lines. You can run it with the &lt;code&gt;-f&lt;/code&gt; flag or run the &lt;code&gt;tailf&lt;/code&gt; program and then it will stay open and print out each of lines added to the end of the log file as soon as they&amp;#8217;re added.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;tail -f logfile.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;So, let&amp;#8217;s combine our new-found skills. With some help of the program &lt;code&gt;xargs&lt;/code&gt; and this &lt;a href="http://www.commandlinefu.com/commands/view/7156/monitor-a-file-with-tail-with-timestamps-added" target="_blank"&gt;Commandlinefu&lt;/a&gt; page, I was able to come up with this little gem:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;tail -f file.txt |xargs -IX curl "http://textbelt.com/text" -d number=1234567890 -d "message=X"
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Instead of printing the newest items out to the command line, they&amp;#8217;ll get sent to your phone number. Pretty cool right? Depending on what type of a file you&amp;#8217;re monitoring, you may not want to get every one of the messages sent to your phone. It&amp;#8217;s easy enough to filter the messages from the log update by using &lt;code&gt;grep&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here&amp;#8217;s a real world example that would send you a SMS each time someone logs into a server using SSH:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;tail -f /var/log/auth.log | grep "sshd.*Accepted" |xargs -IX curl "http://textbelt.com/text" -d number=1234567890 -d "message=X"
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You could modify the &lt;code&gt;grep&lt;/code&gt; expression to send the failed log on attempts as well.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Send Facebook&amp;#8217;s falling stock price every 30 minutes&lt;/b&gt;&lt;/p&gt;

&lt;p&gt;Maybe you have a similar disdain for Facebook as me and it brings you a small amount of joy each time you see something bad happen to Facebook. It&amp;#8217;s easy enough to &lt;a href="http://www.commandlinefu.com/commands/view/2086/command-line-to-get-the-stock-quote-via-yahoo" target="_blank"&gt;scrape Yahoo Finance&lt;/a&gt; using &lt;code&gt;curl&lt;/code&gt; to send the current stock price for any stock through TextBelt. This is the quick example using &amp;#8216;FB&amp;#8217;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt; curl -s 'http://download.finance.yahoo.com/d/quotes.csv?s=fb&amp;amp;f=l1'|xargs -IX curl "http://textbelt.com/text" -d number=1234567890 -d "message=X"
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If you don&amp;#8217;t enjoy watching Facebook&amp;#8217;s stock fall then you can change it to what ever stock you want by replacing the &lt;code&gt;fb&lt;/code&gt; in &lt;code&gt;s=fb&lt;/code&gt; with the stock of your choosing.&lt;/p&gt;

&lt;p&gt;That&amp;#8217;ll just send it once and exit. If you want it to be sent every 30 minutes then you have two options. You can set up the command as a cron job or you can execute the command inside a &lt;code&gt;while true&lt;/code&gt; loop and ask it to &lt;code&gt;sleep&lt;/code&gt; for 1800 seconds before re-running the command. Here&amp;#8217;s an example of the while loop:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;while true; do curl -s 'http://download.finance.yahoo.com/d/quotes.csv?s=fb&amp;amp;f=l1'|xargs -IX curl "http://textbelt.com/text" -d number=1234567890 -d "message=X"; sleep 1800; done;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;That&amp;#8217;s all I have for now. I&amp;#8217;d love to see some additional uses in the comments. Happy automation!&lt;/p&gt;</description><link>http://adambuchanan.me/post/29018724579</link><guid>http://adambuchanan.me/post/29018724579</guid><pubDate>Wed, 08 Aug 2012 21:01:22 -0400</pubDate><category>curl</category><category>sms</category><category>api</category><category>textbelt</category></item><item><title>Facebook, the Coolest Click Fraudster</title><description>&lt;p&gt;Yup, but I&amp;#8217;m not the first or the only one to say it. It&amp;#8217;s very likely that Facebook is participating in planned mass click fraud. This topic is only top of mind because of the post I read a few minutes ago about how &lt;a href="https://www.facebook.com/limitedpressing/posts/209534972507958" target="_blank"&gt;80% of clicks on Facebook ads are bots&lt;/a&gt;. Since that was a Facebook post and the author claims to be deleting the page/account soon, I decided to add the text &lt;a href="http://pastebin.com/rReHTkLr" target="_blank"&gt;here&lt;/a&gt; as well for archiving purposes. This isn&amp;#8217;t something I&amp;#8217;ve worried about in years primarily because I swore off Facebook ads (except in very rare cases) back when Facebook ads were still in beta.&lt;/p&gt;

&lt;p&gt;I wrote about a very similar experience I had just a few months ago with &lt;a href="http://automateeverything.tumblr.com/post/21335938728/a-shopping-engine-click-fraud-story" target="_blank"&gt;click fraud at Shopzilla&lt;/a&gt;. I hate click fraud. If you read my post about Shopzilla, you&amp;#8217;ll also get some of my memories about dealing with Yahoo when they used to get a ton of invalid click activity (that&amp;#8217;s the PC way of referring to click fraud) from publishers on their network. The good thing about Yahoo was that they used to give refunds if you could prove that the activity was &amp;#8220;invalid&amp;#8221;, that is if you even payed enough attention to your analytics to notice. Yahoo wouldn&amp;#8217;t call it a refund though, that would be an admission of guilt. They&amp;#8217;d be very clear when referring to the monies paid as a &amp;#8220;courtesy credit&amp;#8221; even though, I&amp;#8217;ve actually gotten them to send a check as opposed to crediting back to the account balance.&lt;/p&gt;

&lt;p&gt;At least Yahoo had the courtesy to refund fraudulent click spend. Facebook &amp;amp; Shopzilla, well, it seems as though they&amp;#8217;re in denial mode.&lt;/p&gt;

&lt;h2&gt;A Different Perspective&amp;#8230;&lt;/h2&gt;

&lt;p&gt;I think that most of the people who write about this in the coming days will be bashing Facebook over this. They probably deserve it. Also, I bet there are a few who will take Facebook&amp;#8217;s side and defend them. However, there&amp;#8217;s a third perspective that I&amp;#8217;d like to share. How digital marketing agencies fit into the mix.&lt;/p&gt;

&lt;p&gt;Most large online, digital marketing agencies make their revenue based on a percent of the client&amp;#8217;s advertising spend. The more their client spends on advertising, the more the agency brings in for fees. This is logically defensible because if the agency wants to convince their client into spending more money on advertising then the agency must also have to return more sales for that client. You know, provide them a good return on investment.&lt;/p&gt;

&lt;p&gt;Here&amp;#8217;s the thing about ROI&amp;#8230; a good return on investment is different for every business. One of the accounts I manage every day would be doing poorly if the Google Paid Search account returned less than 1000% ROI averaged over a week. However, I&amp;#8217;ve managed plenty of accounts over the years where 200% ROI was a frigg&amp;#8217;n miracle. The agency trick is convincing the client that 500% ROI is some how better long term, even if 1000% is possible now. Who would deny that a return of $5 dollars for every $1 dollar spent on advertising isn&amp;#8217;t an amazing return? If I didn&amp;#8217;t know better, I wouldn&amp;#8217;t.&lt;/p&gt;

&lt;p&gt;If you&amp;#8217;re currently working with an agency to takes their fee based on percent of ad spend, keep your ears open for terms/phrases like this:&lt;/p&gt;

&lt;ul&gt;&lt;li&gt;&amp;#8220;Cast a wide net&amp;#8221;&lt;/li&gt;
&lt;li&gt;&amp;#8220;Branding&amp;#8221;&lt;/li&gt;
&lt;li&gt;&amp;#8220;Long conversion cycle&amp;#8221;&lt;/li&gt;
&lt;li&gt;&amp;#8220;Building awareness&amp;#8221; &lt;/li&gt;
&lt;li&gt;&amp;#8220;prospecting keywords&amp;#8221;, usually referring to broad match keywords in AdWords.&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;I&amp;#8217;m not saying all those are bullshit, I&amp;#8217;m just saying that you need to be able to definitively track and prove results. You owe it to yourself as the client to hold the agency to a high standard/burden of proof. If you don&amp;#8217;t care, they won&amp;#8217;t either. In my experience, more often than not, those strategies are birthed by pressure to grow client budgets.&lt;/p&gt;

&lt;h2&gt;Agencies Care More About Their Own ROI&lt;/h2&gt;

&lt;p&gt;&lt;b&gt;Advertising account managers at agencies are pressured by those above them to grow budgets to increase ROI for the agency&lt;/b&gt;. Imaging that. Yeah, I said it.&lt;/p&gt;

&lt;p&gt;As long as the client is happy, grow spend, work hard to justify it (consulting), twist the graphs and grow those budgets. What happens if client&amp;#8217;s become unhappy, convince them they&amp;#8217;re wrong. How do I know this? I&amp;#8217;ve been there. I was the guy who cared about my clients and took shit from my superiors for it. I don&amp;#8217;t believe that the agency I came from was unique. I took over many accounts from bigger, more well known agencies that were being blatantly negligent with account management. And why, because it benefits them. As a matter of fact, I was just talking with a friend who&amp;#8217;s also an ex-agency guy/girl about me writing this post and this is a quote (I cut out a couple of name&amp;#8230;):&lt;/p&gt;

&lt;p&gt;&lt;i&gt;&amp;#8220;I wasted thousands of dollars &amp;#8220;testing&amp;#8221; FB ads for clients. They don&amp;#8217;t work after three months? &amp;#8220;Keep testing and trying new ad copy.&amp;#8221; Roger that &lt;code&gt;&amp;lt;redacted&amp;gt;&lt;/code&gt;&amp;#8221;&lt;/i&gt;&lt;/p&gt;

&lt;p&gt;&lt;i&gt;&amp;#8220;FB ads for the most part were the biggest goddamn waste of time and money ever. &lt;code&gt;&amp;lt;redacted&amp;gt;&lt;/code&gt; seemed to like them. I think because they burned through available budget.&amp;#8221;&lt;/i&gt;&lt;/p&gt;

&lt;p&gt;I left the agency world (in part) for that reason. I went to work in-house for a company who needed a full time online marketing guy. It was the best move of my life. I&amp;#8217;m now truly incentivized to optimize advertising accounts for ROI. I&amp;#8217;m rewarded when I kick more ass. I like that arrangement.&lt;/p&gt;

&lt;h2&gt;Why Throw Agencies Under the Bus?&lt;/h2&gt;

&lt;p&gt;I think they deserve it because they&amp;#8217;re trusted advisers and they should know better. In many cases, it&amp;#8217;s like taking candy from a baby. Agencies are the ones who know about click fraud. They&amp;#8217;re the people who&amp;#8217;re the experts. They see the large data needed to pick out the oddities in analytics that just don&amp;#8217;t seem right. Agencies should be advocates against shit bombs like Facebook, Shopzilla and Yahoo (like I was and still am). But, more often, agencies are too busy writing blog posts and whitepapers about how social media is the next big way target prospective buyers. Agencies citing one another&amp;#8217;s published &amp;#8220;research&amp;#8221; about how social media &amp;#8220;advertising&amp;#8221; will &amp;#8220;grow your brand&amp;#8221;. These agencies have one interest in common - getting more of your ad budget.&lt;/p&gt;

&lt;p&gt;If you&amp;#8217;re a smart brand, small business or startup then you should tell agencies to &lt;a href="http://www.urbandictionary.com/define.php?term=diaf&amp;amp;defid=232060" target="_blank"&gt;DIAF&lt;/a&gt;. Hire a consultant who trains you or someone in your company who has a vested interest in your success. Hire someone who has real experience. Don&amp;#8217;t be pushed around by buzzwords and skewed market research.&lt;/p&gt;

&lt;p&gt;Take control of your advertising spend and make it return for you.&lt;/p&gt;</description><link>http://adambuchanan.me/post/28377063053</link><guid>http://adambuchanan.me/post/28377063053</guid><pubDate>Mon, 30 Jul 2012 21:23:02 -0400</pubDate><category>click fraud</category><category>facebook</category><category>social media</category></item><item><title>Verifying 301 Redirects During Site Redesign</title><description>&lt;p&gt;I&amp;#8217;m currently working on finishing up redesigns on two e-commerce sites at my day job. This is the culmination of many months of work and significant resources. We&amp;#8217;re within days of launch. I&amp;#8217;m pretty excited, but I decided that I needed to do another review of my 301 redirects to make sure I didn&amp;#8217;t miss anything.&lt;/p&gt;

&lt;p&gt;This analysis showed me that I had.&lt;/p&gt;

&lt;p&gt;When designing and architecting new sites, it&amp;#8217;s really easy to only think about how awesome things will be with the new platform/design/whatever. However, it&amp;#8217;s projects like organizing 301 redirects for old URLs to new URLs that require you not to forget how bad things once were. For me, this analysis forced me to recall how many problems these sites have had over the years with canonical URLs.&lt;/p&gt;

&lt;p&gt;We were able to overcome our internal canonicalization issues by using the &lt;code&gt;rel=canonical&lt;/code&gt; tag and fixing internal linking. However, there were years before where several different URLs were used for any one page. Some of those alternate URLs are still haunting us today.&lt;/p&gt;

&lt;p&gt;My challenge is to dig up all those old URL versions and make sure we have 301 redirects in place so that when we launch our new sites, visitors and search engines see our 404 page as little as possible. We just need to get a list of all those URLs to get started with.&lt;/p&gt;

&lt;h2&gt;Sources of URLs&lt;/h2&gt;

&lt;p&gt;Here&amp;#8217;s a list of several sources I used to get together a list of URLs to check. I wanted to make sure I used a variety of sources with the thinking that a wider net is better.&lt;/p&gt;

&lt;ol&gt;&lt;li&gt;HTML Sitemap&lt;/li&gt;
&lt;li&gt;XML Sitemap&lt;/li&gt;
&lt;li&gt;PPC Account Landing Pages&lt;/li&gt;
&lt;li&gt;Google Webmaster Tools&lt;/li&gt;
&lt;li&gt;Blekko In-Bound Link Report (see my&lt;a href="http://automateeverything.tumblr.com/post/23955909935/blekko-inbound-links-extractor-bookmarklet" target="_blank"&gt; bookmarklet&lt;/a&gt; for easy data extraction)&lt;/li&gt;
&lt;li&gt;Server Logs&lt;/li&gt;
&lt;li&gt;Google Analytics&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;It isn&amp;#8217;t important to me to keep URLs separated by source. Once I had extracted URLs from one source, I just dumped them into an Excel worksheet. Later on you can use Excel to remove duplicates.&lt;/p&gt;

&lt;h2&gt;Checking Server Responses on URLs&lt;/h2&gt;

&lt;p&gt;Once you have your list of pages you&amp;#8217;ll need an efficient (automated) way to check the server response for each. The server response code, i.e. 200, 301, 302, 404, 500, etc. is a part of the HTTP header that sent by the server to the web browser for each page request. I decided to write a short script to extract just the HTTP header.&lt;/p&gt;

&lt;p&gt;Here&amp;#8217;s the Bash one-liner:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;wget --spider -S "$url_to_check" 2&amp;gt;&amp;amp;1;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now just replace &lt;code&gt;$url_to_check&lt;/code&gt; with the actual URL, like &lt;code&gt;&lt;a href="http://www.google.com" target="_blank"&gt;http://www.google.com&lt;/a&gt;&lt;/code&gt;. Here&amp;#8217;s an example:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;wget --spider -S "http://www.google.com" 2&amp;gt;&amp;amp;1;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This is what you&amp;#8217;ll get back (Google sends a rather large header):&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Spider mode enabled. Check if remote file exists.
--2012-07-24 16:11:15--  &lt;a href="http://www.google.com/" target="_blank"&gt;http://www.google.com/&lt;/a&gt;
Resolving www.google.com (www.google.com)... 74.125.225.176, 74.125.225.180, 74.125.225.178, ...
Connecting to www.google.com (www.google.com)|74.125.225.176|:80... connected.
HTTP request sent, awaiting response... 
  HTTP/1.1 200 OK
  Set-Cookie: NID=62=GZrQmPOvK5AyPhgA1RYRKP3KCxVdFL_QZ_GptmYGQrOI2d9nUqQETovH7MhtWroeeFOL_xKGt1w-YffuGhmP5IjF38IcR6IbNlTVBLLU_t35rQwaVZFW7H7jKGVqRIr3; expires=Wed, 23-Jan-2013 20:12:05 GMT; path=/; domain=.google.com; HttpOnly
  Date: Tue, 24 Jul 2012 20:12:05 GMT
  Expires: -1
  Cache-Control: private, max-age=0
  Content-Type: text/html; charset=ISO-8859-1
  Set-Cookie: expires=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=www.google.com
  Set-Cookie: path=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=www.google.com
  Set-Cookie: domain=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=www.google.com
  Set-Cookie: expires=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=www.google.com
  Set-Cookie: path=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=www.google.com
  Set-Cookie: domain=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=www.google.com
  Set-Cookie: expires=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=.www.google.com
  Set-Cookie: path=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=.www.google.com
  Set-Cookie: domain=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=.www.google.com
  Set-Cookie: expires=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=.www.google.com
  Set-Cookie: path=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=.www.google.com
  Set-Cookie: domain=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=.www.google.com
  Set-Cookie: expires=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=google.com
  Set-Cookie: path=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=google.com
  Set-Cookie: domain=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=google.com
  Set-Cookie: expires=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=google.com
  Set-Cookie: path=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=google.com
  Set-Cookie: domain=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=google.com
  Set-Cookie: expires=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=.google.com
  Set-Cookie: path=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=.google.com
  Set-Cookie: domain=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=.google.com
  Set-Cookie: expires=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=.google.com
  Set-Cookie: path=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=.google.com
  Set-Cookie: domain=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=.google.com
  Set-Cookie: PREF=ID=d72f33e49b899251:FF=0:TM=1343160725:LM=1343160725:S=B8YfflyvIQvhgjE8; expires=Thu, 24-Jul-2014 20:12:05 GMT; path=/; domain=.google.com
  Set-Cookie: NID=62=d-OSrg2MYi6_7kbY5lHpW3qQ5ASiMMblUeUfBqppHahxDRXQL4qqPuI1nNxbgV3MoQnwxOuD6mPSTRrCK4xZk_ApFTi0KSsyDibrQ6-KHRXKZlPpECBr6AW39QNfhC9G; expires=Wed, 23-Jan-2013 20:12:05 GMT; path=/; domain=.google.com; HttpOnly
  P3P: CP="This is not a P3P policy! See &lt;a href="http://www.google.com/support/accounts/bin/answer.py?hl=en&amp;amp;answer=151657" target="_blank"&gt;http://www.google.com/support/accounts/bin/answer.py?hl=en&amp;amp;answer=151657&lt;/a&gt;&lt;a href="http://www.google.com" target="_blank"&gt;www.google.com&lt;/a&gt; (&lt;a href="http://www.google.com" target="_blank"&gt;www.google.com&lt;/a&gt;)... 74.125.225.176, 74.125.225.180, 74.125.225.178, ...
Connecting to &lt;a href="http://www.google.com" target="_blank"&gt;www.google.com&lt;/a&gt; (&lt;a href="http://www.google.com" target="_blank"&gt;www.google.com&lt;/a&gt;)|74.125.225.176|:80... connected.
HTTP request sent, awaiting response... 
  HTTP/1.1 200 OK
  Set-Cookie: NID=62=GZrQmPOvK5AyPhgA1RYRKP3KCxVdFL_QZ_GptmYGQrOI2d9nUqQETovH7MhtWroeeFOL_xKGt1w-YffuGhmP5IjF38IcR6IbNlTVBLLU_t35rQwaVZFW7H7jKGVqRIr3; expires=Wed, 23-Jan-2013 20:12:05 GMT; path=/; domain=.google.com; HttpOnly
  Date: Tue, 24 Jul 2012 20:12:05 GMT
  Expires: -1
  Cache-Control: private, max-age=0
  Content-Type: text/html; charset=ISO-8859-1
  Set-Cookie: expires=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=www.google.com
  Set-Cookie: path=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=www.google.com
  Set-Cookie: domain=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=www.google.com
  Set-Cookie: expires=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=www.google.com
  Set-Cookie: path=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=www.google.com
  Set-Cookie: domain=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=www.google.com
  Set-Cookie: expires=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=.www.google.com
  Set-Cookie: path=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=.www.google.com
  Set-Cookie: domain=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=.www.google.com
  Set-Cookie: expires=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=.www.google.com
  Set-Cookie: path=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=.www.google.com
  Set-Cookie: domain=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=.www.google.com
  Set-Cookie: expires=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=google.com
  Set-Cookie: path=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=google.com
  Set-Cookie: domain=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=google.com
  Set-Cookie: expires=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=google.com
  Set-Cookie: path=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=google.com
  Set-Cookie: domain=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=google.com
  Set-Cookie: expires=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=.google.com
  Set-Cookie: path=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=.google.com
  Set-Cookie: domain=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=.google.com
  Set-Cookie: expires=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=.google.com
  Set-Cookie: path=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=.google.com
  Set-Cookie: domain=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=.google.com
  Set-Cookie: PREF=ID=d72f33e49b899251:FF=0:TM=1343160725:LM=1343160725:S=B8YfflyvIQvhgjE8; expires=Thu, 24-Jul-2014 20:12:05 GMT; path=/; domain=.google.com
  Set-Cookie: NID=62=d-OSrg2MYi6_7kbY5lHpW3qQ5ASiMMblUeUfBqppHahxDRXQL4qqPuI1nNxbgV3MoQnwxOuD6mPSTRrCK4xZk_ApFTi0KSsyDibrQ6-KHRXKZlPpECBr6AW39QNfhC9G; expires=Wed, 23-Jan-2013 20:12:05 GMT; path=/; domain=.google.com; HttpOnly
  P3P: CP="This is not a P3P policy! See http://www.google.com/support/accounts/bin/answer.py?hl=en&amp;amp;answer=151657 for more info."
  Server: gws
  X-XSS-Protection: 1; mode=block
  X-Frame-Options: SAMEORIGIN
  Transfer-Encoding: chunked
Length: unspecified [text/html]
Remote file exists and could contain further links,
but recursion is disabled -- not retrieving.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;All you should really care about is the line that says &amp;#8220;&lt;code&gt;HTTP/1.1 200 OK&lt;/code&gt;&amp;#8221;. That is the server response that we want to verify. To extract just the server response from the output, let&amp;#8217;s pipe the HTTP header out to &lt;code&gt;grep&lt;/code&gt; to filter it.&lt;/p&gt;

&lt;p&gt;Run this&amp;#8230;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;wget --spider -S "http://www.google.com" 2&amp;gt;&amp;amp;1 | grep "HTTP/"
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&amp;#8230;and you&amp;#8217;ll get&amp;#8230;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;HTTP/1.1 200 OK
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Perfect, except that it would be very tedious to do this one URL at a time. Let&amp;#8217;s use a Bash &lt;code&gt;for&lt;/code&gt; loop to finish this up. Since you should be doing all of this PRIOR to launching your new site, you&amp;#8217;ll need to replace your &lt;code&gt;productiondomain&lt;/code&gt; in the URLs with the &lt;code&gt;stage.productiondomain&lt;/code&gt; for the development site. &lt;code&gt;sed&lt;/code&gt; is perfect for that. Also, update the &lt;code&gt;/path/to/url-file.txt&lt;/code&gt; to match the actual path to the file containing URLs. This assumes that there isn&amp;#8217;t any other data in the text file except for the URLs you want to check.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt; for URL in `cat "/path/to/url-file.txt" | sed 's/productiondomain.com/stage.productiondomain.com/g'`; do echo "$URL" - `wget --spider -S "$URL" 2&amp;gt;&amp;amp;1 | grep "HTTP/"` ; done
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;For this example, let&amp;#8217;s take the following URLs as the example:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;a href="http://www.slipxsolutions.com" target="_blank"&gt;http://www.slipxsolutions.com&lt;/a&gt;
&lt;a href="http://www.slipxsolutions.com/slip-X_NEXT.php" target="_blank"&gt;http://www.slipxsolutions.com/slip-X_NEXT.php&lt;/a&gt;
&lt;a href="http://www.slipxsolutions.com/product/41/Safety_TreadsTub_Appliqu_and_233s/75/Bath__and__Shower_Appliques/" target="_blank"&gt;http://www.slipxsolutions.com/product/41/Safety_TreadsTub_Appliqu_and_233s/75/Bath__and__Shower_Appliques/&lt;/a&gt;
&lt;a href="http://www.slipxsolutions.com/product/41/Safety_TreadsTub_Appliqu_and_233s/" target="_blank"&gt;http://www.slipxsolutions.com/product/41/Safety_TreadsTub_Appliqu_and_233s/&lt;/a&gt;
&lt;a href="http://www.slipxsolutions.com/product/41/Safety_TreadsTub_Appliqu_and_233s/134/75_Safety_Treads/" target="_blank"&gt;http://www.slipxsolutions.com/product/41/Safety_TreadsTub_Appliqu_and_233s/134/75_Safety_Treads/&lt;/a&gt;
&lt;a href="http://www.slipxsolutions.com/product/41/Safety_TreadsTub_Appliqu_and_233s/78/145_and_quot_Safety_Treads/" target="_blank"&gt;http://www.slipxsolutions.com/product/41/Safety_TreadsTub_Appliqu_and_233s/78/145_and_quot_Safety_Treads/&lt;/a&gt;
&lt;a href="http://www.slipxsolutions.com/product/23/Drain_PlugsDrain_Products/46/Snug_Plug_Drain_Stopper_/" target="_blank"&gt;http://www.slipxsolutions.com/product/23/Drain_PlugsDrain_Products/46/Snug_Plug_Drain_Stopper_/&lt;/a&gt;
&lt;a href="http://www.slipxsolutions.com/product/23/Drain_PlugsDrain_Products/48/StopAClog_Drain_Protector/" target="_blank"&gt;http://www.slipxsolutions.com/product/23/Drain_PlugsDrain_Products/48/StopAClog_Drain_Protector/&lt;/a&gt;
&lt;a href="http://www.slipxsolutions.com/product/23/Drain_PlugsDrain_Products/126/Bottomless_Bath/" target="_blank"&gt;http://www.slipxsolutions.com/product/23/Drain_PlugsDrain_Products/126/Bottomless_Bath/&lt;/a&gt;
&lt;a href="http://www.slipxsolutions.com/product/30/Bath__and__Home_Accessories/113/Shower_Splash_Guard/" target="_blank"&gt;http://www.slipxsolutions.com/product/30/Bath__and__Home_Accessories/113/Shower_Splash_Guard/&lt;/a&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Once I run the tool I get this as output:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;a href="http://www.slip-xsolutions.com" target="_blank"&gt;http://www.slip-xsolutions.com&lt;/a&gt; - HTTP/1.1 200 OK
&lt;a href="http://www.slip-xsolutions.com/slip-X_NEXT.php" target="_blank"&gt;http://www.slip-xsolutions.com/slip-X_NEXT.php&lt;/a&gt; - HTTP/1.1 301 Moved Permanently HTTP/1.1 200 OK
&lt;a href="http://www.slip-xsolutions.com/product/41/Safety_TreadsTub_Appliqu_and_233s/75/Bath__and__Shower_Appliques/" target="_blank"&gt;http://www.slip-xsolutions.com/product/41/Safety_TreadsTub_Appliqu_and_233s/75/Bath__and__Shower_Appliques/&lt;/a&gt; - HTTP/1.1 301 Moved Permanently HTTP/1.1 200 OK
&lt;a href="http://www.slip-xsolutions.com/product/41/Safety_TreadsTub_Appliqu_and_233s/" target="_blank"&gt;http://www.slip-xsolutions.com/product/41/Safety_TreadsTub_Appliqu_and_233s/&lt;/a&gt; - HTTP/1.1 301 Moved Permanently HTTP/1.1 200 OK
&lt;a href="http://www.slip-xsolutions.com/product/41/Safety_TreadsTub_Appliqu_and_233s/134/75_Safety_Treads/" target="_blank"&gt;http://www.slip-xsolutions.com/product/41/Safety_TreadsTub_Appliqu_and_233s/134/75_Safety_Treads/&lt;/a&gt; - HTTP/1.1 301 Moved Permanently HTTP/1.1 200 OK
&lt;a href="http://www.slip-xsolutions.com/product/41/Safety_TreadsTub_Appliqu_and_233s/78/145_and_quot_Safety_Treads/" target="_blank"&gt;http://www.slip-xsolutions.com/product/41/Safety_TreadsTub_Appliqu_and_233s/78/145_and_quot_Safety_Treads/&lt;/a&gt; - HTTP/1.1 301 Moved Permanently HTTP/1.1 200 OK
&lt;a href="http://www.slip-xsolutions.com/product/23/Drain_PlugsDrain_Products/46/Snug_Plug_Drain_Stopper_/" target="_blank"&gt;http://www.slip-xsolutions.com/product/23/Drain_PlugsDrain_Products/46/Snug_Plug_Drain_Stopper_/&lt;/a&gt; - HTTP/1.1 301 Moved Permanently HTTP/1.1 200 OK
&lt;a href="http://www.slip-xsolutions.com/product/23/Drain_PlugsDrain_Products/48/StopAClog_Drain_Protector/" target="_blank"&gt;http://www.slip-xsolutions.com/product/23/Drain_PlugsDrain_Products/48/StopAClog_Drain_Protector/&lt;/a&gt; - HTTP/1.1 301 Moved Permanently HTTP/1.1 200 OK
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now all you have to do is watch and look for server response codes that don&amp;#8217;t match your expectations. Notice that &lt;code&gt;curl&lt;/code&gt; when using the &lt;code&gt;--spider&lt;/code&gt; operator will check each page along the redirect path. If there are 6 redirects, it should follow every one and output the server responses.&lt;/p&gt;

&lt;p&gt;Let me know if you have any questions. Happy redirecting.&lt;/p&gt;</description><link>http://adambuchanan.me/post/27940608573</link><guid>http://adambuchanan.me/post/27940608573</guid><pubDate>Tue, 24 Jul 2012 19:25:00 -0400</pubDate><category>redesign</category><category>seo</category><category>redirects</category><category>server response codes</category></item><item><title>WTF Is Security?</title><description>&lt;p&gt;I&amp;#8217;m not really sure how much this is related to automation, but I just have to get this out.&lt;/p&gt;

&lt;p&gt;I feel kind of insulted right now. If the speed and force of my typing is any indication of my mood, then I&amp;#8217;m sure the whole office knows I&amp;#8217;m pissed (I&amp;#8217;m typing this on a IBM Model M keyboard). Why am I pissed? Well, at my day job, I&amp;#8217;m working with a new vender to help do some e-commerce integration with our accounting package and a set of new websites we&amp;#8217;re developing. Again, the experience I&amp;#8217;m about to share is pretty close to my first impression of them.&lt;/p&gt;

&lt;h2&gt;If I Give You Remote Access, Respect It!&lt;/h2&gt;

&lt;p&gt;This company was trying to troubleshoot a bug and couldn&amp;#8217;t figure it out without getting access to our main server in the office. So, with them being hundreds of miles away, naturally they ask for remote access. I agree.&lt;/p&gt;

&lt;p&gt;They then send me an email with instructions for allowing the remote connection. I&amp;#8217;ll just paste what they sent me, minus the important bits, and let&amp;#8217;s see if you can spot what&amp;#8217;s so fucked up about what they&amp;#8217;re doing&amp;#8230;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Hi Adam,

One of our developers requires a remote connection to your machine to investigate the on-premise component of &amp;lt;redacted&amp;gt;.

Please find below instructions for making your computer available for remote connection via GoToMyPC.com:

eMail:              &amp;lt;redacted&amp;gt;
Password:        &amp;lt;redacted&amp;gt;
NickName:        **Company Name**
AccessCode:    &amp;lt;redacted&amp;gt;

If GoToMyPC is already installed on your computer, there should be a little green and white MYPC logo by the clock in the bottom right hand corner.  If it is there, please right click on the icon and choose ‘Register’.  You will be prompted for the information above.

If GoToMyPC is not already installed on your computer, please go to &lt;a href="http://www.gotomypc.com" target="_blank"&gt;www.gotomypc.com&lt;/a&gt; and log in using the eMail address and password provided above.  When you log in, you will see a list of computer with a ‘Add Computer’ button at the bottom.  Please click on the button and allow the GoToMyPC software to install (should only take a minute)

[PLEASE NOTE: When installing GoToMyPC and registering the computer, this must be done on the machine you are physically in front of (not over a network).  This is a built in security feature.]

Once the software is installed, you will be asked to re-start.  This is not necessary for the product to function.

You will then be prompted for the information provided above.

Please let us know when you are available for us to connect.

Thank you and Kind Regards,

&amp;lt;redacted&amp;gt;

&amp;lt;redacted&amp;gt; Help Desk Administrator
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Let me just say that I don&amp;#8217;t have any real experience with GoToMyPc.com, fortunately for me, I didn&amp;#8217;t really need it to be able to tell how ridiculous this is.&lt;/p&gt;

&lt;p&gt;This company is providing the login credentials for their main account and asking me to log into it and add my server. &lt;b&gt;Please remember that I&amp;#8217;m their client. They don&amp;#8217;t know me. I don&amp;#8217;t work for them. And, they emailed these credentials.&lt;/b&gt;&lt;/p&gt;

&lt;p&gt;I logged in. Guess what I see? I see a list of all their clients&amp;#8217; servers who&amp;#8217;ve also followed these instructions. Next to each computer on the list is a button that would allow me to connect to them! &lt;b&gt;WTF!&lt;/b&gt;. They essentially gave me access to every one of their clients&amp;#8217; servers where their backend accounting software is installed. What the fucking fuck? Are they brain dead? This is where all clients&amp;#8217; customers&amp;#8217; financial and PII data is stored.&lt;/p&gt;

&lt;p&gt;I&amp;#8217;m not an idiot, I didn&amp;#8217;t connect to any of them.&lt;/p&gt;

&lt;h2&gt;Quit Emailing Credentials&lt;/h2&gt;

&lt;p&gt;I&amp;#8217;m a good guy. I&amp;#8217;m not going to do anything with this information besides complain. Yes, I&amp;#8217;ll complain to them too. But there are plenty of people in the world who don&amp;#8217;t have such good intentions. Seriously, we read about big companies getting breached every week.&lt;/p&gt;

&lt;p&gt;Quit emailing passwords and other sensitive information. When you send an email it naturally gets copied several times over. And, more people than your intended recipient have access to the computers where that email is replicated. That&amp;#8217;s the reason why secure protocols exist.&lt;/p&gt;

&lt;h2&gt;How Long Has This Been Going On?&lt;/h2&gt;

&lt;p&gt;There&amp;#8217;s no way to know the answer to that question for sure, but I can make a few guesses based on how people choose passwords. I didn&amp;#8217;t include the password above, but let&amp;#8217;s just say that it ended in &lt;code&gt;2011&lt;/code&gt;, like the year. It&amp;#8217;s currently July, 2012. So my guess is that they&amp;#8217;ve been using GoToMyPc.com like this for a year or more and they haven&amp;#8217;t changed the password.&lt;/p&gt;

&lt;h2&gt;It&amp;#8217;s GoToMyPc, Not LetMeGoToYourPc&lt;/h2&gt;

&lt;p&gt;I&amp;#8217;m fairly certain that the GoToMyPc.com service wasn&amp;#8217;t designed to be used this way. There are client/support style remote access services out there that allow tech support personal to access client computers in the right way (certainly a more secure way).&lt;/p&gt;

&lt;h2&gt;Other Interesting Bits&amp;#8230;&lt;/h2&gt;

&lt;p&gt;When I logged into their account (per their instructions) there were roughly 20 client computers available for connection, including the Help Desk Administrator that sent me the email! Apparently you can access their clients&amp;#8217; computers as well as several computers within their company network! This isn&amp;#8217;t a small company either, which makes this even more scary.&lt;/p&gt;

&lt;p&gt;It may be in their best interest to give a shit after all if people can get into their network (probably not though)!&lt;/p&gt;

&lt;p&gt;Anyway, I&amp;#8217;m done ranting. Even though it probably won&amp;#8217;t change their behavior, I&amp;#8217;ll let them know that I don&amp;#8217;t think this is a good idea. That&amp;#8217;s the least I can do.&lt;/p&gt;</description><link>http://adambuchanan.me/post/27426433541</link><guid>http://adambuchanan.me/post/27426433541</guid><pubDate>Tue, 17 Jul 2012 15:51:00 -0400</pubDate><category>security</category><category>remote access</category><category>wtf</category></item><item><title>Updating Google Analytics Code on Many Static Pages</title><description>&lt;p&gt;I&amp;#8217;m a relative newb. I have been fortunate enough that when I&amp;#8217;ve had to work on sites there&amp;#8217;s almost always been some sort of templating system in place. If I needed to make a site-wide code update, I could just make the change in the some templated code block and that would push the update throughout the site. It makes sense, and thank bejesus it&amp;#8217;s now the norm.&lt;/p&gt;

&lt;p&gt;What about when the whole site is static HTML files? If the site is tens of files, no problem. But what do you do when the site is several hundred? Before today, I had a few theories but had never had to test them out. Here&amp;#8217;s my story.&lt;/p&gt;

&lt;h3&gt;Please Read, Very Important&lt;/h3&gt;

&lt;p&gt;These instructions will not work in every case, they aren&amp;#8217;t designed to. This is a hack I put together that was designed to work with my very specific use case. I&amp;#8217;m sharing it because I think you could update my code based on your needs. Please be careful and always make back ups. Enjoy!&lt;/p&gt;

&lt;h2&gt;The Goal&lt;/h2&gt;

&lt;p&gt;I was asked by a couple of friends to review their site and try to help them figure out why Google Analytics was tracking things strangely. Here were a few symptoms:&lt;/p&gt;

&lt;ol&gt;&lt;li&gt;Event tracking was registering in the GA account even though everything looked correct in the GET request for &lt;code&gt;utm.gif&lt;/code&gt;. &lt;/li&gt;
&lt;li&gt;Keywords and query data wasn&amp;#8217;t coming in for any search visits. No for Paid Search and not for Organic Search.&lt;/li&gt;
&lt;li&gt;No social data.&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;I checked on a few things and saw that they had an old version of GA installed. This is the code.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;script type="text/javascript"&amp;gt; 
var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
&amp;lt;/script&amp;gt;
&amp;lt;script type="text/javascript"&amp;gt; 
var pageTracker = _gat._getTracker("UA-123456-12");
pageTracker._trackPageview();
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I didn&amp;#8217;t really see anything wrong with it, but I decided to just try the new code on one page and see if it fixed the problem. I got the following code from their GA profile:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;script type="text/javascript"&amp;gt;

  var _gaq = _gaq || [];
  _gaq.push(['_setAccount', 'UA-1235526-26']);
  _gaq.push(['_setDomainName', 'rfsystemlab.us']);
  _gaq.push(['_trackPageview']);

  (function() {
    var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
    ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
    var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
  })();

&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I uploaded the updated page to their server and opened it in a web browser. By looking at the source, I could tell&amp;#8230;&lt;/p&gt;

&lt;ol&gt;&lt;li&gt;I was getting the new page with the updated tracking code.&lt;/li&gt;
&lt;li&gt;The new code was working to track pageviews because I could see the GET requests.&lt;/li&gt;
&lt;li&gt;After triggering a few events, I could see that the GET requests for those were sent correctly.&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;I waited a few minutes and checked their GA account again to see that now the Events were correctly showing up in their account! Awesome&amp;#8230;&lt;/p&gt;

&lt;p&gt;Except not.&lt;/p&gt;

&lt;h2&gt;The New Goal&lt;/h2&gt;

&lt;p&gt;Now that I think I know how to fix their tracking problems, I also have to figure out how to&amp;#8230;&lt;/p&gt;

&lt;ol&gt;&lt;li&gt;Remove all the old GA tracking code.&lt;/li&gt;
&lt;li&gt;Add in the new code.&lt;/li&gt;
&lt;li&gt;Avoid doing it one page at a time.&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;I could have just stopped at this point and said, &amp;#8220;You do it&amp;#8221;, but I wouldn&amp;#8217;t do that. Besides, this is fun anyway.&lt;/p&gt;

&lt;h3&gt;My Tools&lt;/h3&gt;

&lt;p&gt;It wasn&amp;#8217;t exactly easy to do this. Through a bunch of Googling, experimentation and a bit of experience with the command line in Linux, I was able to updates several hundred pages fairly easily.&lt;/p&gt;

&lt;p&gt;I used the following standard command line Linux tools to complete this task:&lt;/p&gt;

&lt;ul&gt;&lt;li&gt;sed&lt;/li&gt;
&lt;li&gt;cut&lt;/li&gt;
&lt;li&gt;for loops&lt;/li&gt;
&lt;li&gt;echo&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;That&amp;#8217;s honestly it&amp;#8230; However, I did use many resources to help. Here&amp;#8217;s my list:&lt;/p&gt;

&lt;ol&gt;&lt;li&gt;Stackoverflow - &lt;a href="http://stackoverflow.com/questions/5508245/how-can-i-do-a-multi-line-find-replace-from-nix-command-line" target="_blank"&gt;Here&lt;/a&gt;, &lt;a href="http://stackoverflow.com/questions/2112469/delete-specific-line-numbers-from-a-text-file-using-sed" target="_blank"&gt;here&lt;/a&gt; and &lt;a href="http://stackoverflow.com/questions/1987926/how-do-i-grep-recursively" target="_blank"&gt;here&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://dbaspot.com/shell/270141-replace-line-x-string-y-using-sed-awk.html" target="_blank"&gt;DBASpot.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.linuxquestions.org/questions/programming-9/sed-not-working-if-value-is-passed-thru-a-variable-containg-value-248678/" target="_blank"&gt;LinuxQuestions.org&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;I found each of these by asking Google.&lt;/p&gt;

&lt;h2&gt;Step 1 - Find all the files containing the old Google Analytics code&lt;/h2&gt;

&lt;p&gt;First, before you do anything, make a backup and then a backup of that backup. Seriously.&lt;/p&gt;

&lt;p&gt;I then opened a command line and moved into the root directory for the local copy of the site on my hard drive. Then I ran the following command:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;grep -n -r "var gaJsHost" .
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This command will search recursively for the string &lt;code&gt;"var gaJsHost"&lt;/code&gt; and then output the file name that was matched along with the full text from the line where the match was found. The &lt;code&gt;-n&lt;/code&gt; option tells grep to also print out the line number where the match was found. And, the string we&amp;#8217;re searching for is one of the main variable names in the old GA code that we need to remove. (Don&amp;#8217;t forget that dot at the end of the command. It&amp;#8217;s very important!)&lt;/p&gt;

&lt;p&gt;From there, we have our list of files we can operate on. By putting the previous command in a Bash &lt;code&gt;for&lt;/code&gt; loop, we can manipulate the results and do perform additional operations one row at a time. Here&amp;#8217;s the basic loop:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;for match in `grep -n -r "var gaJsHost" .`; do

    # do some stuff here...

done
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In a nutshell we&amp;#8217;ll need to know the following pieces of information for each match:&lt;/p&gt;

&lt;ul&gt;&lt;li&gt;file name.&lt;/li&gt;
&lt;li&gt;row number where the match is found.&lt;/li&gt;
&lt;li&gt;row number where the old GA code starts.&lt;/li&gt;
&lt;li&gt;row number where the old GA code ends.&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;With that data, we should be able to delete the matching rows as an operation in this loop, for each file. This the next set of code in this process, combining what we&amp;#8217;ve done above with the loop.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;for match in `grep -n -r "var gaJsHost" .`; do
    file=`echo $match | cut -d":" -f1`
    line=`echo $match | cut -d":" -f2`
    start=$(($line-1))
    end=$(($start+7))
done
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Each piece of information we need is stored in a separate variable. The &lt;code&gt;file&lt;/code&gt; variable is cut from the original &lt;code&gt;grep&lt;/code&gt; command output, which is equal to the file name where the match was found. The &lt;code&gt;line&lt;/code&gt; variable was also from the &lt;code&gt;grep&lt;/code&gt; command and is the line number in the &lt;code&gt;file&lt;/code&gt; where the match was made.&lt;/p&gt;

&lt;p&gt;We have two remaining variables that are getting values assigned, &lt;code&gt;start&lt;/code&gt; and &lt;code&gt;end&lt;/code&gt;, which represent the first line in the old GA code and the last line. I know that the string &lt;code&gt;"var gaJsHost"&lt;/code&gt; is found in the second line of the old GA code and that the entire code block is seven lines long. So, the variable &lt;code&gt;start&lt;/code&gt; can be calculated by subtracting one from the &lt;code&gt;line&lt;/code&gt; where the match was found and the &lt;code&gt;end&lt;/code&gt; variable can be calculated by adding seven to &lt;code&gt;start&lt;/code&gt;!&lt;/p&gt;

&lt;p&gt;Now, we just have to use this information to delete those lines. Here&amp;#8217;s the command you&amp;#8217;d add as the next command within the loop:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;sed -i -e "`echo $start`,`echo $end`d" $file
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Then your code would look like this&amp;#8230;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;for match in `grep -n -r "var gaJsHost" .`; do
    file=`echo $match | cut -d":" -f1`
    line=`echo $match | cut -d":" -f2`
    start=$(($line-1))
    end=$(($start+7))
    sed -i -e "`echo $start`,`echo $end`d" $file
done
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;After putting this into a Bash file and running it, I had successfully removed all the old Google Analytics tracking code from all the static HTML files on my local copy of my buddies&amp;#8217; site. We aren&amp;#8217;t done. We still need to add all the new code to the pages.&lt;/p&gt;

&lt;h2&gt;Step 2 - Adding new code to the head tag&lt;/h2&gt;

&lt;p&gt;Honestly I&amp;#8217;m a bit tired of writing the post at this point. Besides, most of the rest of what needs to happen is done by simple modification of the concepts above. So, here&amp;#8217;s the rest of my code:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;# set placeholder text before the &amp;lt;/head&amp;gt; tag for the code to be replaced with later.

for match in `grep -n -r "&amp;lt;/head&amp;gt;" .`; do
    file=`echo $match | cut -d":" -f1`
    line=`echo $match | cut -d":" -f2`
    sed -i "`echo $line`s/.*/NEW_GOOGLE_ANALYTICS_CODE_HERE\n&amp;lt;\/head&amp;gt;/" "$file"
done

# addin the new code..

$GACODE='&amp;lt;\!-- Start Google Analytics Installation --&amp;gt;&amp;lt;script type="text\/javascript"&amp;gt;var _gaq=_gaq\|\|\[\]\;_gaq.push\(\["_setAccount"\,"UA-123456-12"\]\)\;_gaq.push\(\["_setDomainName"\,"example.com"\]\)\;_gaq.push\(\["_trackPageview"\]\)\;\(function\(\)\{var a=document.createElement\("script"\)\;a.type="text\/javascript"\;a.async=true\;a.src=\("https\:"==document.location.protocol\?"https\:\/\/ssl"\:"http:\/\/www"\)\+".google-analytics.com\/ga.js"\;var b=document.getElementsByTagName\("script"\)\[0\]\;b.parentNode.insertBefore\(a\,b\)\}\)\(\)&amp;lt;\/script&amp;gt;&amp;lt;\!-- End Google Analytics Installation --&amp;gt;
'
for match in `grep -n -r "NEW_GOOGLE_ANALYTICS_CODE_HERE" .`; do
    file=`echo $match | cut -d":" -f1`
    sed -i 's/NEW_GOOGLE_ANALYTICS_CODE_HERE/'"$GACODE"'/' "$file"
done
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I hope this is helpful and not too confusing. I would love to answer any questions you may have. Leave a comment, contact me on Google+ or Linkedin.&lt;/p&gt;

&lt;p&gt;Happy automation!&lt;/p&gt;</description><link>http://adambuchanan.me/post/26345221717</link><guid>http://adambuchanan.me/post/26345221717</guid><pubDate>Mon, 02 Jul 2012 09:57:16 -0400</pubDate><category>google analytics</category><category>sed</category><category>bash</category></item><item><title>Dynamic DNS with Bash &amp; Afraid.org</title><description>&lt;p&gt;This has nothing to do with search engine marketing, but everything to do with automation. If you&amp;#8217;ve ever wanted to host a server at a location that doesn&amp;#8217;t have a static IP address then you know just how much of a pain it can be.&lt;/p&gt;

&lt;p&gt;Hopefully by the time you read this you&amp;#8217;re familiar with what &lt;a href="http://en.wikipedia.org/wiki/Domain_Name_System" target="_blank"&gt;DNS&lt;/a&gt; is and what &lt;a href="http://en.wikipedia.org/wiki/Dynamic_DNS" target="_blank"&gt;dynamic DNS&lt;/a&gt; is. If not, those links should catch you up to speed quick enough. I&amp;#8217;ve tried a few different dynamic DNS providers but for the past few years I&amp;#8217;ve been using freedns.afraid.org. I don&amp;#8217;t host this site (of course), but I do host FTP and a few other services that allow me to get to my machines at home where ever I am.&lt;/p&gt;

&lt;p&gt;I&amp;#8217;m writing this post today because I just happened to have to update my scripts that keep my DNS up to date when my IP changes today.&lt;/p&gt;

&lt;h2&gt;The Dynamic DNS Bash Script&lt;/h2&gt;

&lt;p&gt;This is the basic script I use to keep DNS updated for my domain.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;#!/bin/bash

# paste in the info url from your account
get_info_url='http://freedns.afraid.org/api/?action=getdyndns&amp;amp;sha=&amp;lt;paste_your_password_hash_here&amp;gt;'

# get the current ip...
# method 1 - they aren't providing it for scripts anymore...
#ip=`curl -s &lt;a href="http://whatismyip.org" target="_blank"&gt;http://whatismyip.org&lt;/a&gt;`

# method 2 - google provides this as an error message for bots...I'll take it.
ip=$(curl -s "https://www.google.com/search?q=what+is+my+ip+address" | grep "Client IP address:" | sed 's/.*Client IP address: //g;s/).*//g')
# get the current dns settings...
for each in `curl -s "$get_info_url"`
do
  domain=`echo "$each" | cut -d"|" -f1`
  dns_ip=`echo "$each" | cut -d"|" -f2`
  update_url=`echo "$each" | cut -d"|" -f3`

  if [ "$ip" != "$dns_ip" ]
  then
          echo -e "$domain - "\\c &amp;gt;&amp;gt; log
          curl "$update_url" &amp;gt;&amp;gt; log
  fi
done
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Script Explanation&lt;/h2&gt;

&lt;p&gt;Really the script is simple but the first thing you have to do is sign up for an account at &lt;a href="http://freedns.afraid.org/" target="_blank"&gt;freedns.afraid.org&lt;/a&gt; and choose your domain name. Once you&amp;#8217;ve done that, you&amp;#8217;re ready to get yourself setup with this script.&lt;/p&gt;

&lt;ol&gt;&lt;li&gt;Save the script above as &lt;code&gt;dns.sh&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Make sure that the script is set as executable. You can run &lt;code&gt;sudo chmod +x dns.sh&lt;/code&gt; to make sure it is.&lt;/li&gt;
&lt;li&gt;You now need to get your API key. To do that, go to &lt;a href="http://freedns.afraid.org/api/" target="_blank"&gt;http://freedns.afraid.org/api/&lt;/a&gt; and it will explain what you need to do. Basically, you need to hash your username and password, but you can find it on that page if you don&amp;#8217;t want to hash it yourself. &lt;/li&gt;
&lt;li&gt;In the script, replace the string &lt;code&gt;&amp;lt;paste_your_password_hash_here&amp;gt;&lt;/code&gt; with the actual hash (don&amp;#8217;t forget to save&amp;#8230;).&lt;/li&gt;
&lt;li&gt;Now you can run the script when ever your IP address updates. Alternatively (my way), you can setup the script as a cron job and have the script check at regular intervals to make sure everything is up to date.&lt;/li&gt;
&lt;/ol&gt;&lt;h2&gt;Setting up the Script as a Cron Job&lt;/h2&gt;

&lt;p&gt;Open a terminal and run &lt;code&gt;crontab -e&lt;/code&gt;. If you&amp;#8217;ve never done this before, it will probably prompt you to choose an editor. If you don&amp;#8217;t know how to use any of the command line editors then you&amp;#8217;ll need to do some additional Googleing, I&amp;#8217;m not going to explain it. Although, I prefer &lt;code&gt;nano&lt;/code&gt; because I don&amp;#8217;t understand &lt;code&gt;vi&lt;/code&gt; or &lt;code&gt;vim&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Once you have your crontab open, paste the following line somewhere to run the script every 5 minutes.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;*/5 * * * * /path/to/script/dns.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;From now on your DNS information should be up to date. You may have noticed that I&amp;#8217;m scraping Google to get your actual current IP address. That was the change I made to the script today. I used to use another site (commented out) but apparently the focus of the site has changed and they don&amp;#8217;t really like scripts using it to get the current IP address. What a shame.&lt;/p&gt;

&lt;p&gt;Anyway, I hope people find this handy.&lt;/p&gt;

&lt;p&gt;Happy automation!&lt;/p&gt;</description><link>http://adambuchanan.me/post/25473551700</link><guid>http://adambuchanan.me/post/25473551700</guid><pubDate>Tue, 19 Jun 2012 21:22:22 -0400</pubDate><category>dynamic dns</category><category>bash</category></item><item><title>Blekko Inbound Links Extractor Bookmarklet</title><description>&lt;p&gt;Let&amp;#8217;s get right to it. Start using the bookmarklet by dragging the following bookmarklet link to your bookmark toolbar.&lt;/p&gt;

&lt;p&gt;&lt;a style="background-color:white; padding:5px; border:solid 1px grey;" href="javascript:(function()%7Bif(window.myBookmarklet!==undefined)%7BmyBookmarklet();%7Delse%7Bdocument.body.appendChild(document.createElement('script')).src='http://dl.dropbox.com/u/56691816/automateeverything/bookmarklets/blekko-inbound-links.js?';%7D%7D)();" target="_blank"&gt;Blekko - Inbound Links&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;&lt;li&gt;Go to a Blekko Inbound Links SEO report like: &lt;a href="http://blekko.com/ws/ubuntuforums.com%2F+/domainlinks" target="_blank"&gt;ubuntuforums.com/ /domainlinks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Click on the Blekko - Inbound Links bookmarklet from your toolbar.&lt;/li&gt;
&lt;li&gt;Watch for a new &lt;code&gt;&amp;lt;textarea&amp;gt;&amp;lt;/textarea&amp;gt;&lt;/code&gt; to be added to the top of the page containing all the link data from the report.&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;If you&amp;#8217;re pulling data for a popular site like Twitter or Google, be prepared to be patient because there&amp;#8217;s a shit ton of scraping going on. Average and/or small sites typically take a second or two. The example provided above takes only 3 or 4 seconds.&lt;/p&gt;

&lt;p&gt;On the topic of links&amp;#8230;link to me or share it if you like it!&lt;/p&gt;

&lt;h2&gt;The How and Why&lt;/h2&gt;

&lt;p&gt;I&amp;#8217;m still working to improve my jQuery skills. Recently I needed to automate some tasks relating to scraping data from a site. That particular site was built relying heavily on Javascript and Ajax requests, which meant that I couldn&amp;#8217;t use my favorite combination of cURL, grep, cut and Bash to do the scraping. I needed something that would run client-side and execute Javascript.&lt;/p&gt;

&lt;p&gt;After thinking about it for a while, I decided I should learn about whether or not I&amp;#8217;d be able to create a bookmarklet to do the job. I&amp;#8217;ve been learning jQuery over the past few months, so I also wanted to make sure I could pair my developing jQuery skills with this bookmarklet project. I found this great post that provided the &lt;a href="http://coding.smashingmagazine.com/2010/05/23/make-your-own-bookmarklets-with-jquery/" target="_blank"&gt;exact starting point&lt;/a&gt; I was looking for.&lt;/p&gt;

&lt;p&gt;After using the framework provided in that post, I was able to complete that project. In fact, the project ended up working so well that I started to wonder about other ways I could use this technique to automate daily tasks.&lt;/p&gt;

&lt;h2&gt;Blekko&amp;#8217;s SEO Inbound Links Report&lt;/h2&gt;

&lt;p&gt;Very frequently, I use Blekko&amp;#8217;s SEO tools to research sites. I&amp;#8217;m a big fan of all the free information Blekko provides webmasters and search engine marketers. In particular, I use the Blekko Inbound Links report often. This report shows all the incoming links for any site that Blekko has in its index. It&amp;#8217;s really easy to use and navigate. To see this report for yourself, you&amp;#8217;ll need to create an account with Blekko (FREE).&lt;/p&gt;

&lt;p&gt;After your account is created, you can enter &lt;code&gt;automateeverything.tumblr.com/ /domainlinks&lt;/code&gt; to see all the incoming links for this site. My blog is still very young (at the time of writing), so my link profile isn&amp;#8217;t very impressive (you can help change that by linking to me!!). Here&amp;#8217;s a screenshot of what you get by getting the same report for &lt;code&gt;plus.google.com/ /domainlinks&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src="http://dl.dropbox.com/u/56691816/automateeverything/img/Screenshot%20from%202012-05-28%2017%3A31%3A50.png"/&gt;&lt;/p&gt;

&lt;p&gt;So, here&amp;#8217;s the rundown of what you&amp;#8217;re seeing:&lt;/p&gt;

&lt;ul&gt;&lt;li&gt;An overall summary of how many incoming links by how many unique domains.&lt;/li&gt;
&lt;li&gt;A list of domains, sorted by most authority to least (as judged by Blekko&amp;#8217;s &amp;#8220;Host Rank&amp;#8221;).&lt;/li&gt;
&lt;li&gt;The count of how many links there are from each host/domain.&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;While this is good summary information, I also want to know which pages on the domain the links are coming from, what the anchor text is, what page is being linked to and whether it&amp;#8217;s a nofollow link or not. To get that extra detail, you have to click on each of the link counts for each linking domain. That will load a detailed list of links and will show all the information I mentioned above.&lt;/p&gt;

&lt;p&gt;Let me say again, I think it&amp;#8217;s great that Blekko is providing all this information for free. But&amp;#8230;I want more. I want it all in one place. I want it in Excel. I don&amp;#8217;t want to try to combine browser addons and/or macros to get all this data. I don&amp;#8217;t want to go to each individual from domain links page to get all the to, from and anchor link details. This would take a ton of time to do manually. This time would be far better spent actually analyzing the link data.&lt;/p&gt;

&lt;p&gt;At the time of writing, Blekko does not provide an export option. That&amp;#8217;s why I&amp;#8217;m going to do it for everyone!&lt;/p&gt;

&lt;h2&gt;The Bookmarklet&lt;/h2&gt;

&lt;p&gt;This bookmarklet will automate the entire process. In just a second or so, it will scrape all of the information on the page, request each &amp;#8220;links&amp;#8221; page, scrape all that and then finally output a nicely formatted report in a text box at the top of the page. From there you&amp;#8217;ll be able to just copy and paste into Excel to be used in a pivot table, graph or whatever.&lt;/p&gt;

&lt;p&gt;I love &lt;a href="http://ubuntu.com" target="_blank"&gt;Ubuntu&lt;/a&gt;, so I thought I&amp;#8217;d do a demo on UbuntuForums.com to show the power of this bookmarklet.&lt;/p&gt;

&lt;iframe src="http://player.vimeo.com/video/42995839" width="500" height="250" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen&gt;&lt;/iframe&gt;

&lt;p&gt;Like I said, easy and fast! Here&amp;#8217;s a few sample lines from the output of the UbuntuForums.com report:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;From_Host   Host_Rank   Link_From_URL   Link_From_Anchor    nofollow    Link_To_URL
www.43things.com    468.7   &lt;a href="http://www.43things.com/things/view/2140/use-linux" target="_blank"&gt;http://www.43things.com/things/view/2140/use-linux&lt;/a&gt;      nf  &lt;a href="http://ubuntuforums.com/" target="_blank"&gt;http://ubuntuforums.com/&lt;/a&gt;
www.linuxjournal.com    1343.3  &lt;a href="http://www.linuxjournal.com/content/post-penguicon-unity-unification-story" target="_blank"&gt;http://www.linuxjournal.com/content/post-penguicon-unity-unification-story&lt;/a&gt;          &lt;a href="http://ubuntuforums.com/" target="_blank"&gt;http://ubuntuforums.com/&lt;/a&gt;
ubuntuforums.org    1167.3  &lt;a href="http://ubuntuforums.org/showthread.php?t=520091&amp;amp;page=2351" target="_blank"&gt;http://ubuntuforums.org/showthread.php?t=520091&amp;amp;page=2351&lt;/a&gt;           &lt;a href="http://ubuntuforums.com/bump.php?" target="_blank"&gt;http://ubuntuforums.com/bump.php?&lt;/a&gt;
ubuntuforums.org    1167.3  &lt;a href="http://ubuntuforums.org/showthread.php?t=635117" target="_blank"&gt;http://ubuntuforums.org/showthread.php?t=635117&lt;/a&gt; www.ubuntuforums.com        &lt;a href="http://www.ubuntuforums.com/" target="_blank"&gt;http://www.ubuntuforums.com/&lt;/a&gt;
bugs.launchpad.net  653.8   &lt;a href="https://bugs.launchpad.net/ubuntu/+source/evolution/+bug/349312" target="_blank"&gt;https://bugs.launchpad.net/ubuntu/+source/evolution/+bug/349312&lt;/a&gt;     nf  &lt;a href="http://ubuntuforums.com/" target="_blank"&gt;http://ubuntuforums.com/&lt;/a&gt;
bugs.launchpad.net  653.8   &lt;a href="https://bugs.launchpad.net/ubuntu/+source/fglrx-installer/+bug/545257" target="_blank"&gt;https://bugs.launchpad.net/ubuntu/+source/fglrx-installer/+bug/545257&lt;/a&gt;       nf  &lt;a href="http://ubuntuforums.com/" target="_blank"&gt;http://ubuntuforums.com/&lt;/a&gt;
bugs.launchpad.net  653.8   &lt;a href="https://bugs.launchpad.net/ubuntu/+source/fglrx-installer/+bug/545257/comments/6" target="_blank"&gt;https://bugs.launchpad.net/ubuntu/+source/fglrx-installer/+bug/545257/comments/6&lt;/a&gt;        nf  &lt;a href="http://ubuntuforums.com/" target="_blank"&gt;http://ubuntuforums.com/&lt;/a&gt;
www.geek.com    927.7   &lt;a href="http://www.geek.com/articles/geek-pick/microsoft-expertzone-training-teaches-best-buy-employees-about-linux-inferiority-2009097/" target="_blank"&gt;http://www.geek.com/articles/geek-pick/microsoft-expertzone-training-teaches-best-buy-employees-about-linux-inferiority-2009097/&lt;/a&gt;        nf  &lt;a href="http://www.ubuntuforums.com/" target="_blank"&gt;http://www.ubuntuforums.com/&lt;/a&gt;
www.ubuntugeek.com  257.5   &lt;a href="http://www.ubuntugeek.com/avast-antivirus-for-ubuntu-desktop.html" target="_blank"&gt;http://www.ubuntugeek.com/avast-antivirus-for-ubuntu-desktop.html&lt;/a&gt;       nf  &lt;a href="http://www.ubuntuforums.com/" target="_blank"&gt;http://www.ubuntuforums.com/&lt;/a&gt;
www.ubuntugeek.com  257.5   &lt;a href="http://www.ubuntugeek.com/avast-antivirus-for-ubuntu-desktop.html/comment-page-2" target="_blank"&gt;http://www.ubuntugeek.com/avast-antivirus-for-ubuntu-desktop.html/comment-page-2&lt;/a&gt;        nf  &lt;a href="http://www.ubuntuforums.com/" target="_blank"&gt;http://www.ubuntuforums.com/&lt;/a&gt;
www.piratbyran.org  100.6   &lt;a href="http://www.piratbyran.org/index.php?view=forum&amp;amp;a=thread&amp;amp;id=37918&amp;amp;fview=34" target="_blank"&gt;http://www.piratbyran.org/index.php?view=forum&amp;amp;a=thread&amp;amp;id=37918&amp;amp;fview=34&lt;/a&gt;           &lt;a href="http://www.ubuntuforums.com/" target="_blank"&gt;http://www.ubuntuforums.com/&lt;/a&gt;
www.forevergeek.com 122.7   &lt;a href="http://www.forevergeek.com/2005/04/ubuntu_504_hoary_hedgehog_review/" target="_blank"&gt;http://www.forevergeek.com/2005/04/ubuntu_504_hoary_hedgehog_review/&lt;/a&gt;    Official Ubuntu forums/suppport site        &lt;a href="http://ubuntuforums.com/" target="_blank"&gt;http://ubuntuforums.com/&lt;/a&gt;
www.forevergeek.com 122.7   &lt;a href="http://www.forevergeek.com/2005/04/ubuntu_504_hoary_hedgehog_review/?replytocom=21953" target="_blank"&gt;http://www.forevergeek.com/2005/04/ubuntu_504_hoary_hedgehog_review/?replytocom=21953&lt;/a&gt;   Official Ubuntu forums/suppport site    nf  &lt;a href="http://ubuntuforums.com/" target="_blank"&gt;http://ubuntuforums.com/&lt;/a&gt;
www.forevergeek.com 122.7   &lt;a href="http://www.forevergeek.com/2005/04/ubuntu_504_hoary_hedgehog_review/?replytocom=21959" target="_blank"&gt;http://www.forevergeek.com/2005/04/ubuntu_504_hoary_hedgehog_review/?replytocom=21959&lt;/a&gt;   Official Ubuntu forums/suppport site    nf  &lt;a href="http://ubuntuforums.com/" target="_blank"&gt;http://ubuntuforums.com/&lt;/a&gt;
www.forevergeek.com 122.7   &lt;a href="http://www.forevergeek.com/2005/04/ubuntu_504_hoary_hedgehog_review/?replytocom=21969" target="_blank"&gt;http://www.forevergeek.com/2005/04/ubuntu_504_hoary_hedgehog_review/?replytocom=21969&lt;/a&gt;   Official Ubuntu forums/suppport site    nf  &lt;a href="http://ubuntuforums.com/" target="_blank"&gt;http://ubuntuforums.com/&lt;/a&gt;
www.forevergeek.com 122.7   &lt;a href="http://www.forevergeek.com/2005/04/ubuntu_504_hoary_hedgehog_review/?replytocom=21974" target="_blank"&gt;http://www.forevergeek.com/2005/04/ubuntu_504_hoary_hedgehog_review/?replytocom=21974&lt;/a&gt;   Official Ubuntu forums/suppport site    nf  &lt;a href="http://ubuntuforums.com/" target="_blank"&gt;http://ubuntuforums.com/&lt;/a&gt;
www.forevergeek.com 122.7   &lt;a href="http://www.forevergeek.com/2005/04/ubuntu_504_hoary_hedgehog_review/?replytocom=21985" target="_blank"&gt;http://www.forevergeek.com/2005/04/ubuntu_504_hoary_hedgehog_review/?replytocom=21985&lt;/a&gt;   Official Ubuntu forums/suppport site    nf  &lt;a href="http://ubuntuforums.com/" target="_blank"&gt;http://ubuntuforums.com/&lt;/a&gt;&lt;a href="http://www.43things.com" target="_blank"&gt;www.43things.com&lt;/a&gt;    468.7   http://www.43things.com/things/view/2140/use-linux      nf  http://ubuntuforums.com/
&lt;a href="http://www.linuxjournal.com" target="_blank"&gt;www.linuxjournal.com&lt;/a&gt;    1343.3  http://www.linuxjournal.com/content/post-penguicon-unity-unification-story          http://ubuntuforums.com/
ubuntuforums.org    1167.3  http://ubuntuforums.org/showthread.php?t=520091&amp;amp;page=2351           http://ubuntuforums.com/bump.php?
ubuntuforums.org    1167.3  http://ubuntuforums.org/showthread.php?t=635117 &lt;a href="http://www.ubuntuforums.com" target="_blank"&gt;www.ubuntuforums.com&lt;/a&gt;        http://www.ubuntuforums.com/
bugs.launchpad.net  653.8   https://bugs.launchpad.net/ubuntu/+source/evolution/+bug/349312     nf  http://ubuntuforums.com/
bugs.launchpad.net  653.8   https://bugs.launchpad.net/ubuntu/+source/fglrx-installer/+bug/545257       nf  http://ubuntuforums.com/
bugs.launchpad.net  653.8   https://bugs.launchpad.net/ubuntu/+source/fglrx-installer/+bug/545257/comments/6        nf  http://ubuntuforums.com/
&lt;a href="http://www.geek.com" target="_blank"&gt;www.geek.com&lt;/a&gt;    927.7   http://www.geek.com/articles/geek-pick/microsoft-expertzone-training-teaches-best-buy-employees-about-linux-inferiority-2009097/        nf  http://www.ubuntuforums.com/
&lt;a href="http://www.ubuntugeek.com" target="_blank"&gt;www.ubuntugeek.com&lt;/a&gt;  257.5   http://www.ubuntugeek.com/avast-antivirus-for-ubuntu-desktop.html       nf  http://www.ubuntuforums.com/
&lt;a href="http://www.ubuntugeek.com" target="_blank"&gt;www.ubuntugeek.com&lt;/a&gt;  257.5   http://www.ubuntugeek.com/avast-antivirus-for-ubuntu-desktop.html/comment-page-2        nf  http://www.ubuntuforums.com/
&lt;a href="http://www.piratbyran.org" target="_blank"&gt;www.piratbyran.org&lt;/a&gt;  100.6   http://www.piratbyran.org/index.php?view=forum&amp;amp;a=thread&amp;amp;id=37918&amp;amp;fview=34           http://www.ubuntuforums.com/
&lt;a href="http://www.forevergeek.com" target="_blank"&gt;www.forevergeek.com&lt;/a&gt; 122.7   http://www.forevergeek.com/2005/04/ubuntu_504_hoary_hedgehog_review/    Official Ubuntu forums/suppport site        http://ubuntuforums.com/
&lt;a href="http://www.forevergeek.com" target="_blank"&gt;www.forevergeek.com&lt;/a&gt; 122.7   http://www.forevergeek.com/2005/04/ubuntu_504_hoary_hedgehog_review/?replytocom=21953   Official Ubuntu forums/suppport site    nf  http://ubuntuforums.com/
&lt;a href="http://www.forevergeek.com" target="_blank"&gt;www.forevergeek.com&lt;/a&gt; 122.7   http://www.forevergeek.com/2005/04/ubuntu_504_hoary_hedgehog_review/?replytocom=21959   Official Ubuntu forums/suppport site    nf  http://ubuntuforums.com/
&lt;a href="http://www.forevergeek.com" target="_blank"&gt;www.forevergeek.com&lt;/a&gt; 122.7   http://www.forevergeek.com/2005/04/ubuntu_504_hoary_hedgehog_review/?replytocom=21969   Official Ubuntu forums/suppport site    nf  http://ubuntuforums.com/
&lt;a href="http://www.forevergeek.com" target="_blank"&gt;www.forevergeek.com&lt;/a&gt; 122.7   http://www.forevergeek.com/2005/04/ubuntu_504_hoary_hedgehog_review/?replytocom=21974   Official Ubuntu forums/suppport site    nf  http://ubuntuforums.com/
&lt;a href="http://www.forevergeek.com" target="_blank"&gt;www.forevergeek.com&lt;/a&gt; 122.7   http://www.forevergeek.com/2005/04/ubuntu_504_hoary_hedgehog_review/?replytocom=21985   Official Ubuntu forums/suppport site    nf  http://ubuntuforums.com/
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Notes on Usage&lt;/h2&gt;

&lt;ul&gt;&lt;li&gt;The bookmarklet is cross-browser and cross-platform. If you run into any compatibility issues at all, please drop me a comment with the details of your platform and the query in Blekko.&lt;/li&gt;
&lt;li&gt;This bookmarklet works by calling the actual Javascript file from my public Dropbox folder. You can see the full code here: &lt;a href="http://dl.dropbox.com/u/56691816/automateeverything/bookmarklets/blekko-inbound-links.js" target="_blank"&gt;http://dl.dropbox.com/u/56691816/automateeverything/bookmarklets/blekko-inbound-links.js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Since this bookmarklet executes code that&amp;#8217;s pulled from an external source, it&amp;#8217;s functionality and feature set is subject to change. Not to freak you out but if I decided to do nasty things with the Javascript at some point in the future, the only way you&amp;#8217;d know is if you inspected the file each time before running it. But, isn&amp;#8217;t that the case with any software that updates its-self?&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;I hope you enjoy it. Please feel free to let me know if you have feature requests or bugs.&lt;/p&gt;

&lt;p&gt;Enjoy and happy automation!&lt;/p&gt;</description><link>http://adambuchanan.me/post/23955909935</link><guid>http://adambuchanan.me/post/23955909935</guid><pubDate>Mon, 28 May 2012 18:17:00 -0400</pubDate><category>seo</category><category>link analysis</category><category>blekko</category><category>bookmarklet</category><category>jquery</category></item><item><title>Google Analytics PDF Tracking Made Easy</title><description>&lt;p&gt;Tracking the quantity of PDF downloads is a very common wish for business to business and/or lead generation campaigns. It makes sense right? You understand that if a visitor is requesting a PDF then that visitor wants to learn more and is interested in what they&amp;#8217;ve learned from your site so far.&lt;/p&gt;

&lt;p&gt;The problem with trying to track PDF downloads as a conversion point or a goal is that a PDF document is different than a normal page. You cannot insert Google Analytics code in that PDF to have it track like you would another page. That means you won&amp;#8217;t be able to set it up as a goal in your Analytics profile.&lt;/p&gt;

&lt;h2&gt;The Good News&lt;/h2&gt;

&lt;p&gt;The good news is that it&amp;#8217;s simple enough to setup Google Analytics Event Tracking. In a nutshell, what this will do it record an &amp;#8220;event&amp;#8221; each time the PDF link is clicked by a user. Here&amp;#8217;s Google&amp;#8217;s &lt;a href="http://support.google.com/googleanalytics/bin/answer.py?hl=en&amp;amp;answer=55529" target="_blank"&gt;overview&lt;/a&gt; of how to set it up.&lt;/p&gt;

&lt;p&gt;If you&amp;#8217;re too lazy to read through all the documentation provided by Google, here&amp;#8217;s the &lt;b&gt;tl;dr&lt;/b&gt;:&lt;/p&gt;

&lt;p&gt;Before adding the onClick, the link would look like this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;a href="/some/pdf/file.pdf"&amp;gt;Download this PDF&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;After adding the onClick, it would look something like this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;a onclick="_gaq.push(['_trackEvent', 'PDF', 'Download', '/some/pdf/file.pdf']);"" href="/some/pdf/file.pdf"&amp;gt;Download this PDF&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Then, once the link is clicked, you will record an &amp;#8220;Event&amp;#8221; in Google Analytics with the category equal to &lt;code&gt;PDF&lt;/code&gt;, the action equal to &lt;code&gt;Download&lt;/code&gt; and the label equal to &lt;code&gt;/some/pdf/file.pdf&lt;/code&gt;. Pretty simple right?&lt;/p&gt;

&lt;h2&gt;The Bad News&lt;/h2&gt;

&lt;p&gt;Now it&amp;#8217;s time for the bad news. I&amp;#8217;m typically in the consultant position. I&amp;#8217;m trying to convince a site owner, marketing manager or webmaster why they should go through the trouble to tag each and every &amp;#8220;a href&amp;#8221; PDF link with this onClick attribute, each of which needs to be customized so we can measure appropriately. This is a lot of work. Typically, the project doesn&amp;#8217;t even start. Other times when it tagging these links becomes a project, it rarely is done perfectly and almost never is kept up with. This leads to inconsistent reporting.&lt;/p&gt;

&lt;p&gt;The only thing worse than no tracking is inaccurate tracking. There&amp;#8217;s nothing worse to a marketing professional (who&amp;#8217;s trying to justify one&amp;#8217;s own existence) than having to explain that the reporting is invalid because there were errors made in the setup of the analytics. However, IT HAPPENS ALL THE TIME. It&amp;#8217;s insanely frustrating.&lt;/p&gt;

&lt;p&gt;This blog, after all, is titled &amp;#8220;Automate Everything&amp;#8221;. Hopefully my solution to this problem won&amp;#8217;t disappoint. I&amp;#8217;ve been learning Javascript, more specifically JQuery, over the past several months. I was thinking about how to solve this problem. How can I use my new skills to make it insanely easy and efficient for people to add event tracking for Google Analytics to all the PDF links on their site? I&amp;#8217;m happy to say, I&amp;#8217;ve figured it out.&lt;/p&gt;

&lt;h2&gt;PDF Event Tracking Solution&lt;/h2&gt;

&lt;p&gt;My solution will allow you to track every PDF link on your site by just pasting a few lines of JQuery in the footer of your site. Here&amp;#8217;s my solution:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script&amp;gt;
    $("a[href$='pdf']").each(function(index) {
      pdfLabel = $(this).attr('href');
      pdfOnClick = "_gaq.push(['_trackEvent', 'PDF', 'Download', '" + pdfLabel + "']);";
      $(this).attr("onClick", pdfOnClick);
    });
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You should be able to place this code in the footer of almost any site and when the page loads, it will go through all the &lt;code&gt;&amp;lt;a href=""&lt;/code&gt; tags and look for links with the href ending in &lt;code&gt;.pdf&lt;/code&gt;. When it finds one, it simply adds the &lt;code&gt;onClick&lt;/code&gt; attribute to perform the event tracking in Google Analytics.&lt;/p&gt;

&lt;p&gt;If your site already includes the JQuery framework then there&amp;#8217;s no need to repeat it. Simply omit the opening &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag and include the rest like so&amp;#8230;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;script&amp;gt;
    $("a[href$='pdf']").each(function(index) {
      pdfLabel = $(this).attr('href');
      pdfOnClick = "_gaq.push(['_trackEvent', 'PDF', 'Download', '" + pdfLabel + "']);";
      $(this).attr("onClick", pdfOnClick);
    });
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The only thing you need to keep in mind is that this code needs to be placed as far down on the page as possible. Once this code executes, it will only have an effect on the page elements (DOM elements) that have already been loaded by the browser.&lt;/p&gt;

&lt;p&gt;This is a very quick, highly efficient way to enable tracking of your PDF downloads using event tracking in Google Analytics. I&amp;#8217;m actually quite surprised that Google doesn&amp;#8217;t offer suggestions like this on their help pages.&lt;/p&gt;

&lt;p&gt;I hope you&amp;#8217;ve found this helpful. Please leave a comment if you have questions and share it if you have found it to be helpful. Thanks for reading.&lt;/p&gt;

&lt;p&gt;Happy automation!&lt;/p&gt;</description><link>http://adambuchanan.me/post/22957300206</link><guid>http://adambuchanan.me/post/22957300206</guid><pubDate>Sun, 13 May 2012 02:51:00 -0400</pubDate><category>google analytics</category><category>jquery</category><category>tracking</category></item><item><title>Scraping Keyword Suggest on TheFind.com</title><description>&lt;p&gt;This will be a really quick post. If you follow my blog then you&amp;#8217;ll already get the gist of this post and the fact that I cannot get enough of keyword research.&lt;/p&gt;

&lt;p&gt;Continuing on my quest to scrape every available keyword suggestion tool available for use in my automated keyword research methods, I decided to work on TheFind.com next. I&amp;#8217;ve honestly written so many tools like this for many different sites and at this point doing so is really trivial. However, the fact that it&amp;#8217;s easy now doesn&amp;#8217;t detract from the true value provided.&lt;/p&gt;

&lt;p&gt;If this is the first keyword research or keyword suggest scraping related post of mine that you&amp;#8217;ve read, please remember to have a look at my post &amp;#8220;&lt;a href="http://automateeverything.tumblr.com/post/19581427372/16-lines-of-bash-keyword-research-glory" target="_blank"&gt;16 Lines of Bash = Keyword Research Glory&lt;/a&gt;&amp;#8221;. It will give you a full write up of what&amp;#8217;s going on in the video below if it all looks foreign to you.&lt;/p&gt;

&lt;h2&gt;Video on Scraping TheFind.com&amp;#8217;s Keyword Suggest&lt;/h2&gt;

&lt;iframe src="http://player.vimeo.com/video/41888665" width="500" height="313" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen&gt;&lt;/iframe&gt;

&lt;p&gt;Please leave a comment if you have questions about the video. I appologize for the lack of audio as I&amp;#8217;m currently missing a microphone.&lt;/p&gt;

&lt;p&gt;Happy automation!&lt;/p&gt;</description><link>http://adambuchanan.me/post/22777566761</link><guid>http://adambuchanan.me/post/22777566761</guid><pubDate>Thu, 10 May 2012 09:18:00 -0400</pubDate><category>thefind.com</category><category>Keyword research</category><category>keyword suggest tool</category><category>web scraping</category></item><item><title>Blekko Keyword Suggest Scraper</title><description>&lt;p&gt;I love keyword research. That&amp;#8217;s a good thing because as a Search Marketing Professional, I do a ton of it. One thing that&amp;#8217;s important for keyword research is using a variety of sources to keep your results fresh. Another benefit of having several keyword research sources is having a diverse audience.&lt;/p&gt;

&lt;p&gt;When performing keyword research for Paid Search, estimating potential traffic volume is less important than for search engine optimization. As I&amp;#8217;ve &lt;a href="http://automateeverything.tumblr.com/post/19417082782/keyword-research-the-intelligent-fast-relevant-way" target="_blank"&gt;posted about&lt;/a&gt; &lt;a href="http://automateeverything.tumblr.com/post/19581427372/16-lines-of-bash-keyword-research-glory" target="_blank"&gt;several&lt;/a&gt; &lt;a href="http://automateeverything.tumblr.com/post/21057959906/google-keyword-suggest-scraper-tool" target="_blank"&gt;times before&lt;/a&gt;, my favorite style of keyword research (when search volume estimates don&amp;#8217;t matter) is scraping search/keyword suggestion tools. I&amp;#8217;ve been using Blekko for about a year now, both as a search engine but also for leveraging their search suggest functionality. It&amp;#8217;s only fitting that I show a simple command for returning Blekko keyword suggestions using a Bash shell.&lt;/p&gt;

&lt;h2&gt;Blekko Keyword Suggestions&lt;/h2&gt;

&lt;p&gt;Here&amp;#8217;s the minimum command to run to get back formatted keyword suggestions from Blekko.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;curl -s "http://blekko.com/autocomplete?query=keyword+research" | sed 's/.*\[//g;s/\].*//g;s/","/\n/g;s/"//g'
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The only portion of the command above that you&amp;#8217;ll need to modify is the &lt;code&gt;keyword+research&lt;/code&gt; text, which is equal to the root seed keyword (with spaces replaced with a plus sign). Running the command above produces these results.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;keyword research
keyword research tips
keyword research tool
keyword research services
keyword research seo tool
keyword research pro
keyword research software
keyword research seo
keyword research 2
keyword research service
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Blekko&amp;#8217;s search suggest tool is limited to 10 results at a time, which is very common. If you&amp;#8217;ve read my other posts on how this is done in Google, Yahoo, Bing, etc. then you&amp;#8217;ll know that I commonly query several search suggestion sources with one command to make this research and comparison even more simple. Below is this Blekko code integrated with my suggestion tool which combines Google, Amazon, Yahoo, Bing and now Blekko.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;#!/bin/bash

q=$(echo "$1" | sed 's/ /%20/g')

clear
echo -e "\nGetting Suggestions for \"$1\""

echo -e "\nGoogle:"
curl -s "http://www.google.com/s?sugexp=pfwl&amp;amp;cp=15&amp;amp;q=$q" | sed 's/\[/\n\[/g' | cut -d'"' -f2 | tail -n +4

echo -e "\nAmazon:"
curl -s "http://t1-completion.amazon.com/search/complete?method=completion&amp;amp;q=$q&amp;amp;search-alias=aps&amp;amp;client=amazon-search-ui&amp;amp;mkt=1&amp;amp;x=updateISSCompletion&amp;amp;sc=1" | sed 's/,\[{".*//g;s/,/\n/g' | cut -d'"' -f2 | grep -v '\[\|\]\|\{\|\]' | tail -n +2

echo -e "\nYahoo:"
y_1=$(curl -s "http://sugg.us.search.yahoo.net/gossip-us-ura?droprotated=1&amp;amp;output=sd1&amp;amp;command=$q&amp;amp;nresults=10")
# this way uses our keyword as a suffix rather than prefix. usually there are some duplicates with the first method but those are removed later.
y_2=$(curl -s "http://sugg.us.search.yahoo.net/gossip-us-ura?droprotated=0&amp;amp;output=sd1&amp;amp;command=$q&amp;amp;nresults=10")
echo $y_1 $y_2 | sed 's/{/\n{/g' | grep '"k"' | cut -d'"' -f4 | sort -u

echo -e "\nBing:"
curl -s "http://api.bing.com/qsonhs.aspx?FORM=ASAPIW&amp;amp;mkt=en-US&amp;amp;type=cb&amp;amp;cb=sa_inst.apiCB&amp;amp;q=$q&amp;amp;cp=13&amp;amp;bq=$q" | sed 's/{/\n{/g' | grep '"Txt"' | cut -d'"' -f4
echo

echo "Blekko:"
curl -s "http://blekko.com/autocomplete?query=$q" | sed 's/.*\[//g;s/\].*//g;s/","/\n/g;s/"//g'
echo; echo
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If you save the script as &lt;code&gt;sugg.sh&lt;/code&gt;, you&amp;#8217;ll be able to run it like this.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;./sugg.sh "keyword tool"
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;That will produce the following results:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Getting Suggestions for "keyword tool"

Google:
keyword tool
keyword tool free
keyword toolbox
keyword tool youtube
keyword tool estimator
keyword tool dominator
keyword tool by google
keyword tool traffic estimator
keyword tool seo
keyword tool reviews

Amazon:
keyword tool

Yahoo:

Bing:
keyword tool bing
keyword tool google
keyword tool
keyword tool external
keyword tools free
keyword tool dominator
keyword tool adwords
keyword tool wordtracker

Blekko:
keyword tool
keyword tool api
keyword tool search
keyword tool software
keyword tool download
keyword tool 2
keyword tool review
keyword tool ppc
keyword tool crack
keyword tool training
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Pretty cool right! Unfortunately, Yahoo doesn&amp;#8217;t seem to have any knowledge of what a &amp;#8220;keyword tool&amp;#8221; is. I can&amp;#8217;t say I&amp;#8217;m surprised.&lt;/p&gt;

&lt;h2&gt;Keyword Suggestion Enumeration&lt;/h2&gt;

&lt;p&gt;I mentioned above that Blekko limits the keyword suggestion results to 10 phrases and that it&amp;#8217;s very common to do so. I&amp;#8217;ve also posted about how it&amp;#8217;s trivial to start with a root phrase, like &amp;#8220;keyword tools&amp;#8221;, and then add each letter of the alphabet on to the end to see what results get returned. Doing that for every letter of the alphabet, removing duplicates and then sorting alphabetically is the next natural step in the evolution of any keyword suggestion based keyword research tool.&lt;/p&gt;

&lt;p&gt;So, here&amp;#8217;s the code for my keyword suggestion enumeration tool with the Blekko code added in. This takes a bit longer to run because it&amp;#8217;s sending many, many HTTP GET requests and has to wait for the search engine&amp;#8217;s responses. This version reaches out to 5 different search suggestion services and makes a total of 6 different queries per letter of the alphabet, which equals 156 requests. Think about how long that would take you to manually place those searches in your browser and then note the results. Waiting 10 or 15 seconds for a result doesn&amp;#8217;t seem so bad now!&lt;/p&gt;

&lt;p&gt;Anyway, here is the code.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;#!/bin/bash

q=$(echo "$1" | sed 's/ /%20/g')
tmp=tmp.txt

echo "" &amp;gt; $tmp
for suffix in {a..z}
do
    curl -s "http://www.google.com/s?sugexp=pfwl&amp;amp;cp=15&amp;amp;q=$q%20$suffix" | sed 's/\[/\n\[/g' | cut -d'"' -f2 | tail -n +4 &amp;gt;&amp;gt; $tmp
    curl -s "http://t1-completion.amazon.com/search/complete?method=completion&amp;amp;q=$q%20$suffix&amp;amp;search-alias=aps&amp;amp;client=amazon-search-ui&amp;amp;mkt=1&amp;amp;x=updateISSCompletion&amp;amp;sc=1" | sed 's/,\[{".*//g;s/,/\n/g' | cut -d'"' -f2 | grep -v '\[\|\]\|\{\|\]' | tail -n +3 &amp;gt;&amp;gt; $tmp
    curl -s "http://sugg.us.search.yahoo.net/gossip-us-ura?droprotated=1&amp;amp;output=sd1&amp;amp;command=$q%20$suffix&amp;amp;nresults=10" | sed 's/{/\n{/g' | grep '"k"' | cut -d'"' -f4 &amp;gt;&amp;gt; $tmp
    # this way uses our keyword as a suffix rather than prefix. usually there are some duplicates with the first method but those are removed later.
    curl -s "http://sugg.us.search.yahoo.net/gossip-us-ura?droprotated=0&amp;amp;output=sd1&amp;amp;command=$q%20$suffix&amp;amp;nresults=10" | sed 's/{/\n{/g' | grep '"k"' | cut -d'"' -f4 &amp;gt;&amp;gt; $tmp
    curl -s "http://api.bing.com/qsonhs.aspx?FORM=ASAPIW&amp;amp;mkt=en-US&amp;amp;type=cb&amp;amp;cb=sa_inst.apiCB&amp;amp;q=$q%20$suffix&amp;amp;cp=13&amp;amp;bq=$q" | sed 's/{/\n{/g' | grep '"Txt"' | cut -d'"' -f4 &amp;gt;&amp;gt; $tmp
    curl -s "http://blekko.com/autocomplete?query=$q" | sed 's/.*\[//g;s/\].*//g;s/","/\n/g;s/"//g' &amp;gt;&amp;gt; $tmp
    echo &amp;gt;&amp;gt; $tmp
done

sed '/^$/d' $tmp | sort | uniq
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If you save it as &amp;#8220;enum_sugg.sh&amp;#8221;, here&amp;#8217;s how you would run it.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;./enum_sugg.sh "keyword tools"
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And here&amp;#8217;s the result you would get (well, at least this is what I get right now).&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;adwords keyword tools external
best free keyword tools
best keyword tools 2012
free keyword tools online
google adwords keyword tools external
google keyword tool kit
google keyword tools google searches
google keyword tools uk
google keyword tool youtube
keyword density tools
keyword discovery tools
keyword lookup tool
keyword niche tools
keyword optimization tools
keyword phrase tool
keyword preview tool
keyword question tool
keyword research tools 2012
keyword research tools free
keyword research tools review
keyword search tool yahoo
keyword selector tool yahoo
keyword suggestion tool youtube
keyword tool adsense
keyword tool analyzer
keyword tool api
keyword tool average search volume
keyword tool bar
keyword tool beta
keyword tool bing
keyword tool book
keyword tool comma seperated
keyword tool copy list free
keyword tool density
keyword tool discover
keyword tool dominator
keyword tool download
keyword tool estimator
keyword tool external adwords
keyword tool external google
keyword tool finder
keyword tool find little competion warrior
keyword tool for app store
keyword tool for niche ideas
keyword tool generator
keyword tool golf instruction
keyword tool google adwords
keyword tool google external
keyword tool google too many searches
keyword tool in english
keyword tool in google
keyword tool in spanish
keyword tool kit
keyword tool microsoft word
keyword tool multilple
keyword tool multiple
keyword tool old
keyword tool old version
keyword tool overture
keyword tool plr
keyword tool previous interface
keyword tool previous months
keyword tools accuracy
keyword tools accurate
keyword tools adsense
keyword tools adwords
keyword tools adwords google
keyword tools affiliate
keyword tools alexa
keyword tools analysis
keyword tools articles
keyword tools available
keyword tools based
keyword tools beginners
keyword tools best
keyword tools beta
keyword tools bing
keyword tools blog
keyword tools by google
keyword tools by microsoft
keyword tools campaign
keyword tools canada
keyword tools chinese
keyword tools commercial intent
keyword tools compared
keyword tools comparison
keyword tools competition
keyword tools competitors
keyword tools content
keyword tools cpc
keyword tools de adwords
keyword tools dictionary
keyword tools digitalpoint
keyword tools directory
keyword tools disadvantages
keyword tools download
keyword tools dreamweaver
keyword tool search
keyword tool search by city
keyword tool search google
keyword tool search on yahoo google and msn
keyword tools ebay
keyword tools english
keyword tool seo
keyword tool seo organic
keyword tools estimator
keyword tools exact match
keyword tools excel
keyword tools external
keyword tools external google
keyword tools external suggestion tool
keyword tools for adsense
keyword tools for adwords
keyword tools for analysis
keyword tools for bing
keyword tools for ebay
keyword tools for mac
keyword tools for seo
keyword tools for yahoo
keyword tools free
keyword tools free in google
keyword tools from google
keyword tools generator
keyword tools google
keyword tools google adsense
keyword tools google adwords
keyword tools google old version
keyword tools google search global monthly
keyword tools google uk
keyword tools google vs wordtracker
keyword tools guide
keyword tools help
keyword tools home
keyword tools html
keyword tools html code
keyword tools hubpages
keyword tools ideas
keyword tool simple
keyword tools in english
keyword tools in google
keyword tools internet
keyword tools in yahoo
keyword tools ireland
keyword tools kei
keyword tools keywordspy
keyword tools link
keyword tools link building
keyword tools linux
keyword tools list
keyword tools local searches
keyword tools location
keyword tools long tail
keyword tools mac
keyword tools mac free
keyword tools malaysia
keyword tools market
keyword tools market samurai
keyword tools microsoft
keyword tools misspell
keyword tools moneyword matrix
keyword tools msn
keyword tools music
keyword tools nichebot
keyword tools niche finder
keyword tool software
keyword tools old interface
keyword tools omniture
keyword tools on google
keyword tools online
keyword tools online local
keyword tools on the internet
keyword tools on yahoo
keyword tools organic seo
keyword tools overture
keyword tools page optimization
keyword tools paid search
keyword tools pay per click
keyword tools pdf
keyword tools photos
keyword tools ppc
keyword tools price
keyword tools rank
keyword tools rapid
keyword tools rating
keyword tools remove duplicates
keyword tools research
keyword tools resume
keyword tools review
keyword toolss
keyword tools samurai
keyword tools search
keyword tools search engine optimization
keyword tools seo
keyword tools seobook
keyword tools site
keyword tools software
keyword tools south africa
keyword tools spy
keyword tools statistics
keyword tools techniques
keyword tools that work
keyword tools tips
keyword tools to compete
keyword tools tracker
keyword tools traffic
keyword tools traffic estimator
keyword tools traffic travis
keyword tools trends
keyword tools tricks
keyword tools twitter
keyword tools uk
keyword tools url
keyword tools warez
keyword tools webmaster
keyword tools websites
keyword tools wiki
keyword tools word
keyword tools wordpress
keyword tools wordtracker
keyword tools wordze
keyword tools work
keyword tools worth
keyword tools yahoo
keyword tools yahoo panama
keyword tools youtube
keyword tool target geographic location
keyword tool that shortens urls
keyword tool tips
keyword tool traffic estimator
keyword tool uk
keyword tool volume
keyword tool word document
keyword tool yahoo
keyword tool yuri arcurs
lsi keyword tools
paid keyword tools
seo tools keyword density
seo tools keyword list generator
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I dare you to find a better keyword research tool or method than this. I doubt you will, but if you do, I&amp;#8217;d love to know about it and be proven wrong!&lt;/p&gt;

&lt;p&gt;Anyway, I hope you enjoyed the post. Please feel free to &amp;#8220;steal&amp;#8221; my code, use it, modify it, etc.&lt;/p&gt;

&lt;p&gt;As always, happy automation!&lt;/p&gt;</description><link>http://adambuchanan.me/post/22700447516</link><guid>http://adambuchanan.me/post/22700447516</guid><pubDate>Wed, 09 May 2012 00:19:13 -0400</pubDate><category>keyword research</category><category>blekko</category><category>Keyword suggest</category></item></channel></rss>
