Twitterbox control.php

Code summary: 

This is the latest version of the server PHP script for use with the Twitterbox, which does all of the hard work in contacting Twitter.

<?php
/*
Twitterbox Intermediary Thing v0.41
Ordinal Malaprop
Last updated 2009-04-23 21:45

This script sits on a server somewhere and sends and receives data from Twitter. It is meant to be able to talk to an LSL script in a way that it can easily deal with.

Requires PHP 5.2 for the JSON-parsing functions.

Changes since 0.3:
- added timeouts for web services
- changed fail text to "no response from..."
- timestamp is now absolute rather than relative for logging purposes
- works on LA timezone
- allows for no feeds from client
- uses proper version parameter :)
- tinyurl function does not die completely if it can't get a tinyurl (not that I've ever seen this happen anyway)
- changed the SLURL caption to include the actual location SLURLed
- added SLPOS keyword to insert textual representation of SL location
- tries to detect type of feed before trying to parse it

I considered using the short-lived "since" parameter, but it appears that it encountered problems and is not being used in practice.

Version 0.41 ( 2009-04-23 21:45 )
- Twitter for some reason was demanding that friend item checks be GET rather than POST. Who am I to argue?

LICENCE

This code is licenced under a Creative Commons Attribution licence:
http://creativecommons.org/licenses/by/3.0/
Attribution in this case would probably mean a mention in the credits somewhere; it depends on what the application is really.

In addition, it may not be sold unmodified as is.

*/


define("VERSION", 0.4);

//-----------------------------------------------------------------------------
// Basic function to send a request to Twitter, using curl
function twitter_send($file, $user, $pass, $data) {
        global $since;
        // Construct a curl request and set all the right options
        $c = curl_init();
        curl_setopt($c, CURLOPT_URL, "http://twitter.com/statuses/$file.json");
        curl_setopt($c, CURLOPT_PATH, TRUE);
        curl_setopt($c, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
        curl_setopt($c, CURLOPT_USERPWD, "$user:$pass");
        if ($data) curl_setopt($c, CURLOPT_POSTFIELDS, $data);
        else curl_setopt($c, CURLOPT_HTTPGET, TRUE);
        curl_setopt($c, CURLOPT_RETURNTRANSFER, TRUE);
        curl_setopt($c, CURLOPT_HEADER, FALSE);
        // Don't wait too long for Twitter
        curl_setopt($c, CURLOPT_TIMEOUT, 30);
        // Not sure if this is being used, but include it anyway
        curl_setopt($c, CURLOPT_USERAGENT, "TwitterBox ". VERSION);
        // These are special proposed headers to identify the client to Twitter (from the Twitter Development Talk group)
        $headers = array(
                "X-Twitter-Client" => "Twitterbox",
                "X-Twitter-Client-URL" => "http://ordinalmalaprop.com/twitter/",
                "X-Twitter-Client-Version" => VERSION
        );
        curl_setopt($c, CURLOPT_HTTPHEADER, $headers);
        // Now send the response off
        $response = @curl_exec($c);
        if (chop($response)) {
                // We got a non-blank response, so try to parse it as JSON
                $json = json_decode($response, TRUE);
                curl_close($c);
//              print_r($json);
                // Return the parsed data
                return $json;
        }
        else die("No response from Twitter!");
}

// Fucntion to get the TinyURL of the first entry in an RSS feed
function feedlink_get($url) {
        $c = curl_init();
        curl_setopt($c, CURLOPT_URL, $url);
        curl_setopt($c, CURLOPT_RETURNTRANSFER, TRUE);
        curl_setopt($c, CURLOPT_HEADER, FALSE);
        curl_setopt($c, CURLOPT_TIMEOUT, 15);
        $feed_data = curl_exec($c);
        curl_close($c);
        if (!$feed_data) die("Failed to get stream at $url");
        else {
                // Try RSS parsing
                if (strpos($feed_data, 'rss') !== FALSE && preg_match('/<item>.+?<link>\s*?(\S.+?\S)\s*?<\/link>/is', $feed_data, $matches)) {
                        $feed_data = get_tinyurl($matches[1]);
                        return $feed_data;
                }
                // Try Atom parsing
                else if (strpos($feed_data, '<feed xmlns="http://www.w3.org/2005/Atom"') !== FALSE && preg_match('/<entry.+?>.+?<link.+?href="(.+?)".+?>/is', $feed_data, $matches)) {
                        $feed_data = get_tinyurl($matches[1]);
                        return $feed_data;
                }
                // Give up
                else {
                        die("Failed to find new feed link at $url");
                }
        }
}

// Function to get a tinyurl for something
// We probably don't need to use curl here, but, you know, why not?
function get_tinyurl($bigurl)
{
        $c = curl_init();
        curl_setopt($c, CURLOPT_URL, "http://tinyurl.com/api-create.php?url=$bigurl");
        curl_setopt($c, CURLOPT_RETURNTRANSFER, TRUE);
        curl_setopt($c, CURLOPT_HEADER, FALSE);
        // Don't wait more than 10 seconds for a TinyURL
        curl_setopt($c, CURLOPT_CONNECTTIMEOUT, 10);
        $tinyurl = curl_exec($c);
        curl_close($c);
        // If we got a tinyurl, return it
        if ($tinyurl) return $tinyurl;
        // otherwise just return the original one - Twitter will probably be able to tinyurl it
        else return $bigurl;
}

//-----------------------------------------------------------------------------
// Main code

// Set timezone to be closest to SL time (no San_Francisco timezone code in PHP?)
date_default_timezone_set("America/Los_Angeles");

header("Content-type: text/plain");

// Read POST input
$post = file('php://input');

// If there isn't anything in the POST, don't do anything
if (!$post) {
        die("go away");
}

// Read in the information that was sent
$user = urldecode(chop($post[0]));
$pass = urldecode(chop($post[1]));
$action = urldecode(chop($post[2]));
$status = urldecode(chop($post[3]));
// location to add slurl for, in the form simname/x/y/z/
$location = urldecode(chop($post[4]));
// Text representation of location
$loc_array = explode("/", $location);
$loc_text = $loc_array[0]. " ". $loc_array[1]. ",". $loc_array[2]. ",". $loc_array[3];
// Keywords and RSS feeds to check and replace
$feeds_raw = explode(",", urldecode(chop($post[5])));
$feeds = array();
$f = 0;
do {
        $feeds[$feeds_raw[$f]] = $feeds_raw[$f+1];
        $f += 2;
} while ($f < sizeof($feeds_raw));
// Unix time of last check - don't return anything before this
$since = urldecode(chop($post[6]));

ob_flush();
flush();

// Allow for slight clock irregularities
$now = time() - 5;

// Now process the action requested
// All of these request feedback from Twitter in JSON format, which is parsed by the above function.
if ($action == "update") {
        if (strlen($status) > 0) {
                // "Insert current position" keyword
                $status = str_replace("SLPOS", $loc_text, $status);
                // Go through each feed keyword checking to see if it occurs
                foreach ($feeds as $feed_name => $feed_url) {
                        // Do we have any keywords for that feed here?
                        if (@strpos($status, $feed_name) !== FALSE) {
                                // If so, get the tinyurl for the first <link> element there
                                $tinyurl = feedlink_get($feed_url);
                                // and replace instances of that keyword with it
                                $status = str_replace($feed_name, $tinyurl. " ", $status);
                        ob_flush();
                        flush();
                        }
                }
                if (strpos($status, "SLURL") !== FALSE && $location) {
                        $loc_array = explode("/", $location);
                        $loc_text = $loc_array[0]. " ". $loc_array[1]. ",". $loc_array[2]. ",". $loc_array[3];
                        // SLurl page strips out all HTML in the msg - didn't know that...
                        $slurl = "http://slurl.com/secondlife/". str_replace(" ", "%20", $location). "?x=200&y=260&title=". urlencode(gmdate("j M y @ H:i", time() - 28800). " SLT"). "&msg=". rawurlencode(htmlentities(str_replace("SLURL", $loc_text, $status))). "&img=http%3A//ordinalmalaprop.com/twitter/twitterbox-credit.jpg";
                        $tiny_slurl = get_tinyurl($slurl);
                        $status = str_replace("SLURL", $tiny_slurl, $status);
                }
                ob_flush();
                flush();
        }
        $json = twitter_send('update', $user, $pass, "status=".  rawurlencode($status));
        // The update procedure
        // Check the time that the last update was posted at
        $created = strtotime($json["created_at"]);
        // If the time is before now, that means it's failed!
        // 2007-07-01 - this doesn't seem to work any more, assume it has succeeded
//      if ($created < $now) {
//              die("Date was w");
//      }
        // Otherwise say "ok" and coincidentally update the screen name and ID
        if (sizeof($json, COUNT_RECURSIVE) <= 1) die("Twitter gave me bad output - " + print_r($json, TRUE));
        else {
                echo "OK,". $json["user"]["id"]. ",". $json["user"]["screen_name"]. ",posted";
        }
}
else if ($action == "get id" && ($json = twitter_send('update', $user, $pass, ""))) {
        // To just update the screen name and ID, send a blank update which will not be displayed
        // and read in the information.
        if (sizeof($json, COUNT_RECURSIVE) <= 1) die("Twitter gave me bad output - " + print_r($json, TRUE));
        else echo "OK,". $json["user"]["id"]. ",". $json["user"]["screen_name"];
}
else if ($action == "check" && ($json = twitter_send('friends_timeline', $user, $pass, ""))) {
        // Check recent tweets
        // Go through them and compile a list of the last few, up to an appropriate size limit to send back to LSL
        // 1500 bytes should work - longer than that and the header gets in the way
        // LSL doesn't like receiving any real quantity of data
        $length = 0;
        $entry = "";
        $f = 0;
        // Sometimes, Twitter doesn't seem to produce proper output, even if it gets through
        if (sizeof($json, COUNT_RECURSIVE) <= 1) die("Twitter gave me bad output");
        $max = sizeof($json);
        $tweets = array();
        do {
                $length += strlen($entry);
                if ($entry != "") $tweets[] = $entry;
                // Format for each tweet:
                //      <user's screen name>
                //      <update text> (<SLT timestamp>)
                //      <UNIX time of creation, to see if it's new>
                $unixtime = strtotime($json[$f]["created_at"]);
                $entry = $json[$f]["user"]["screen_name"]. "\n". $json[$f]["text"]. " (". date("j M H:i", $unixtime). " SLT)\n$unixtime";
                ob_flush();
                flush();
        } while (++$f < $max && $length + strlen($entry) < 1500);
        // Put them in chronological order
        $tweets = array_reverse($tweets);
        // and output
        echo "OK\n". implode("\n", $tweets);
}
else echo "pardon?";

?>