<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>PHP, iPad, and iPhone Developer - Ben Dodson &#187; Code</title>
	<atom:link href="http://bendodson.com/category/code/feed/" rel="self" type="application/rss+xml" />
	<link>http://bendodson.com</link>
	<description>The blog and portfolio of an Apple iPhone Developer</description>
	<lastBuildDate>Sat, 05 Dec 2009 20:00:25 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.8.5</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>TwitterFollowers PHP Class &#8211; A Better Way To Track Followers, Quitters, and Returning Followers on Twitter</title>
		<link>http://bendodson.com/2009/05/25/twitterfollowers-php-class-a-better-way-to-track-twitter-followers-quitters-and-returning-followers/</link>
		<comments>http://bendodson.com/2009/05/25/twitterfollowers-php-class-a-better-way-to-track-twitter-followers-quitters-and-returning-followers/#comments</comments>
		<pubDate>Mon, 25 May 2009 18:10:40 +0000</pubDate>
		<dc:creator>Ben Dodson</dc:creator>
				<category><![CDATA[Code]]></category>
		<category><![CDATA[followers]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[qwitter]]></category>
		<category><![CDATA[tutorial]]></category>
		<category><![CDATA[twitter]]></category>

		<guid isPermaLink="false">http://bendodson.com/blog/?p=150</guid>
		<description><![CDATA[One of the key things missing from Twitter is the ability to be notified when somebody stops following you or starts re-following you.  There are a number of web apps that can provide this information but they either require you to login or only work on individual followers rather than all of them.  In this tutorial, I show you how to effectively manage your Twitter followers.]]></description>
			<content:encoded><![CDATA[<p>Over the past few months, there have been a number of web apps that have popped up with the task of feeding your ego (or indeed deflating it) by telling you exactly who is following you on Twitter and giving you pretty graphs to show you how your followers are increasing &#8211; some of them even go so far as to estimate how many followers you are likely to have in a weeks time!  However, the key thing for me that is missing from Twitter is the ability to see who has stopped following you and also those people that stopped but are now following you again as you don&#8217;t get email alerts from Twitter for these two things.  This is a useful piece of information to have as it will let you know when people drop off and whether they are important (e.g. friends who don&#8217;t care what you are talking about thus suggesting you should stop talking crap) or not (e.g. spam bots).</p>
<p><span id="more-150"></span>I did a bit of research and the only real web app to fulfill this need that I could find was the beautifully designed <a href="http://useqwitter.com/">Qwitter</a>.  However, the problem with Qwitter is that it only gives you the details for one person with the idea being that you say &#8220;tell me when this username stops following me&#8221; &#8211; it&#8217;s a little bit stalkerish in my opinion!  Like any PHP developer, I decided that I could build my own little system to give me my Twitter ego boost and so have come up with the class below which you can all now take and use as you see fit.</p>
<p><i><strong>Update:</strong> Turns out I was wrong about Qwitter as the username you put in to follow is supposed to be yours, not the person you want to watch when they leave you.  They need to do better copywriting! In any case, the class below serves as a good demo of the public Twitter data and allows you to extend it the way you want.</i></p>
<p><i><strong>Note:</strong> This won&#8217;t work straight out of the box &#8211; I&#8217;ve put in a few comments which say &#8220;SQL Required&#8221;.  This is because you may well have your own schema (although I do provide one) and you may have your own framework or DB connection functions (I know I do).  Once you&#8217;ve done those, just substitute the constants for your own details and it should all work</i></p>
<h3>TwitterFollowers.class.php</h3>

<div class="wp_syntax"><div class="code"><pre class="php" style="font-family:monospace;"><span style="color: #000000; font-weight: bold;">&lt;?php</span>
&nbsp;
<span style="color: #009933; font-style: italic;">/**
 * Crawls Twitter Followers and sends an email alert to show you who has started following, stopped following, and started re-following
 *
 * @author Ben Dodson
 **/</span>
<span style="color: #000000; font-weight: bold;">class</span> TwitterFollowers
<span style="color: #009900;">&#123;</span>
	<span style="color: #666666; font-style: italic;">// define constants</span>
	<span style="color: #000000; font-weight: bold;">const</span> username	<span style="color: #339933;">=</span> <span style="color: #0000ff;">'bendodson'</span><span style="color: #339933;">;</span>
	<span style="color: #000000; font-weight: bold;">const</span> email 	<span style="color: #339933;">=</span> <span style="color: #0000ff;">'ben@bendodson.com'</span><span style="color: #339933;">;</span>
	<span style="color: #000000; font-weight: bold;">const</span> subject	<span style="color: #339933;">=</span> <span style="color: #0000ff;">'Twitter Updates'</span><span style="color: #339933;">;</span>
	<span style="color: #000000; font-weight: bold;">const</span> from	<span style="color: #339933;">=</span> <span style="color: #0000ff;">'TwitterBot &lt;twitter@bendodson.com&gt;'</span><span style="color: #339933;">;</span>
&nbsp;
	<span style="color: #666666; font-style: italic;">// define internal variables</span>
	protected <span style="color: #000088;">$actualFollowers</span> <span style="color: #339933;">=</span> <span style="color: #990000;">array</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
	protected <span style="color: #000088;">$internalFollowers</span> <span style="color: #339933;">=</span> <span style="color: #990000;">array</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
	protected <span style="color: #000088;">$followerChanges</span> <span style="color: #339933;">=</span> <span style="color: #990000;">array</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
	protected <span style="color: #000088;">$now</span> <span style="color: #339933;">=</span> <span style="color: #0000ff;">''</span><span style="color: #339933;">;</span>
&nbsp;
	<span style="color: #000000; font-weight: bold;">function</span> __construct<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span>
	<span style="color: #009900;">&#123;</span>
		<span style="color: #000088;">$this</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">now</span> <span style="color: #339933;">=</span> <span style="color: #990000;">date</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'Y-m-d H:i:s'</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
		<span style="color: #000088;">$json</span> <span style="color: #339933;">=</span> <span style="color: #990000;">file_get_contents</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'http://twitter.com/followers/ids/'</span><span style="color: #339933;">.</span><span style="color: #000000; font-weight: bold;">self</span><span style="color: #339933;">::</span><span style="color: #004000;">username</span><span style="color: #339933;">.</span><span style="color: #0000ff;">'.json'</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
		<span style="color: #000088;">$this</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">actualFollowers</span> <span style="color: #339933;">=</span> <span style="color: #990000;">json_decode</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$json</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
		<span style="color: #000088;">$this</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">internalFollowers</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$this</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">getInternalFollowers</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
		<span style="color: #b1b100;">foreach</span> <span style="color: #009900;">&#40;</span><span style="color: #000088;">$this</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">actualFollowers</span> <span style="color: #b1b100;">as</span> <span style="color: #000088;">$actualFollower</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
			<span style="color: #b1b100;">if</span> <span style="color: #009900;">&#40;</span><span style="color: #339933;">!</span><span style="color: #990000;">in_array</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$actualFollower</span><span style="color: #339933;">,</span> <span style="color: #000088;">$this</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">internalFollowers</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
				<span style="color: #b1b100;">if</span> <span style="color: #009900;">&#40;</span><span style="color: #000088;">$this</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">getTweeter</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$actualFollower</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
					<span style="color: #000088;">$this</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">followerChanges</span><span style="color: #009900;">&#91;</span><span style="color: #0000ff;">'returning follower'</span><span style="color: #009900;">&#93;</span><span style="color: #009900;">&#91;</span><span style="color: #009900;">&#93;</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$actualFollower</span><span style="color: #339933;">;</span>
					<span style="color: #666666; font-style: italic;">/*
					--SQL REQUIRED--
					UPDATE TwitterFollowers SET start = $this-&gt;now, end = NULL WHERE id = $actualFollower
					*/</span>
				<span style="color: #009900;">&#125;</span> <span style="color: #b1b100;">else</span> <span style="color: #009900;">&#123;</span>
					<span style="color: #000088;">$this</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">followerChanges</span><span style="color: #009900;">&#91;</span><span style="color: #0000ff;">'new follower'</span><span style="color: #009900;">&#93;</span><span style="color: #009900;">&#91;</span><span style="color: #009900;">&#93;</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$actualFollower</span><span style="color: #339933;">;</span>
					<span style="color: #666666; font-style: italic;">/*
					--SQL REQUIRED--
					INSERT INTO TwitterFollowers (id) VALUES ($actualFollower)
					*/</span>
				<span style="color: #009900;">&#125;</span>
			<span style="color: #009900;">&#125;</span>
		<span style="color: #009900;">&#125;</span>
&nbsp;
		<span style="color: #b1b100;">foreach</span> <span style="color: #009900;">&#40;</span><span style="color: #000088;">$this</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">internalFollowers</span> <span style="color: #b1b100;">as</span> <span style="color: #000088;">$internalFollower</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
			<span style="color: #b1b100;">if</span> <span style="color: #009900;">&#40;</span><span style="color: #339933;">!</span><span style="color: #990000;">in_array</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$internalFollower</span><span style="color: #339933;">,</span> <span style="color: #000088;">$this</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">actualFollowers</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
				<span style="color: #000088;">$this</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">followerChanges</span><span style="color: #009900;">&#91;</span><span style="color: #0000ff;">'stopped following'</span><span style="color: #009900;">&#93;</span><span style="color: #009900;">&#91;</span><span style="color: #009900;">&#93;</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$internalFollower</span><span style="color: #339933;">;</span>
				<span style="color: #666666; font-style: italic;">/*
				--SQL REQUIRED--
				UPDATE TwitterFollowers SET end = $this-&gt;now WHERE id = $internalFollower
				*/</span>
			<span style="color: #009900;">&#125;</span>
		<span style="color: #009900;">&#125;</span>
&nbsp;
		<span style="color: #000088;">$this</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">sendEmail</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>	
	<span style="color: #009900;">&#125;</span>
&nbsp;
	protected <span style="color: #000000; font-weight: bold;">function</span> getInternalFollowers<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span>
	<span style="color: #009900;">&#123;</span>
		<span style="color: #000088;">$data</span> <span style="color: #339933;">=</span> <span style="color: #990000;">array</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
		<span style="color: #000088;">$raw</span> <span style="color: #339933;">=</span> 
		<span style="color: #666666; font-style: italic;">/*
		--SQL REQUIRED--
		SELECT id FROM TwitterFollowers WHERE end IS NULL
		*/</span>
		<span style="color: #b1b100;">foreach</span> <span style="color: #009900;">&#40;</span><span style="color: #000088;">$raw</span> <span style="color: #b1b100;">as</span> <span style="color: #000088;">$r</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
			<span style="color: #000088;">$data</span><span style="color: #009900;">&#91;</span><span style="color: #009900;">&#93;</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$r</span><span style="color: #009900;">&#91;</span><span style="color: #0000ff;">'id'</span><span style="color: #009900;">&#93;</span><span style="color: #339933;">;</span>
		<span style="color: #009900;">&#125;</span>
		<span style="color: #b1b100;">return</span> <span style="color: #000088;">$data</span><span style="color: #339933;">;</span>
	<span style="color: #009900;">&#125;</span>
&nbsp;
	protected <span style="color: #000000; font-weight: bold;">function</span> getTweeter<span style="color: #009900;">&#40;</span><span style="color: #000088;">$id</span><span style="color: #009900;">&#41;</span>
	<span style="color: #009900;">&#123;</span>
		<span style="color: #b1b100;">return</span>
		<span style="color: #666666; font-style: italic;">/*
		--SQL REQUIRED--
		SELET * FROM TwitterFollowers WHERE id = $id LIMIT 1
		*/</span>		
	<span style="color: #009900;">&#125;</span>
&nbsp;
	protected <span style="color: #000000; font-weight: bold;">function</span> getTweeterDetails<span style="color: #009900;">&#40;</span><span style="color: #000088;">$id</span><span style="color: #009900;">&#41;</span>
	<span style="color: #009900;">&#123;</span>
		<span style="color: #000088;">$json</span> <span style="color: #339933;">=</span> <span style="color: #990000;">file_get_contents</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'http://twitter.com/users/show/'</span><span style="color: #339933;">.</span><span style="color: #000088;">$id</span><span style="color: #339933;">.</span><span style="color: #0000ff;">'.json'</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
		<span style="color: #000088;">$tweeter</span> <span style="color: #339933;">=</span> <span style="color: #990000;">json_decode</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$json</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
		<span style="color: #b1b100;">return</span> <span style="color: #000088;">$tweeter</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">name</span> <span style="color: #339933;">.</span> <span style="color: #0000ff;">' ('</span><span style="color: #339933;">.</span><span style="color: #000088;">$tweeter</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">screen_name</span> <span style="color: #339933;">.</span> <span style="color: #0000ff;">')'</span><span style="color: #339933;">;</span>
	<span style="color: #009900;">&#125;</span>
&nbsp;
	protected <span style="color: #000000; font-weight: bold;">function</span> sendEmail<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span>
	<span style="color: #009900;">&#123;</span>
		<span style="color: #000088;">$to</span>      <span style="color: #339933;">=</span> <span style="color: #000000; font-weight: bold;">self</span><span style="color: #339933;">::</span><span style="color: #004000;">email</span><span style="color: #339933;">;</span>
		<span style="color: #000088;">$subject</span> <span style="color: #339933;">=</span> <span style="color: #000000; font-weight: bold;">self</span><span style="color: #339933;">::</span><span style="color: #004000;">subject</span><span style="color: #339933;">;</span>
		<span style="color: #000088;">$headers</span> <span style="color: #339933;">=</span> <span style="color: #0000ff;">'From: '</span> <span style="color: #339933;">.</span> <span style="color: #000000; font-weight: bold;">self</span><span style="color: #339933;">::</span><span style="color: #004000;">from</span> <span style="color: #339933;">.</span> <span style="color: #0000ff;">&quot;<span style="color: #000099; font-weight: bold;">\r</span><span style="color: #000099; font-weight: bold;">\n</span>&quot;</span> <span style="color: #339933;">.</span> <span style="color: #0000ff;">'Reply-To: '</span> <span style="color: #339933;">.</span> <span style="color: #000000; font-weight: bold;">self</span><span style="color: #339933;">::</span><span style="color: #004000;">from</span><span style="color: #339933;">;</span>
&nbsp;
		<span style="color: #000088;">$message</span> <span style="color: #339933;">=</span> <span style="color: #0000ff;">'Hi,'</span> <span style="color: #339933;">.</span> <span style="color: #0000ff;">&quot;<span style="color: #000099; font-weight: bold;">\r</span><span style="color: #000099; font-weight: bold;">\n</span><span style="color: #000099; font-weight: bold;">\r</span><span style="color: #000099; font-weight: bold;">\n</span>&quot;</span><span style="color: #339933;">;</span>
		<span style="color: #000088;">$message</span> <span style="color: #339933;">.=</span> <span style="color: #0000ff;">'Here are your Twitter Updates:'</span> <span style="color: #339933;">.</span> <span style="color: #0000ff;">&quot;<span style="color: #000099; font-weight: bold;">\r</span><span style="color: #000099; font-weight: bold;">\n</span>&quot;</span><span style="color: #339933;">;</span>
&nbsp;
		<span style="color: #b1b100;">if</span> <span style="color: #009900;">&#40;</span><span style="color: #990000;">count</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$this</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">followerChanges</span><span style="color: #009900;">&#41;</span> <span style="color: #339933;">&gt;</span> <span style="color: #cc66cc;">0</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
			<span style="color: #b1b100;">foreach</span> <span style="color: #009900;">&#40;</span><span style="color: #000088;">$this</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">followerChanges</span> <span style="color: #b1b100;">as</span> <span style="color: #000088;">$changeType</span> <span style="color: #339933;">=&gt;</span> <span style="color: #000088;">$change</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
				<span style="color: #000088;">$message</span> <span style="color: #339933;">.=</span> <span style="color: #0000ff;">&quot;<span style="color: #000099; font-weight: bold;">\r</span><span style="color: #000099; font-weight: bold;">\n</span>&quot;</span> <span style="color: #339933;">.</span> <span style="color: #0000ff;">'--'</span><span style="color: #339933;">.</span><span style="color: #990000;">strtoupper</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$changeType</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">.</span><span style="color: #0000ff;">'--'</span> <span style="color: #339933;">.</span> <span style="color: #0000ff;">&quot;<span style="color: #000099; font-weight: bold;">\r</span><span style="color: #000099; font-weight: bold;">\n</span><span style="color: #000099; font-weight: bold;">\r</span><span style="color: #000099; font-weight: bold;">\n</span>&quot;</span><span style="color: #339933;">;</span>
				<span style="color: #b1b100;">foreach</span> <span style="color: #009900;">&#40;</span><span style="color: #000088;">$change</span> <span style="color: #b1b100;">as</span> <span style="color: #000088;">$tweeter</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
					<span style="color: #000088;">$message</span> <span style="color: #339933;">.=</span> <span style="color: #0000ff;">'*'</span> <span style="color: #339933;">.</span> <span style="color: #000088;">$this</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">getTweeterDetails</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$tweeter</span><span style="color: #009900;">&#41;</span> <span style="color: #339933;">.</span> <span style="color: #0000ff;">&quot;<span style="color: #000099; font-weight: bold;">\r</span><span style="color: #000099; font-weight: bold;">\n</span>&quot;</span><span style="color: #339933;">;</span>
				<span style="color: #009900;">&#125;</span>
			<span style="color: #009900;">&#125;</span>
		<span style="color: #009900;">&#125;</span> <span style="color: #b1b100;">else</span> <span style="color: #009900;">&#123;</span>
			<span style="color: #000088;">$message</span> <span style="color: #339933;">.=</span> <span style="color: #0000ff;">&quot;<span style="color: #000099; font-weight: bold;">\r</span><span style="color: #000099; font-weight: bold;">\n</span>&quot;</span> <span style="color: #339933;">.</span> <span style="color: #0000ff;">'--NO UPDATES FOUND--'</span> <span style="color: #339933;">.</span> <span style="color: #0000ff;">&quot;<span style="color: #000099; font-weight: bold;">\r</span><span style="color: #000099; font-weight: bold;">\n</span>&quot;</span><span style="color: #339933;">;</span>
		<span style="color: #009900;">&#125;</span>
&nbsp;
		<span style="color: #990000;">mail</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$to</span><span style="color: #339933;">,</span> <span style="color: #000088;">$subject</span><span style="color: #339933;">,</span> <span style="color: #000088;">$message</span><span style="color: #339933;">,</span> <span style="color: #000088;">$headers</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
	<span style="color: #009900;">&#125;</span>
&nbsp;
<span style="color: #009900;">&#125;</span>
&nbsp;
<span style="color: #000000; font-weight: bold;">?&gt;</span></pre></div></div>

<h3>MySQL Database Schema</h3>

<div class="wp_syntax"><div class="code"><pre class="sql" style="font-family:monospace;"><span style="color: #993333; font-weight: bold;">CREATE</span> <span style="color: #993333; font-weight: bold;">TABLE</span> <span style="color: #993333; font-weight: bold;">IF</span> <span style="color: #993333; font-weight: bold;">NOT</span> <span style="color: #993333; font-weight: bold;">EXISTS</span> <span style="color: #ff0000;">`TwitterFollowers`</span> <span style="color: #66cc66;">&#40;</span>
  <span style="color: #ff0000;">`id`</span> int<span style="color: #66cc66;">&#40;</span><span style="color: #cc66cc;">20</span><span style="color: #66cc66;">&#41;</span> <span style="color: #993333; font-weight: bold;">NOT</span> <span style="color: #993333; font-weight: bold;">NULL</span><span style="color: #66cc66;">,</span>
  <span style="color: #ff0000;">`start`</span> timestamp <span style="color: #993333; font-weight: bold;">NOT</span> <span style="color: #993333; font-weight: bold;">NULL</span> <span style="color: #993333; font-weight: bold;">DEFAULT</span> CURRENT_TIMESTAMP<span style="color: #66cc66;">,</span>
  <span style="color: #ff0000;">`end`</span> timestamp <span style="color: #993333; font-weight: bold;">NULL</span> <span style="color: #993333; font-weight: bold;">DEFAULT</span> <span style="color: #993333; font-weight: bold;">NULL</span><span style="color: #66cc66;">,</span>
  <span style="color: #993333; font-weight: bold;">PRIMARY</span> <span style="color: #993333; font-weight: bold;">KEY</span>  <span style="color: #66cc66;">&#40;</span><span style="color: #ff0000;">`id`</span><span style="color: #66cc66;">&#41;</span>
<span style="color: #66cc66;">&#41;</span> ENGINE<span style="color: #66cc66;">=</span>MyISAM <span style="color: #993333; font-weight: bold;">DEFAULT</span> CHARSET<span style="color: #66cc66;">=</span>latin1;</pre></div></div>

<h3>How does it work?</h3>
<p>First of all, you will need to substitute the SQL sections for your own particular schema and database functions.  Once you&#8217;ve done that, alter the class constants so that they are your own username and the email address you want to send your updates to.  Finally, set up a CRON job so that it runs at a certain point every day.  I currently have mine set to run at 9am every morning but I may well change it to run every time I post a tweet as then I&#8217;d be able to see which tweet had made people start or stop following me.</p>
<p>The script works by checking the publicly accessible JSON feed of your followers and getting all of their IDs.  I say it&#8217;s publicly accessible, but I don&#8217;t think it is if you have protected your updates which will of course cause problems!  Once it has all of the IDs, it checks this against the IDs stored in your database &#8211; if there aren&#8217;t any then everyone will show up as following you on the first run.  If it finds an ID in your database that isn&#8217;t in your JSON string then you&#8217;ve been dumped!  Conversely, if it finds an ID in the JSON string but not in the DB then, congratulations, you&#8217;ve got a new follower.  The final instance is if it finds an ID in the JSON string that is in the DB but has an end datetime assigned to it.  This means the person was following you, stopped, and has now decided to re-follow you.</p>
<p>The whole lot then gets packaged up and emailed to you with each section broken down so you can read it clearly.  In order to do this, it looks up each ID that goes into the email against that persons publicly available Twitter information to give you both their &#8220;real name&#8221; and &#8220;username&#8221;.</p>
<h3>Known Problems</h3>
<p>* I don&#8217;t think it will work if you have your updates set to hidden.<br />
* If one of your followers gets banned from Twitter, then their name won&#8217;t show up in the email (it will just be blank)<br />
* This script won&#8217;t work if you have more than 5000 followers &#8211; this is because that is the maximum result set from the JSON string.  You&#8217;d need to add paging information to get more than 5000 although this would be fairly easily.  Alas I don&#8217;t have that many followers to be able to test that out!</p>
<h3>Conclusion</h3>
<p>So now you can easily (if you know PHP) get updates on all your Twitter followers and non-followers.  Feel free to use all the above code and modify to your hearts content &#8211; if you found it to be useful, then please leave a comment below.  Oh, and I couldn&#8217;t possibly write a post about Twitter without reminding you that you can follow me <a href="http://twitter.com/bendodson/">@bendodson</a> <img src='http://bendodson.com/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' /> </p>
]]></content:encoded>
			<wfw:commentRss>http://bendodson.com/2009/05/25/twitterfollowers-php-class-a-better-way-to-track-twitter-followers-quitters-and-returning-followers/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>Getting Xbox Live Achievements Data: Part 2 &#8211; The AppleScript Solution</title>
		<link>http://bendodson.com/2009/05/19/getting-xbox-live-achievements-data-part-2-the-applescript-solution/</link>
		<comments>http://bendodson.com/2009/05/19/getting-xbox-live-achievements-data-part-2-the-applescript-solution/#comments</comments>
		<pubDate>Tue, 19 May 2009 23:54:39 +0000</pubDate>
		<dc:creator>Ben Dodson</dc:creator>
				<category><![CDATA[Code]]></category>
		<category><![CDATA[achievements]]></category>
		<category><![CDATA[applescript]]></category>
		<category><![CDATA[gamerscore]]></category>
		<category><![CDATA[windows live]]></category>
		<category><![CDATA[xbox 360]]></category>

		<guid isPermaLink="false">http://bendodson.com/blog/?p=140</guid>
		<description><![CDATA[Following on from the <a href="http://bendodson.com/blog/2009/05/12/getting-xbox-live-achievements-with-php-part-1-the-problems/">first of this series of tutorials</a> on how to extract Xbox Live achievement data using PHP and AppleScript, I thought I would use this second part to look at the AppleScript that powers one side of the system I've put together.  In this tutorial, you'll learn how to use Safari to open remote documents and save their text and source code to files locally.]]></description>
			<content:encoded><![CDATA[<p>Following on from the <a href="http://bendodson.com/blog/2009/05/12/getting-xbox-live-achievements-with-php-part-1-the-problems/">first of this series of tutorials</a> on how to extract Xbox Live achievement data using PHP and AppleScript, I thought I would use this second part to look at the AppleScript that powers one side of the system I&#8217;ve put together.  In the next part, I&#8217;ll be explaining the PHP class I&#8217;ve built, and in the fourth part (the last of the series) I&#8217;ll be showing you how the two talk together and how you can use the collected data with other APIs such as Facebook Connect.</p>
<p><span id="more-140"></span>So, let&#8217;s have a look at some code!</p>
<h3>XboxLive AppleScript</h3>

<div class="wp_syntax"><div class="code"><pre class="applescript" style="font-family:monospace;"><span style="color: #ff0033; font-weight: bold;">set</span> urlFilePath <span style="color: #ff0033; font-weight: bold;">to</span> <span style="color: #009900;">&quot;http://externals.bendodson.com/XboxLive/urls.txt&quot;</span>
<span style="color: #ff0033; font-weight: bold;">set</span> dataFilePath <span style="color: #ff0033; font-weight: bold;">to</span> <span style="color: #009900;">&quot;server:XboxLive:data.txt&quot;</span>
<span style="color: #ff0033; font-weight: bold;">set</span> toCrawl <span style="color: #ff0033; font-weight: bold;">to</span> <span style="color: #009900;">&quot;&quot;</span>
&nbsp;
<span style="color: #ff0033; font-weight: bold;">set</span> dataFile <span style="color: #ff0033; font-weight: bold;">to</span> <span style="color: #0066ff;">open</span> for access <span style="color: #0066ff;">file</span> dataFilePath <span style="color: #ff0033; font-weight: bold;">with</span> write permission
<span style="color: #ff0033; font-weight: bold;">set</span> eof <span style="color: #ff0033; font-weight: bold;">of</span> dataFile <span style="color: #ff0033; font-weight: bold;">to</span> <span style="color: #000000;">0</span>
<span style="color: #0066ff;">close</span> access dataFile
&nbsp;
<span style="color: #ff0033; font-weight: bold;">tell</span> <span style="color: #0066ff;">application</span> <span style="color: #009900;">&quot;Safari&quot;</span>
	<span style="color: #0066ff;">open</span> location urlFilePath
	delay <span style="color: #000000;">1</span>
	<span style="color: #0066ff;">do JavaScript</span> <span style="color: #009900;">&quot;window.location.reload()&quot;</span> <span style="color: #ff0033; font-weight: bold;">in</span> <span style="color: #ff0033;">the</span> <span style="color: #ff0033;">front</span>
	delay <span style="color: #000000;">1</span>
	<span style="color: #ff0033; font-weight: bold;">try</span>
		<span style="color: #ff0033; font-weight: bold;">set</span> toCrawl <span style="color: #ff0033; font-weight: bold;">to</span> <span style="color: #000000;">&#40;</span><span style="color: #ff0033;">the</span> <span style="color: #0066ff;">text</span> <span style="color: #ff0033; font-weight: bold;">of</span> <span style="color: #ff0033;">the</span> <span style="color: #ff0033;">front</span> <span style="color: #0066ff;">document</span><span style="color: #000000;">&#41;</span>
	<span style="color: #ff0033; font-weight: bold;">end</span> <span style="color: #ff0033; font-weight: bold;">try</span>
<span style="color: #ff0033; font-weight: bold;">end</span> <span style="color: #ff0033; font-weight: bold;">tell</span>
&nbsp;
<span style="color: #ff0033; font-weight: bold;">if</span> length <span style="color: #ff0033; font-weight: bold;">of</span> toCrawl &gt; <span style="color: #000000;">0</span> <span style="color: #ff0033; font-weight: bold;">then</span>
	<span style="color: #ff0033; font-weight: bold;">tell</span> <span style="color: #0066ff;">application</span> <span style="color: #009900;">&quot;Safari&quot;</span> <span style="color: #ff0033; font-weight: bold;">to</span> <span style="color: #0066ff;">open</span> location toCrawl
	delay <span style="color: #000000;">15</span>
	<span style="color: #ff0033; font-weight: bold;">tell</span> <span style="color: #0066ff;">application</span> <span style="color: #009900;">&quot;Safari&quot;</span>
		<span style="color: #ff0033; font-weight: bold;">set</span> sourceCode <span style="color: #ff0033; font-weight: bold;">to</span> <span style="color: #ff0033;">the</span> source <span style="color: #ff0033; font-weight: bold;">of</span> <span style="color: #ff0033;">front</span> <span style="color: #0066ff;">document</span> <span style="color: #ff0033;">as</span> <span style="color: #0066ff;">string</span>
		<span style="color: #ff0033; font-weight: bold;">set</span> dataFile <span style="color: #ff0033; font-weight: bold;">to</span> <span style="color: #0066ff;">open</span> for access <span style="color: #0066ff;">file</span> dataFilePath <span style="color: #ff0033; font-weight: bold;">with</span> write permission
		write sourceCode <span style="color: #ff0033; font-weight: bold;">to</span> dataFile starting at eof
		<span style="color: #0066ff;">close</span> access dataFile
	<span style="color: #ff0033; font-weight: bold;">end</span> <span style="color: #ff0033; font-weight: bold;">tell</span>
<span style="color: #ff0033; font-weight: bold;">end</span> <span style="color: #ff0033; font-weight: bold;">if</span>
&nbsp;
<span style="color: #ff0033; font-weight: bold;">tell</span> <span style="color: #0066ff;">application</span> <span style="color: #009900;">&quot;Safari&quot;</span> <span style="color: #ff0033; font-weight: bold;">to</span> <span style="color: #0066ff;">close</span> <span style="color: #ff0033;">every</span> <span style="color: #0066ff;">window</span></pre></div></div>

<p>This is the first time I&#8217;ve used AppleScript for anything other than just playing around and I have to say that as a language it&#8217;s incredibly good.  Just by reading through the above, you&#8217;ll probably be able to work out what&#8217;s going on even if you&#8217;ve never seen any type of programming code in the past.  Even so, I&#8217;ll go through each section and explain what it does along with the reasons why I decided to do it in this particular way rather than some of the other ways I could have chosen.</p>
<p><em><strong>Note:</strong> All of this AppleScript is completely self-taught from searching around on the internet.  I was going to buy the book <a href="http://www.amazon.co.uk/AppleScript-Missing-Manual-Adam-Goldstein/dp/0596008503/ref=sr_11_1?ie=UTF8&#038;qid=1242776373&#038;sr=11-1">AppleScript: The Missing Manual</a> but I was able to read all the sections I needed on <a href="http://books.google.com/books?id=-ynfWvkwzpwC">Google Books</a> which was convenient &#8211; I&#8217;ll probably buy the book anyway to brush up on a few other areas.  If you are an AppleScript guru and you know a way to optimize my code, please use the comments section below so others can learn and so I can update it.</em></p>
<p>Before we get on to looking at the code, it might be worth briefly recapping how everything will work.  My server will check the XboxLive API in order to see if my gamerscore has increased.  If it has, then it saves the URL of the achievements page for my most recently played game (which it can&#8217;t itself read due you needing to login to Xbox Live with javascript enabled &#8211; something cURL can&#8217;t do!) in a text file on the server.  My mac mini at home then runs the above AppleScript in order to retrieve the saved URL, open it in Safari, and save the HTML in it&#8217;s own text file that is available via the internet.  My server will then check this text file, parse the HTML, and save the achievements to a database.</p>
<h3>How does it all work?</h3>
<p>Now we&#8217;ve got that out of the way, let&#8217;s look at that AppleScript in more detail:</p>

<div class="wp_syntax"><div class="code"><pre class="applescript" style="font-family:monospace;"><span style="color: #ff0033; font-weight: bold;">set</span> urlFilePath <span style="color: #ff0033; font-weight: bold;">to</span> <span style="color: #009900;">&quot;http://externals.bendodson.com/XboxLive/urls.txt&quot;</span>
<span style="color: #ff0033; font-weight: bold;">set</span> dataFilePath <span style="color: #ff0033; font-weight: bold;">to</span> <span style="color: #009900;">&quot;server:XboxLive:data.txt&quot;</span>
<span style="color: #ff0033; font-weight: bold;">set</span> toCrawl <span style="color: #ff0033; font-weight: bold;">to</span> <span style="color: #009900;">&quot;&quot;</span></pre></div></div>

<p>These three lines of code are used to define variables which we will use later on in the code.  The first one, <code>urlFilePath</code>, is the URL of the text file on my server that will tell our script what URL we need to retrieve the HTML from.  You&#8217;ll see how we populate that text file with my XboxLive php class which will be discussed in part three of this four part series.  The second variable, <code>dataFilePath</code>, is interesting as it contains the path to the file we are going to save the HTML to on the local machine.  So why the strange syntax?  This is referred to as Finder syntax and is a way for AppleScript to reference a particular section within Finder, in this case a text file.  It is essentially the same as &#8220;/server/XboxLive/data.txt&#8221; which we could have used &#8211; the difference is that we would have had to have converted that into the Finder syntax in order to use some of the file editing commands we&#8217;ll use later so I thought it best just to save it correctly at the beginning.</p>

<div class="wp_syntax"><div class="code"><pre class="applescript" style="font-family:monospace;"><span style="color: #ff0033; font-weight: bold;">set</span> dataFile <span style="color: #ff0033; font-weight: bold;">to</span> <span style="color: #0066ff;">open</span> for access <span style="color: #0066ff;">file</span> dataFilePath <span style="color: #ff0033; font-weight: bold;">with</span> write permission
<span style="color: #ff0033; font-weight: bold;">set</span> eof <span style="color: #ff0033; font-weight: bold;">of</span> dataFile <span style="color: #ff0033; font-weight: bold;">to</span> <span style="color: #000000;">0</span>
<span style="color: #0066ff;">close</span> access dataFile</pre></div></div>

<p>These three lines are again fairly easy to follow.  We set a variable of <code>dataFile</code> to be the handler of the file declared in the path of <code>dataFilePath</code>.  Note that we have specifically mentioned we want to use write permissions as the default is just to use read permissions.  The next line sets the eof or &#8220;end of file&#8221; within the handler to 0 whilst the following line tidies up by closing the file handler (which isn&#8217;t strictly necessary but good practice).  The reason for setting <code>eof</code> to 0 is that we want to delete the contents of the file before we put anything else in later.  This is practical for the simple reason that we don&#8217;t want our PHP script on the server to parse a load of data in the text file (or even download it) if it&#8217;s something it has already read as that would be a waste of processing power and bandwidth.</p>

<div class="wp_syntax"><div class="code"><pre class="applescript" style="font-family:monospace;"><span style="color: #ff0033; font-weight: bold;">tell</span> <span style="color: #0066ff;">application</span> <span style="color: #009900;">&quot;Safari&quot;</span>
	<span style="color: #0066ff;">open</span> location urlFilePath
	delay <span style="color: #000000;">1</span>
	<span style="color: #0066ff;">do JavaScript</span> <span style="color: #009900;">&quot;window.location.reload()&quot;</span> <span style="color: #ff0033; font-weight: bold;">in</span> <span style="color: #ff0033;">the</span> <span style="color: #ff0033;">front</span>
	delay <span style="color: #000000;">1</span>
	<span style="color: #ff0033; font-weight: bold;">try</span>
		<span style="color: #ff0033; font-weight: bold;">set</span> toCrawl <span style="color: #ff0033; font-weight: bold;">to</span> <span style="color: #000000;">&#40;</span><span style="color: #ff0033;">the</span> <span style="color: #0066ff;">text</span> <span style="color: #ff0033; font-weight: bold;">of</span> <span style="color: #ff0033;">the</span> <span style="color: #ff0033;">front</span> <span style="color: #0066ff;">document</span><span style="color: #000000;">&#41;</span>
	<span style="color: #ff0033; font-weight: bold;">end</span> <span style="color: #ff0033; font-weight: bold;">try</span>
<span style="color: #ff0033; font-weight: bold;">end</span> <span style="color: #ff0033; font-weight: bold;">tell</span></pre></div></div>

<p>Now we get to the first real section of the code that deals with our problem.  In these lines, the application Safari is made to open the text file on the server that may contain the URLs, refresh that page using JavaScript, and then attempt to set the variable <code>toCrawl</code> to the text of the file.  Before we even examine this in depth, you may be wondering something along the lines of &#8220;why don&#8217;t you download the file or read it with FTP rather than opening it in Safari&#8221; and this would be sensible.  My initial thoughts on how to get the text on the server into a variable in my AppleScript was to access the file via FTP.  OS X has very basic FTP support (read only unfortunately) that can be mounted through Finder and then accessed using the Finder syntax we used earlier on.  I had originally written some AppleScript that would run in the startup process of the mac mini which would mount the drive.  Then, this AppleScript read the file in using <code>open for access file urlFilePath</code> and set the variable that way.  It all worked perfectly until the server changed the contents of the URL text file.  No matter what I did, the text file came back the same as it had when first fetched and it was that that I realised that the FTP built into Finder is fundamentally flawed as everything is cached.  If you don&#8217;t edit the file through Finder (e.g. by using a mac application that saves it again through Finder) then it will never know it&#8217;s updated and thus can&#8217;t be used in this scenario.</p>
<p>With that out of the way, let&#8217;s look at my workaround.  The first and last lines are the opening and closing of a <code>tell</code>; a way to get an application to do what we want.  In this instance we are going to tell Safari to open the URL saved in the variable <code>urlFilePath</code> and then delay for one second.  This delay is needed as Safari may take this long to open the URL.  Without the delay, we may be in danger of running code on a page that hasn&#8217;t loaded.  In the next line, we tell JavaScript to reload the document before waiting another second for this to complete.  This refresh is necessary to clear any caching of the document.  The final section is used to set the variable <code>toCrawl</code> to the contents of the browser window.  You may wonder why there is a <code>try</code> statement wrapped around it?  This is because if the text file was empty and you tried to get <code>the text of the front document</code>, the script would error.  To get around that, we initially set the variable (in the very first block of code if you remember?) and then use <code>try</code> to reset the variable only if no error would be caused in doing so.  Very useful!</p>
<p>By the end of this block of code, we will have a variable which will either contain a URL if the text file on the server had one, or it will be empty, meaning that the server is not requesting any HTML.  Let&#8217;s move on to the next section:</p>

<div class="wp_syntax"><div class="code"><pre class="applescript" style="font-family:monospace;"><span style="color: #ff0033; font-weight: bold;">if</span> length <span style="color: #ff0033; font-weight: bold;">of</span> toCrawl &gt; <span style="color: #000000;">0</span> <span style="color: #ff0033; font-weight: bold;">then</span>
	<span style="color: #ff0033; font-weight: bold;">tell</span> <span style="color: #0066ff;">application</span> <span style="color: #009900;">&quot;Safari&quot;</span> <span style="color: #ff0033; font-weight: bold;">to</span> <span style="color: #0066ff;">open</span> location toCrawl
	delay <span style="color: #000000;">15</span>
	<span style="color: #ff0033; font-weight: bold;">tell</span> <span style="color: #0066ff;">application</span> <span style="color: #009900;">&quot;Safari&quot;</span>
		<span style="color: #ff0033; font-weight: bold;">set</span> sourceCode <span style="color: #ff0033; font-weight: bold;">to</span> <span style="color: #ff0033;">the</span> source <span style="color: #ff0033; font-weight: bold;">of</span> <span style="color: #ff0033;">front</span> <span style="color: #0066ff;">document</span> <span style="color: #ff0033;">as</span> <span style="color: #0066ff;">string</span>
		<span style="color: #ff0033; font-weight: bold;">set</span> dataFile <span style="color: #ff0033; font-weight: bold;">to</span> <span style="color: #0066ff;">open</span> for access <span style="color: #0066ff;">file</span> dataFilePath <span style="color: #ff0033; font-weight: bold;">with</span> write permission
		write sourceCode <span style="color: #ff0033; font-weight: bold;">to</span> dataFile starting at eof
		<span style="color: #0066ff;">close</span> access dataFile
	<span style="color: #ff0033; font-weight: bold;">end</span> <span style="color: #ff0033; font-weight: bold;">tell</span>
<span style="color: #ff0033; font-weight: bold;">end</span> <span style="color: #ff0033; font-weight: bold;">if</span></pre></div></div>

<p>This is the core piece of functionality that I was trying to achieve all in this block of 11 lines.  This script will open a URL in Safari, and then save the source code of the loaded page in a text file.  You&#8217;ll notice that the first and last lines are an <code>if</code> statement relating to the length of the <code>toCrawl</code> variable.  I don&#8217;t unlock achievements every 5 minutes of the day and so, more often than not, the <code>toCrawl</code> variable will be empty.  If it is, then we want to completely ignore the next section of code as there is no reason whatsoever to run it!</p>
<p>The next line is a one line <code>tell</code> to make Safari open the URL we saved which is then followed by a 15 second delay.  You&#8217;ll notice this is a lot longer than the 1 second delays earlier.  The reason for this is that, in the first case, it was a simple text file with around 100 characters in it and so loaded incredibly quickly.  This URL, conversely, will be a very large page (around the 100kb mark) that may go through a series of 5 redirects depending on how recently the page was accessed.  This is because after 15 minutes of inactivity, the site forces you back to the login page but I have a cookie stored that will then automatically log me back in.  It just takes a few seconds to go through the process of all the redirects to get to the actual page hence the long time delay.</p>
<p>The last section is a simple expansion of the code we used at the beginning.  We tell Safari to set a variable of <code>sourceCode</code> to be the source of the page that&#8217;s open &#8211; we also tell it to be forced as a string in case there are any casting issues.  Next, we open the <code>dataFilePath</code> and set a handler of <code>dataFile</code> so that we can then write the <code>sourceCode</code> variable into the file starting at the end of the file (which we all know is masquerading as the beginning of the file also as we set it earlier on&#8230; keep up!) before tidying up after ourselves and closing access to the handler.  Easy!</p>

<div class="wp_syntax"><div class="code"><pre class="applescript" style="font-family:monospace;"><span style="color: #ff0033; font-weight: bold;">tell</span> <span style="color: #0066ff;">application</span> <span style="color: #009900;">&quot;Safari&quot;</span> <span style="color: #ff0033; font-weight: bold;">to</span> <span style="color: #0066ff;">close</span> <span style="color: #ff0033;">every</span> <span style="color: #0066ff;">window</span></pre></div></div>

<p>In the very final line of code, we tell Safari to close all of it&#8217;s windows in preparation for the next iteration.  This may not seem terribly important, but trust me, if you neglect to put it in and then unlock 10 achievements, you&#8217;ll find your mac now has 20 open Safari windows!</p>
<h3>Conclusion</h3>
<p>So that&#8217;s all there is to this section &#8211; a large chunk of AppleScript and a rationale as to why I had to open Safari to get at a text document rather than doing it a slightly more simple way via FTP (due to massive caching issues).  I hope this post has introduced some of you to AppleScript which I have found to be a rather powerful tool when it comes to mac development.  It&#8217;s very easy to understand and is a great way of transitioning from a web-based language to a desktop-based one especially as you can save AppleScript as a standard mac application.</p>
<p>Join me again for part three of this four part series when I&#8217;ll be looking at this from the other angle; the PHP server that needs to parse the HTML we have gathered using this AppleScript.  To make sure you don&#8217;t miss a section, you can <a href="http://bendodson.com/blog/feed/">subscribe to the RSS Feed</a> or <a href="http://twitter.com/bendodson/">follow me on Twitter</a>.  Please feel free to leave any comments or suggestions on this page.</p>
]]></content:encoded>
			<wfw:commentRss>http://bendodson.com/2009/05/19/getting-xbox-live-achievements-data-part-2-the-applescript-solution/feed/</wfw:commentRss>
		<slash:comments>14</slash:comments>
		</item>
		<item>
		<title>Getting Xbox Live Achievements Data: Part 1 &#8211; The PHP Problems</title>
		<link>http://bendodson.com/2009/05/12/getting-xbox-live-achievements-with-php-part-1-the-problems/</link>
		<comments>http://bendodson.com/2009/05/12/getting-xbox-live-achievements-with-php-part-1-the-problems/#comments</comments>
		<pubDate>Tue, 12 May 2009 09:55:10 +0000</pubDate>
		<dc:creator>Ben Dodson</dc:creator>
				<category><![CDATA[Code]]></category>
		<category><![CDATA[achievements]]></category>
		<category><![CDATA[applescript]]></category>
		<category><![CDATA[curl]]></category>
		<category><![CDATA[gamerscore]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[screen scraping]]></category>
		<category><![CDATA[windows live]]></category>
		<category><![CDATA[xbox 360]]></category>

		<guid isPermaLink="false">http://bendodson.com/blog/?p=105</guid>
		<description><![CDATA[Every time I unlock an achievement on my Xbox 360, I really want to be able to display it on my website or ideally on my Facebook wall.  I thought with a bit of API work I'd be able to achieve this but it turns out that getting the Xbox Live data isn't as easy as I thought!  In this series, I'm going to show you the problems I encountered as well as the final (rather complex) workaround I'm creating in order to get it all to work!]]></description>
			<content:encoded><![CDATA[<p>Those of you with an Xbox 360 (or indeed some &#8220;Games for Windows&#8221; titles) will know all to well about the achievements system prevalent in every game.  For those that don&#8217;t know, every gamer has a profile which has a gamerscore.  This score goes up by completing certain tasks within each game as laid down by the developer.  This could be something you would do anyway such as &#8220;finish the game&#8221; or something random such as &#8220;destory 10 cars in 10 seconds&#8221;.  Every full game can give out 1000 gamerpoints (1250 with expansion packs) and an Xbox Arcade title can give out 200.  These points are somewhat of a geek badge of honour for most Xbox gamers who will try and do everything to get the full 1000 in each of their games &#8211; there are also those that want to increase the number as quickly as possible so you can find numerous guides online for the easiest way to get 1000 points (it seems Avatar is still the best way giving you the full 1000 with about 3 minutes of gameplay!)</p>
<p><span id="more-105"></span>When I was trying to compete with my ex-boss over the number of gamerpoints we each had (I lost by the way), I found that there was no public API from Microsoft to allow you to get at the Xbox Live data.  There was however an internal API and one Microsoft associate had set up a restful API so that you could publicly call the internal one.  This worked well enough for the basic site I put together to compare two gamerscores but I&#8217;ve been wanting to do more with the API for some time.</p>
<p>My overall idea is that I&#8217;ll be able to type in my userid and then have my server poll Xbox Live at a certain time and then update my Facebook Wall when I unlock new achievements.  The message would be something along the lines of &#8220;Ben just completed the &#8216;Fuzz Off&#8217; achievement in Banjo Kazooie: N&#038;B and earned 20G&#8221; and would have the correct 32&#215;32px image for the achievement.  I initially thought that this would be fairly easy but I was unfortunately very wrong!  In this series, I&#8217;m going to show you the problems I encountered as well as the final (rather complex) workaround I&#8217;m creating in order to get it all to work!  If you&#8217;ve got any questions, please leave a comment or <a href="http://bendodson.com/contact/">get in touch</a>.</p>
<h3>Attempt #1: Using the API</h3>
<p>When I first sat down to work on this project, my initial thoughts were &#8220;I can just reuse the public API I used for my gamerscore comparison site &#8211; there&#8217;s bound to be an achievement section in the returned data&#8221;.  After eagerly re-downloading all the code, I discovered that although there was some achievement data, it was nowhere near as detailed as the information that I would need.  The problem was that the API only shows you your recently played games and how many achievements you have unlocked in each one as well as the overall number of points you have earned for that game.  Theoretically, I could check the API every few minutes and compare the number of points with a local copy in order to work out when a new achievement had been unlocked but I&#8217;d only be able to say that an achievement had been unlocked in a certain game worth a number of points.  To make things even trickier, if I unlocked more than one achievement within the timeframe of the API check, then the results would be wrong (e.g. it might say I&#8217;d unlocked one achievement worth 45G when in fact I&#8217;d done two; one for 20G and one for 25G).  This would become even more complex if I unlocked an achievement, then switched games and unlocked one in that game before the API had been called.  In short, the public API, useful though it can be, was not going to work for this.</p>
<h3>Attempt #2: Screen Scraping</h3>
<p>So now we move to option two; screen scraping.  This is the process of getting the server to request pages from a website as if it were a browser and then just ripping the content out of the HTML.  It&#8217;s messier than an API as it relies on the websites HTML not changing and it&#8217;s also a lot more processor intensive (as you&#8217;re parsing an entire XHTML page &#8211; possibly marked up invalidly &#8211; rather than a nice small XML or JSON file).  I&#8217;ve done lots of screen scraping in the past, both for my <a href="http://tubeupdates.com/">Tube Updates API</a> and for the <a href="http://packrattools.com/tracker/">Packrat Market Tracker</a> (a tracking system for a Facebook game), so I didn&#8217;t think it would be too much hassle.  But then I hadn&#8217;t banked on Microsoft&#8230;</p>
<p>The first hurdle is that although my Xbox Live data is set to be shown publicly, you still have to be logged in with a Windows Live account to view it.  This is annoying because it means my script is going to have to log in to Windows Live in order to get the HTML of my achievements listings.  The second hurdle is that there is no single page listing my latest unlocked achievements &#8211; the main profile page shows my last played game (and it&#8217;s last unlocked achievements) but that&#8217;s no good as they are not in order and it might be that I&#8217;ve switched games after unlocking something so the last achievement on the profile page may not be the last achievement I&#8217;ve unlocked.  This isn&#8217;t such a big problem as there are pages for each game so I&#8217;ll just have to crawl each of my recently played games pages and get the achievements on each one but it&#8217;s slightly more hassle than having one page of latest achievements (as it means I have to make several requests thus increasing bandwidth and script run time).</p>
<h3>Logging In to Windows Live</h3>
<p>Generally, logging into a site is quite easy using cURL.  You need to work out where the form is posting to, put all of the data to be posted in an array, and then make a cURL request that sends that array to that URL.  You will also need to enable both a cookie file and a cookie jar (a basic text file that is used for all of the cookies during the requests) as you will probably only want to login once and then have each future request know that you are already logged in as this will save on overall requests per execution of the task.</p>
<p>The Windows Live login, on the other hand, is an entirely different kettle of fish!  The URL you are posting to changes on each request as do the variables that you are posting.  This means we need to make a request to the login page first of all and extract all of the data from the hidden input fields as well as the action attribute of the form.  We can then go about posting that data (along with our email address and password) to the URL we just extracted.  This POST goes through a HTTPS connection though, so we need to modify our cURL request further in order to ensure that SSL certificates are just accepted without question.  Our overall cURL request, with all of these options, will look roughly like this:</p>

<div class="wp_syntax"><div class="code"><pre class="php" style="font-family:monospace;"><span style="color: #000000; font-weight: bold;">&lt;?php</span>
<span style="color: #666666; font-style: italic;">// set up cURL request - the $url would be the action URL that you're POSTing to</span>
<span style="color: #000088;">$curl</span> <span style="color: #339933;">=</span> <span style="color: #990000;">curl_init</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$url</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
<span style="color: #666666; font-style: italic;">// make sure the script follows all redirects and sets each one as the referer of the next request</span>
<span style="color: #990000;">curl_setopt</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$curl</span><span style="color: #339933;">,</span> CURLOPT_AUTOREFERER<span style="color: #339933;">,</span> <span style="color: #009900; font-weight: bold;">true</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
<span style="color: #990000;">curl_setopt</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$curl</span><span style="color: #339933;">,</span> CURLOPT_RETURNTRANSFER<span style="color: #339933;">,</span> <span style="color: #009900; font-weight: bold;">true</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
<span style="color: #990000;">curl_setopt</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$curl</span><span style="color: #339933;">,</span> CURLOPT_FOLLOWLOCATION<span style="color: #339933;">,</span> <span style="color: #009900; font-weight: bold;">true</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
<span style="color: #990000;">curl_setopt</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$curl</span><span style="color: #339933;">,</span> CURLOPT_HEADER<span style="color: #339933;">,</span> <span style="color: #009900; font-weight: bold;">false</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
<span style="color: #666666; font-style: italic;">// ssl options - don't verify each certificate, just accept it</span>
<span style="color: #990000;">curl_setopt</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$curl</span><span style="color: #339933;">,</span> CURLOPT_SSL_VERIFYHOST<span style="color: #339933;">,</span> <span style="color: #009900; font-weight: bold;">false</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
<span style="color: #990000;">curl_setopt</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$curl</span><span style="color: #339933;">,</span> CURLOPT_SSL_VERIFYPEER<span style="color: #339933;">,</span> <span style="color: #009900; font-weight: bold;">false</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
<span style="color: #666666; font-style: italic;">// fake the user-agent so the site thinks we are a browser, in this case Safari 3.2.1</span>
<span style="color: #990000;">curl_setopt</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$curl</span><span style="color: #339933;">,</span> CURLOPT_USERAGENT<span style="color: #339933;">,</span> <span style="color: #0000ff;">'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_6; en-us) AppleWebKit/525.27.1 (KHTML, like Gecko) Version/3.2.1 Safari/525.27.1'</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
<span style="color: #666666; font-style: italic;">// tell cURL to use a text file for all cookies used in the request - $cookie should be a path to a txt file with 755 permissions</span>
<span style="color: #990000;">curl_setopt</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$curl</span><span style="color: #339933;">,</span> CURLOPT_COOKIEFILE<span style="color: #339933;">,</span> <span style="color: #000088;">$cookie</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
<span style="color: #990000;">curl_setopt</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$curl</span><span style="color: #339933;">,</span> CURLOPT_COOKIEJAR<span style="color: #339933;">,</span> <span style="color: #000088;">$cookie</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
<span style="color: #666666; font-style: italic;">// post options - the data that is going to be sent to the server.  $post should be an array with key=&gt;var pairs of each piece to be sent</span>
<span style="color: #b1b100;">foreach</span> <span style="color: #009900;">&#40;</span><span style="color: #000088;">$post</span> <span style="color: #b1b100;">as</span> <span style="color: #000088;">$key</span> <span style="color: #339933;">=&gt;</span> <span style="color: #000088;">$var</span><span style="color: #009900;">&#41;</span>
<span style="color: #009900;">&#123;</span>
	<span style="color: #000088;">$postfields</span> <span style="color: #339933;">.=</span> <span style="color: #000088;">$key</span> <span style="color: #339933;">.</span> <span style="color: #0000ff;">'='</span> <span style="color: #339933;">.</span> <span style="color: #990000;">urlencode</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$var</span><span style="color: #009900;">&#41;</span> <span style="color: #339933;">.</span> <span style="color: #0000ff;">'&amp;'</span><span style="color: #339933;">;</span>
<span style="color: #009900;">&#125;</span>
<span style="color: #990000;">curl_setopt</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$curl</span><span style="color: #339933;">,</span> CURLOPT_POST<span style="color: #339933;">,</span> <span style="color: #009900; font-weight: bold;">true</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
<span style="color: #990000;">curl_setopt</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$curl</span><span style="color: #339933;">,</span> CURLOPT_POSTFIELDS<span style="color: #339933;">,</span> <span style="color: #000088;">$postfields</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
<span style="color: #666666; font-style: italic;">// make the request and save the result as $response - then close the request</span>
<span style="color: #000088;">$response</span> <span style="color: #339933;">=</span> <span style="color: #990000;">curl_exec</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$curl</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
<span style="color: #990000;">curl_close</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$curl</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
<span style="color: #000000; font-weight: bold;">?&gt;</span></pre></div></div>

<p>I had thought that this would be the end of it and that the returned data would be the first page after logging into Windows Live.  Instead, I got nothing.  Absolutely nothing.  No matter what settings I tinkered with or parts of the code I changed, it was just returning blank.  It was then that I noticed the rather unpleasant JavaScript files on the page and some suspicious &lt;noscript&gt; code at the top of the page.  If you load the login page without JavaScript in a normal browser, then the code in the &lt;noscript&gt; section gets read which has a meta redirect to send you to a page telling you that you must have JavaScript enabled!  I hadn&#8217;t noticed this previously as my cURL request doesn&#8217;t understand HTML, it was just returning it as a big lump so I was able to get all of the variables, etc, out without being redirected as I would be in a normal browser.</p>
<p>I didn&#8217;t think too much of this as obviously the page worked without JavaScript &#8211; it must just be a rudimentary way to make people upgrade their browser (although it didn&#8217;t actually give you any advice &#8211; very bad usability!).  But no, the login does require JavaScript as when you submit the form a huge amount of obfuscated code does some crazy code-fu to the POST request and encrypts it all before sending thus making JavaScript a requirement to log in to Windows Live.  To my mind, this has obviously been done to prevent people from screen scraping their sites such as Hotmail but it really is a pain!</p>
<h3>The AppleScript Idea</h3>
<p>It was about 3am by the time I&#8217;d realised that screen scraping wasn&#8217;t going to work and I&#8217;d been playing with the code for around 5-6 hours so was pretty annoyed with it.  So today I sat down and listed all of the obstacles so I could work out a way round them:</p>
<ul>
<li>The data from the API wasn&#8217;t good enough so couldn&#8217;t be used</li>
<li>Although I could screen scrape the Xbox Live profile page / game pages, I couldn&#8217;t get to them as needed to be logged in to Windows Live</li>
<li>I couldn&#8217;t log in to Windows Live without JavaScript</li>
</ul>
<p>After writing this down and having a think, I realised that I have a static IP address and a mac mini which is always turned on and connected to the internet.  I also realised that all my server needed to parse the Xbox Live pages was the HTML itself &#8211; it didn&#8217;t necessarily have to come from a cURL request or even from my server.  After this &#8216;mini&#8217; enlightenment I set about writing a plan that would allow me to get around the Windows Live login using a combination of a server running some PHP and cURL requests and a mac mini running some AppleScript.  It will work roughly like this&#8230;</p>
<p>The server will store a record of all of my game achievements in a MySQL database.  It will therefore know my gamerscore and be able to compare it to the gamerscore found using the API.  Every five minutes it will check this and if it notices a difference in the numbers, it will know that I have earned an achievement and thus needs the HTML that alluded me yesterday.  It knows the URL it needs so it will log this in a text file on the server that will be publicly available via a URL.</p>
<p>Meanwhile, the Mac Mini will use AppleScript to check the URL list on the server every five minutes.  If it finds a URL, it knows that the server needs some HTML so it will oblige by loading the URL in Safari (which will be set to be permanently logged in to Windows Live thanks to authenticating and choosing &#8220;save my email address and password&#8221; which stores a cookie) and then getting the source of the page and dumping it in a text file on the Mac Mini.</p>
<p>The text file on the Mac Mini (with the HTML we need) will be available to my server thanks to my Static IP and so when the next CRON job on the server runs, it will see that it wanted some HTML (based on their being some URLs in its own text file) and so will check the text file on the Mac Mini and thus get the HTML it needs.  It can then parse this, work out the new achievements and log them in the database accordingly.  It will then clear the URL list (so that the mac mini doesn&#8217;t try and do an update when it doesn&#8217;t need to) and then continue on it&#8217;s cycle by checking if the gamerscore is equal to the (now updated) database.</p>
<h3>The Next Step</h3>
<p>So, after a failed evenings development, I have now come up with a solid plan to get around several key hurdles.  I&#8217;ll be posting part two of this series shortly once I have built the application and it will have all of the source code included for those of you that want to replicate a similar system.  In the mean time, I hope this post will show you that problems do pop up in application development and that they can be resolved easily by writing out a list of each hurdle before formulating a plan to get around them.</p>
<p><strong>Update:</strong> Part two of this tutorial is <a href="http://bendodson.com/blog/2009/05/19/getting-xbox-live-achievements-data-part-2-the-applescript-solution/">now available</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://bendodson.com/2009/05/12/getting-xbox-live-achievements-with-php-part-1-the-problems/feed/</wfw:commentRss>
		<slash:comments>9</slash:comments>
		</item>
		<item>
		<title>How to control a Mac Mini from your iPhone including waking, sleeping, and audio / video</title>
		<link>http://bendodson.com/2008/09/08/control-mac-mini-from-iphone-with-waking-sleeping-audio-video/</link>
		<comments>http://bendodson.com/2008/09/08/control-mac-mini-from-iphone-with-waking-sleeping-audio-video/#comments</comments>
		<pubDate>Mon, 08 Sep 2008 15:36:51 +0000</pubDate>
		<dc:creator>Ben Dodson</dc:creator>
				<category><![CDATA[Code]]></category>
		<category><![CDATA[airport express]]></category>
		<category><![CDATA[iPhone]]></category>
		<category><![CDATA[iphone 3g]]></category>
		<category><![CDATA[mac mini]]></category>
		<category><![CDATA[sleep]]></category>
		<category><![CDATA[vnc]]></category>
		<category><![CDATA[wake on lan]]></category>

		<guid isPermaLink="false">http://bendodson.com/blog/?p=42</guid>
		<description><![CDATA[After cleaning through my technology cupboard, I realised I could create an entertainment system controlled by my iPhone 3G from a Mac Mini and some Apple airport accessories.  I'll show you how in this tutorial!]]></description>
			<content:encoded><![CDATA[<p>I was recently cleaning through the &#8220;technology cupboard&#8221; in my flat (every geek has one &#8211; it&#8217;s a place where all the useless electronics we&#8217;ve collected over the years live) when I came across an idea for creating a home entertainment system for my bedroom.  The main driver for this was a forgotten Mac Mini (512MB of RAM and an old G4 processor) which I realised could hold all of my music on its 40GB hard drive and send it wirelessly to all of my other equipment.</p>
<p><span id="more-42"></span>
<p>The problem I have is that at present I have 3 computers at home and 1 at work (plus my iPhone 3G) which all have a complete copy of all of my music files (around 20GB &#8211; obviously the iPhone doesn&#8217;t have all of that as it doesn&#8217;t fit but that&#8217;s a separate issue) &#8211; the downside to this is that when I update one machine the others are then out of sync.  My original plan was to have the mac mini hold everything and then the other computers would use it&#8217;s hard drive over my network as the master copy.  That way if I added music to the mac mini, the others would all be in sync.  I have since completely given up on this idea as I needed to a) access music on my laptop all the time and b) access music on my work iMac without having to leave everything on at home and broadcast through a static IP (which would also have been a little slow especially when I skip through lots of tracks &#8211; I&#8217;m very picky at what I listen to).</p>
<p>However, since I started writing this article there have been <a href="http://forums.macrumors.com/showthread.php?t=557826" rel="external">rumours flying around</a> about the 9th September &#8220;Lets Rock&#8221; event being staged by Apple.  Several sources are touting that iTunes 8 is going to be released and will have an option to stream your music wirelessly to other machines (or an iPhone) through the internet.  Let&#8217;s hope this is true!</p>
<p>With the initial problem left alone for now, I decided to think on another problem I have.  If I want to listen to music in my bedroom, then I have to bring my laptop in and listen to it from there.  This isn&#8217;t a huge difficulty but the sound quality isn&#8217;t great and I don&#8217;t like using my laptop in my bedroom (mainly because I try to keep some separation between work / computer based activities from other things I enjoy like reading and writing &#8211; the ideal separation is a physical one so I don&#8217;t use my computer in my bedroom at all).  I could buy a cheap hi-fi system but then it wouldn&#8217;t be connected to my iTunes library.  Alternatively I could get an iPhone dock but they are quite expensive (due to the magnetic shielding) and also I can&#8217;t fit all my music on there so it wasn&#8217;t really an option.  At this point I&#8217;d like to point out that yes I could have used <a href="http://www.apple.com/uk/airportexpress/" rel="external">AirTunes</a> (plugging a hi-fi unit into the 3mm audio jack on an Airport Express in order to pick up a shared iTunes library) but that would require me leaving the laptop on in the other room.  Not a great hardship but I&#8217;d have to get out of bed to turn it off (which would be a hardship).</p>
<p>After a bit of thought and a look at the components I had, I worked out that I could put the mac mini under my bed with a wireless card and some speakers and then play music through it using the <a href="http://phobos.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=284417350&#038;mt=8" rel="external">Apple Remote application</a> available from the App Store for iPhone.  I could also install a VNC client so that I could control the mac mini remotely if necessary (in the case of copying music onto it, installing updates, etc).  Perfect.</p>
<p>I&#8217;m quite pedantic however and so the above solution wasn&#8217;t yet 100% perfect.  The main problem I had was that the mac mini would need to be on 24 hours a day when I&#8217;d only be using it for around an hour a day at most. This doesn&#8217;t bother me from an environmental perspective (I&#8217;m not into &#8220;green&#8221;) but did bother me from the point of view that I&#8217;d be paying for electricity I wasn&#8217;t using (around 45-50W an hour I believe which would be around 16p a day &#8211; that works out at nearly &pound;60 a year for nothing!) and that the components would be wearing down from overuse.  It would probably become a little unstable as well and I&#8217;d worry it would burst into flames or something!  So, the mac mini needed to be put to sleep and needed to be woken up.  I could do this by pressing the power button but it&#8217;s under my bed and I can&#8217;t be bothered with the movement involved.  Thus a solution had to be created that would let me wake it and put it to sleep remotely with my iPhone being the obvious candidate for this as it was already choosing the songs being played.</p>
<p><strong>Challenge One &#8211; Make the mac go to sleep remotely</strong></p>
<p>Waking up a machine is easy with Wake-on-LAN (as we&#8217;ll see shortly) but there seems to be no easy way to put one to sleep.  My initial ideas of having an inactivity timer were quickly discarded as I realised that either the machine wouldn&#8217;t go to sleep if music was left playing or it might go to sleep too quickly.  A better solution was needed and I eventually came across a terminal command which will make your mac sleep:</p>

<div class="wp_syntax"><div class="code"><pre class="applescript" style="font-family:monospace;">osascript <span style="color: #000000;">-</span>e <span style="color: #ff0000;">'tell application &quot;System Events&quot; to sleep'</span>;</pre></div></div>

<p>You can try the above in Terminal and watch in awe as your mac succumbs to tiredness.  So this is all well and good but can only be run on the machine which you want to put to sleep; it can&#8217;t be done remotely.</p>
<p>A solution to the problem comes in the form of the Apache2 web server that comes installed by default on all OS X Leopard installations.  If I could set the mac mini up as a server, knock up a bit of PHP to pass the sleep command directly to the machine, and then broadcast the IP so that the command could be run via Safari on the iPhone, then I would be on to a winner!  This is exactly what I ended up doing and the instructions are below for your delectation.</p>
<p>First of all we need to activate the Apache2 web server in Leopard.  To do this, go to &#8220;System Preferences&#8221; and then to the &#8220;Sharing&#8221; icon.  If you tick the &#8220;Web Sharing&#8221; checkbox, then the server will come to life and will be enabled whenever you start the machine.  You can check this has worked by going to <a href="http://localhost/" rel="external">http://localhost/</a> on the machine and checking you get an apache default installation message.  Now this is done, we need to set our Computer Name for easy access to this page from anywhere on our home network.  The setting for this is also in the &#8220;Sharing&#8221; control panel and so I set mine to mini.  This allows the web server to be reached by any computer on the network by going to <a href="http://mini.local/" rel="external">http://mini.local/</a> &#8211; try it yourself by changing it to whatever name you want (the URL to access your computer will be shown on the same screen).</p>
<p>Now that we can connect to the web server, we need to get PHP up and running and write our script.  PHP 5 doesn&#8217;t come enabled by default so we have to do this by opening up terminal and typing the code below.  I am assuming at this point that you have the excellent <a href="http://macromates.com/" rel="external">TextMate</a> installed which is accessed from the &#8216;mate&#8217; command in Terminal.  If you don&#8217;t have it, either install it or substitute &#8216;mate&#8217; for some other text editor command like &#8216;pico&#8217; or &#8216;vi&#8217;.</p>

<div class="wp_syntax"><div class="code"><pre class="bash" style="font-family:monospace;"><span style="color: #c20cb9; font-weight: bold;">sudo</span> mate <span style="color: #000000; font-weight: bold;">/</span>etc<span style="color: #000000; font-weight: bold;">/</span>apache2<span style="color: #000000; font-weight: bold;">/</span>httpd.conf</pre></div></div>

<p>You may need to type in your administrative password if prompted.  Now you have the Apache2 configuration file open, scroll down to somewhere around line 114 where you will find the line:</p>

<div class="wp_syntax"><div class="code"><pre class="bash" style="font-family:monospace;"><span style="color: #666666; font-style: italic;"># LoadModule php5_module libexec/apache2/libphp5.so</span></pre></div></div>

<p>You&#8217;ll need to uncomment this (by removing the proceeding hash) and then save the file.  Once this is done, you&#8217;ll need to restart Apache in order for your changes to be made available.  You do this with the command:</p>

<div class="wp_syntax"><div class="code"><pre class="bash" style="font-family:monospace;"><span style="color: #c20cb9; font-weight: bold;">sudo</span> apachectl graceful</pre></div></div>

<p>You can use &#8216;restart&#8217; in place of &#8216;graceful&#8217; to do a full restart, but a graceful restart won&#8217;t kick off anyone that is currently using your server.  This isn&#8217;t going to matter here but it&#8217;s a good habit to get into in case you ever need to restart an apache server in the future.</p>
<p>You now have PHP5 installed and ready to go so lets write the PHP script that is going to power our sleep command.  You&#8217;ll need to go to your Apache DocumentRoot (which by default is in a folder called Sites in your home folder) and delete any files that are in there.  Now create a new file called &#8220;index.php&#8221; with the following code in it:</p>

<div class="wp_syntax"><table><tr><td class="line_numbers"><pre>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
</pre></td><td class="code"><pre class="php" style="font-family:monospace;">&lt;!DOCTYPE html PUBLIC &quot;-//W3C//DTD XHTML 1.0 Strict//EN&quot;
    &quot;http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd&quot;&gt;
&lt;html&gt;
&lt;head&gt;
    &lt;meta http-equiv=&quot;Content-type&quot; content=&quot;text/html; charset=utf-8&quot;&gt;
    &lt;title&gt;Mac Mini Sleeping App&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;h1&gt;Goodnight...&lt;/h1&gt;
&lt;/body&gt;
&lt;/html&gt;
<span style="color: #000000; font-weight: bold;">&lt;?php</span>
<span style="color: #990000;">exec</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'./sleep.scpt'</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
<span style="color: #990000;">exit</span><span style="color: #339933;">;</span>
<span style="color: #000000; font-weight: bold;">?&gt;</span></pre></td></tr></table></div>

<p>All this code is doing is outputting a message that says &#8220;goodnight&#8221; and then executes an applescript file (in this case sleep.scpt) which contains the code from earlier:</p>

<div class="wp_syntax"><div class="code"><pre class="applescript" style="font-family:monospace;">osascript <span style="color: #000000;">-</span>e <span style="color: #ff0000;">'tell application &quot;System Events&quot; to sleep'</span></pre></div></div>

<p>I find it&#8217;s easier to include a file in this way rather than typing the command into a function such as <a href="http://uk3.php.net/passthru" rel="external">passthru()</a> as it allows for easier control over quotes, etc.  The <a href="http://uk3.php.net/manual/en/function.exec.php" rel="external">exec()</a> command used in the above is pretty much telling PHP to type the command we used earlier into Terminal.  Unfortunately this won&#8217;t actually do anything at present (at least it shouldn&#8217;t do) as the Apache webserver is not authorised by default to perform such important system commands as are available through exec().  To do this we need to go back to the Apache2 config file and set the user and group of the web server to be the same as that of the user of the machine.  Now this is a calculated security risk as it means any scripts on the server can have full access to your machine and thus compromise it.  However, this is for personal home use and no-one will be able to access it unless they are on your network let alone run scripts on it so I think it&#8217;s ok.  Let&#8217;s set the permissions by opening up the config file:</p>

<div class="wp_syntax"><div class="code"><pre class="bash" style="font-family:monospace;"><span style="color: #c20cb9; font-weight: bold;">sudo</span> mate <span style="color: #000000; font-weight: bold;">/</span>etc<span style="color: #000000; font-weight: bold;">/</span>apache2<span style="color: #000000; font-weight: bold;">/</span>httpd.conf</pre></div></div>

<p>Now go to around line 126 and amend the User to your own username and Group to &#8217;staff&#8217;.  In my case this was:</p>

<div class="wp_syntax"><div class="code"><pre class="bash" style="font-family:monospace;">User ben
Group staff</pre></div></div>

<p>Yours will differ (unless you&#8217;re called ben) but you can find out what yours is by going to your home folder &#8211; the name of the folder is the name of your user.</p>
<p>Now restart the apache server (remember our graceful command from earlier?) and try out your site.  You should find that your mac goes fast to sleep.  Success!  Simply go to the URL with safari on your iPhone, tap the &#8216;+&#8217; icon, and then choose &#8220;save to home screen&#8221; in order to set it up as a web app.  If you want a fancy icon, then stick a square PNG named &#8220;apple-touch-icon.png&#8221; in the same folder as the index.php file and you&#8217;ll notice that it appears on your home screen when you bookmark the page.</p>
<p><strong>Challenge Two &#8211; Wake the mac up</strong></p>
<p>Ok, you now have a sleeping mac that you want to wake up.  Most computers have a little feature built in to them called Wake-on-LAN.  The idea is that when the computer is asleep, the ethernet port is actually still active and listening for data.  If a certain command is sent (referred to as a &#8220;magic packet&#8221;) then the ethernet card will tell the rest of the computer to wake up.  This is exactly what we need but unfortunately will not work over a wireless connection (as a wireless card doesn&#8217;t stay awake when the computer is asleep).  If you have connected your mac to an ethernet connection then you can ignore the next few steps, but for me a long cable wasn&#8217;t really an option.  I therefore came across a solution that would allow me to pretend I was on ethernet; wireless bridging.</p>
<p>The idea behind wireless bridging is fairly simple.  Rather than having an ethernet cable from your router to your computer, you instead have 2 wireless routers that are connected via ethernet to each machine (or modem) which then act like an ethernet link between the two.  Now I already had an <a href="http://www.apple.com/uk/airportextreme/" rel="external">Airport Extreme</a> which was broadcasting my network / internet wirelessly and so all I needed was another router on the other end.  After a bit of headscratching with a BT router that was lying around, I decided the best way to proceed was to pop to the Apple Store and buy an <a href="http://www.apple.com/uk/airportexpress/" rel="external">Airport Express</a>.  The Airport Express is plugged into a standard power socket and then broadcasts a wireless signal.  On it&#8217;s base it has 3 inputs; ethernet, USB, and 3mm Audio.  Usually the Airport Express is connected to a wired modem via ethernet so it can quickly and easily broadcast internet to the rest of your house however you can also plug in a printer to share that to wireless devices or plug in a standard hi-fi unit in order to utilise the AirTunes feature I mentioned earlier.</p>
<p>I instead used it&#8217;s network bridging service in order to connect it to the mac mini via ethernet and then extend the wireless network created by my Airport Extreme.  This is fairly easy to do from the Airport Utility &#8211; I won&#8217;t go into the exact process here as it is mimicked in <a href="http://www.macosxhints.com/article.php?story=20040927125820995" rel="external">several other places</a>, but the end result that the Airport Express connects to the wireless network created by the Airport Extreme and sends this to the mac mini via ethernet.  This means that we can now send a Wake-on-LAN command as both the Airport Extreme and Airport Express are &#8220;always on&#8221; allowing the packet to go through the bridge, down the ethernet cable, and straight into the ethernet port.  Simple!</p>
<p>Now we just need to find a way to send the magic packet from the iPhone.  The application <a href="http://phobos.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=286568674&#038;mt=8" rel="external">iWOL</a> allows us to do this easily and has a very quick setup.  Simply type in a name to reference the machine and the MAC address of the ethernet card (this can be found by going to &#8220;System Preferences&#8221;->&#8221;Network&#8221; and selecting your ethernet card.  Now click on &#8220;advanced&#8221; and then on the &#8220;Ethernet&#8221; tab.  Your MAC Address is the &#8220;Ethernet ID&#8221;).  Now enable &#8220;LAN Broadcast&#8221; mode and you should be good to go.  Once your computer is asleep, open up iWol and you should be able to wake it up at the press of a button.</p>
<p><strong>Further Enhancements</strong></p>
<p>At present, my setup is working in the same way as detailed in the steps above.  I am in the process of making the sleeping web app slightly tidier with a fancy interface and icon, etc, and this will be available for download from my site shortly &#8211; the working title is &#8220;Rohypnol&#8221;&hellip;</p>
<p>In addition to the above steps, I have installed a freeware application called <a href="http://www.robbiehanson.com/alarmclock/index.html" rel="external">Alarm Clock 2</a> on the mac mini which allows me to use it as an alarm clock.  Various options are available including a &#8220;wake from sleep&#8221; mode which is perfect for this project.  I now have a special playlist on my mac mini which starts off quietly and over 5 minutes gently increases in volume.  Nothing says wake up in the morning like <a href="http://www.youtube.com/watch?v=oHg5SJYRHA0" rel="external">Rick Rollin&#8217;</a> music&hellip;</p>
<p>In order to control the mac mini better, I also installed a freeware VNC server called <a href="http://sourceforge.net/projects/osxvnc/" rel="external">Vine Server</a> that allows me to control the machine remotely. I won&#8217;t go into the finer details of VNC setup as this has also been covered in detail <a href="http://www.xawk.com/apple-htpc-step01-setup-and-vnc.html" rel="external">elsewhere on the net</a>.  I will however mention the excellent <a href="http://phobos.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=284984448&amp;mt=8" rel="external">VNC Lite</a> app for iPhone which allows you to access VNC controlled machines.  It&#8217;s easy to use and very intuitive &#8211; it&#8217;s also free!</p>
<p>In the future I plan to make some improvements to this system by plugging in a monitor which can then sit on the end of the bed allowing me to watch movies and tv shows downloaded through iTunes as well as DVDs etc but this is an upgrade for another day.  For now I&#8217;m quite happy with the setup which was achieved relatively easily and cheaply as I had all the components to hand.  I probably wouldn&#8217;t recommend it if you were going to buy all the parts as the whole system would cost about &pound;500 but as a small project it&#8217;s worked very well.  Now I just have to wait to see what iTunes 8 will add to this setup&hellip;</p>
<p>If you have any questions or comments, then please use the comments box below or <a href="http://bendodson.com/contact/">contact me</a>!</p>
]]></content:encoded>
			<wfw:commentRss>http://bendodson.com/2008/09/08/control-mac-mini-from-iphone-with-waking-sleeping-audio-video/feed/</wfw:commentRss>
		<slash:comments>14</slash:comments>
		</item>
		<item>
		<title>Google extended encoding made easy!</title>
		<link>http://bendodson.com/2008/02/28/google-extended-encoding-made-easy/</link>
		<comments>http://bendodson.com/2008/02/28/google-extended-encoding-made-easy/#comments</comments>
		<pubDate>Thu, 28 Feb 2008 15:13:36 +0000</pubDate>
		<dc:creator>Ben Dodson</dc:creator>
				<category><![CDATA[Code]]></category>
		<category><![CDATA[extended encoding]]></category>
		<category><![CDATA[google charts]]></category>
		<category><![CDATA[php]]></category>

		<guid isPermaLink="false">http://bendodson.com/blog/?p=18</guid>
		<description><![CDATA[I&#8217;ve been having a play around with Google Charts recently but came across a problem with the range of values that can be used. With simple encoding, you can only have values between 0 and 64, and with text encoding only values between 0 to 100.  This is annoying when dealing with things such [...]]]></description>
			<content:encoded><![CDATA[<p>I&#8217;ve been having a play around with <a href="http://code.google.com/apis/chart" rel="external">Google Charts</a> recently but came across a problem with the range of values that can be used. With <a href="http://code.google.com/apis/chart/#simple" rel="external">simple encoding</a>, you can only have values between 0 and 64, and with <a href="http://code.google.com/apis/chart/#text" rel="external">text encoding</a> only values between 0 to 100.  This is annoying when dealing with things such as tracking website hits or weight when values are typically much higher.</p>
<p><span id="more-18"></span>
<p>I played around with some functions to try and factorise the numbers so that they would be in the range of 0 to 100 but this didn&#8217;t go so well so I decided to tackle Google&#8217;s <a href="http://code.google.com/apis/chart/#extended" rel="external">extended encoding</a>.</p>
<p>This is a system of encoding that basically takes a pair of alphanumeric characters and translates them into a number between 0 and 4095 (much better!).  So for example, B9 translates to 125 and .a would translate to 4058.  However, although this has a much larger range, it&#8217;s a bit harder to get your head around and so what is needed is a simple function (or two) to convert to and from the extended encoding.  Predictably, I have said PHP functions here:</p>

<div class="wp_syntax"><table><tr><td class="line_numbers"><pre>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
</pre></td><td class="code"><pre class="php" style="font-family:monospace;"><span style="color: #000000; font-weight: bold;">&lt;?php</span>
&nbsp;
<span style="color: #000000; font-weight: bold;">function</span> array_to_extended_encoding<span style="color: #009900;">&#40;</span><span style="color: #000088;">$array</span><span style="color: #009900;">&#41;</span>
<span style="color: #009900;">&#123;</span>
    <span style="color: #000088;">$characters</span> <span style="color: #339933;">=</span> <span style="color: #0000ff;">'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-.'</span><span style="color: #339933;">;</span>
&nbsp;
    <span style="color: #000088;">$encoding</span> <span style="color: #339933;">=</span> <span style="color: #0000ff;">''</span><span style="color: #339933;">;</span>
    <span style="color: #b1b100;">foreach</span> <span style="color: #009900;">&#40;</span><span style="color: #000088;">$array</span> <span style="color: #b1b100;">as</span> <span style="color: #000088;">$value</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
        <span style="color: #000088;">$first</span> <span style="color: #339933;">=</span> <span style="color: #990000;">floor</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$value</span> <span style="color: #339933;">/</span> <span style="color: #cc66cc;">64</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
        <span style="color: #000088;">$second</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$value</span> <span style="color: #339933;">%</span> <span style="color: #cc66cc;">64</span><span style="color: #339933;">;</span>
        <span style="color: #000088;">$encoding</span> <span style="color: #339933;">.=</span> <span style="color: #000088;">$characters</span><span style="color: #009900;">&#91;</span><span style="color: #000088;">$first</span><span style="color: #009900;">&#93;</span> <span style="color: #339933;">.</span> <span style="color: #000088;">$characters</span><span style="color: #009900;">&#91;</span><span style="color: #000088;">$second</span><span style="color: #009900;">&#93;</span><span style="color: #339933;">;</span>
    <span style="color: #009900;">&#125;</span>
&nbsp;
    <span style="color: #b1b100;">return</span> <span style="color: #000088;">$encoding</span><span style="color: #339933;">;</span>
<span style="color: #009900;">&#125;</span>
&nbsp;
<span style="color: #000000; font-weight: bold;">?&gt;</span></pre></td></tr></table></div>


<div class="wp_syntax"><table><tr><td class="line_numbers"><pre>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
</pre></td><td class="code"><pre class="php" style="font-family:monospace;"><span style="color: #000000; font-weight: bold;">&lt;?php</span>
&nbsp;
<span style="color: #000000; font-weight: bold;">function</span> extended_encoding_to_array<span style="color: #009900;">&#40;</span><span style="color: #000088;">$string</span><span style="color: #009900;">&#41;</span>
<span style="color: #009900;">&#123;</span>
    <span style="color: #000088;">$characters</span> <span style="color: #339933;">=</span> <span style="color: #0000ff;">'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-.'</span><span style="color: #339933;">;</span>  
&nbsp;
    <span style="color: #b1b100;">for</span> <span style="color: #009900;">&#40;</span><span style="color: #000088;">$i</span><span style="color: #339933;">=</span><span style="color: #cc66cc;">0</span><span style="color: #339933;">;</span><span style="color: #000088;">$i</span><span style="color: #339933;">&lt;</span>strlen<span style="color: #009900;">&#40;</span><span style="color: #000088;">$characters</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span><span style="color: #000088;">$i</span><span style="color: #339933;">++</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
        <span style="color: #000088;">$first</span><span style="color: #009900;">&#91;</span><span style="color: #000088;">$characters</span><span style="color: #009900;">&#91;</span><span style="color: #000088;">$i</span><span style="color: #009900;">&#93;</span><span style="color: #009900;">&#93;</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$i</span> <span style="color: #339933;">*</span> <span style="color: #cc66cc;">64</span><span style="color: #339933;">;</span>
    <span style="color: #009900;">&#125;</span>
&nbsp;
    <span style="color: #b1b100;">for</span> <span style="color: #009900;">&#40;</span><span style="color: #000088;">$i</span><span style="color: #339933;">=</span><span style="color: #cc66cc;">0</span><span style="color: #339933;">;</span><span style="color: #000088;">$i</span><span style="color: #339933;">&lt;</span>strlen<span style="color: #009900;">&#40;</span><span style="color: #000088;">$characters</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span><span style="color: #000088;">$i</span><span style="color: #339933;">++</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
        <span style="color: #000088;">$second</span><span style="color: #009900;">&#91;</span><span style="color: #000088;">$characters</span><span style="color: #009900;">&#91;</span><span style="color: #000088;">$i</span><span style="color: #009900;">&#93;</span><span style="color: #009900;">&#93;</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$i</span><span style="color: #339933;">;</span>
    <span style="color: #009900;">&#125;</span>
&nbsp;
    <span style="color: #000088;">$pairs</span> <span style="color: #339933;">=</span> <span style="color: #990000;">str_split</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$string</span><span style="color: #339933;">,</span><span style="color: #cc66cc;">2</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
    <span style="color: #b1b100;">foreach</span> <span style="color: #009900;">&#40;</span><span style="color: #000088;">$pairs</span> <span style="color: #b1b100;">as</span> <span style="color: #000088;">$pair</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
        <span style="color: #000088;">$value</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$first</span><span style="color: #009900;">&#91;</span><span style="color: #000088;">$pair</span><span style="color: #009900;">&#91;</span><span style="color: #cc66cc;">0</span><span style="color: #009900;">&#93;</span><span style="color: #009900;">&#93;</span><span style="color: #339933;">;</span>
        <span style="color: #000088;">$value</span> <span style="color: #339933;">+=</span> <span style="color: #000088;">$second</span><span style="color: #009900;">&#91;</span><span style="color: #000088;">$pair</span><span style="color: #009900;">&#91;</span><span style="color: #cc66cc;">1</span><span style="color: #009900;">&#93;</span><span style="color: #009900;">&#93;</span><span style="color: #339933;">;</span>
        <span style="color: #000088;">$values</span><span style="color: #009900;">&#91;</span><span style="color: #009900;">&#93;</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$value</span><span style="color: #339933;">;</span>
    <span style="color: #009900;">&#125;</span>
&nbsp;
    <span style="color: #b1b100;">return</span> <span style="color: #000088;">$values</span><span style="color: #339933;">;</span>
<span style="color: #009900;">&#125;</span>
&nbsp;
<span style="color: #000000; font-weight: bold;">?&gt;</span></pre></td></tr></table></div>

<p>They work fairly simply by taking advantage of PHP&#8217;s casting methods in that a string can be interpreted as an array.</p>
<p>In the first function, <code>array_to_extended_encoding()</code>, an array of numbers should be passed e.g. <code>array(250,39,400,1904,2)</code>.  The function first lists all of the extended encoding characters in order as one string named <code>$characters</code>.  It then loops through our array of numbers, and creates two variables; <code>$first</code> and <code>$second</code>.  In <code>$first</code>, we store the number from our array divided by 64 and rounded down using the <a href="http://www.php.net/floor" rel="external"><code>floor()</code></a> function.  For <code>$second</code>, we use the modulus operator (<code>%</code>) to find the remainder once the number from the array is divided by 64.  We then take both <code>$first</code> and <code>$second</code> and work out the encoding by looking up the numbers from within the <code>$characters</code> cast as an array.  This gives us 2 characters which make up the extended encoding for the number.  We keep extending the value of <code>$encoding</code> until we end up with a string representing the full extended encoding of the array passed to the function.</p>
<p>The second function, <code>extended_encoding_to_array()</code>, is slightly more advanced.  It accepts a string as its only parameter which should be an extended encoding (e.g. <code>$encoding = 'AA..B9aC'</code>).  We first list all of the characters in a string as we did for our previous function, but then we create two arrays which will contain all of the numbers we need to decode the extended encoding.  In <code>$first</code>, we create an array of each character in the <code>$characters</code> string as a number multiplied by 64 (so the letter b would be 64 as it is signified by number 1 in the array multiplied by 64).  Within <code>$second</code> we perform a similar operation but instead just assign the number of the character rather than multiplying it by 64.  This gives us two arrays, with keys relating to each character that can be used as an encoding and values as the numerical equivalent.  It is now a simple case of splitting the string that was passed to the function into chunks of 2 characters using <a href="http://www.php.net/str_split" rel="external"><code>str_split()</code></a> and looping through the returned array setting the value of each character within the pair from the arrays we created.  We then add the two returned numbers together to give us the decoded figure and add it to an array which will be returned by the function.</p>
<p>Simple?  It has certainly made things a lot easier for the functions I&#8217;m writing for a PHP wrapper for Google Charts and hopefully it will help somebody else as well.   If you have any questions or comments, please <a href="http://bendodson.com/contact/">get in touch</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://bendodson.com/2008/02/28/google-extended-encoding-made-easy/feed/</wfw:commentRss>
		<slash:comments>15</slash:comments>
		</item>
	</channel>
</rss>
