Touch the firehose of ds106, the most recent flow of content from all of the blogs syndicated into ds106. As of right now, there have been 92731 posts brought in here going back to December 2010. If you want to be part of the flow, first learn more about ds106. Then, if you are truly ready and up to the task of creating web art, sign up and start doing it.

All about the @ds106radio twitterbot

Posted by
|

Earlier this week, as the the ds106radio-mania was in full frenzy, I tweeted the following idea to #ds106 (which Jim promptly retweeted to the masses):

Who can program it? Well actually Jim, now that you ask, I can program it. It seemed like a fun little project, so I decided to see how far I could get with it.

From a big picture perspective, the way I wanted the twitterbot to work was to have a little script running on my server (personal cyberinfrastructure…) that would periodically check what song ds106radio was playing, and tweet it if it was different than the song that was playing the previous time the script checked.

Finding out what song was playing turned out to be simple. Thanks to Grant’s blog post about how he created the station, and a little digging around, I found a status page for the station that provided exactly what I was looking for. It helpfully reports the mount point (live broadcast or autodj), current listeners, and current song.

After writing a little PHP script to retrieve a copy of the status page to my server, I needed to figure out how to extract the current song and other data from the page. I started out by trying to write some regular expressions, but that ended up being a giant complex fail. After some more digging around, I found PHP Simple HTML DOM Parser, which lets the script work with an HTML document in much the same way one can with jQuery, our favorite Javascript library. In no time at all I was able to extract the data I needed. (I later needed to update this after Grant and Jim noticed that it wasn’t tweeting live broadcasts.)

Once I setup the script to check for new songs (every minute), I needed a way to determine whether or not to tweet it – it would not be awesome to keep tweeting songs longer than a minute multiple times while they are playing. The solution here was to keep a file containing a list of the songs that were previously tweeted. If the song extracted from the status page matches the last song in the file’s song list, don’t tweet it. (it’s not necessary to keep a list of EVERY song EVER played, but that data has future potential…)

When the script figures out that it needs to tweet a song, how does it do that? Digging around yet again, I found twitteroauth, which is a handy tool that makes it easy to send a tweet from a script – all you need are oauth credentials. Anyone who added a twitter feed to their blog knows this part: you have to set up an application on twitter, copy out some secret key data, and paste it in the right place. Once that was done, the twitter bot was in business!

If you’re interested in writing your own twitterbot, following is the code for the one I created for @ds106radio. It is MIT licensed, which is very permissive. See here for the cliff’s notes on what you can and can’t do with the code.

<?php
/*
 * File: config.php
 * Copyright (c) 2011 Galago - http://galagonwagon.com - [email protected]
 * This source is subject to the MIT License.
 * Please see the license.txt file for more information.
 */

$consumerKey = '<blah>';
$consumerSecret = '<blah>';
$accessToken = '<blah>';
$accessTokenSecret = '<blah>';

$songLog = "/path/to/song.log";
$stationURL = "http://208.82.115.69:8010/";
<?php
/*  
 * File: updateSong.php
 * Copyright (c) 2011 Galago - http://galagonwagon.com - [email protected]
 * This source is subject to the MIT License.
 * Please see the license.txt file for more information.
 */

<?php
require 'config.php';
require 'twitteroauth/twitteroauth/twitteroauth.php';
require 'simplehtmldom/simple_html_dom.php';

$song = getLastSong();
$nowPlaying = getNowPlaying();

if ($song != $nowPlaying) {
        updateSong($nowPlaying);
        updateTwitter($nowPlaying);
}


function updateTwitter($song) {
        global $consumerKey, $consumerSecret, $accessToken, $accessTokenSecret;
        $connection = new TwitterOAuth($consumerKey, $consumerSecret, $accessToken, $accessTokenSecret);
        $status = $connection->post('statuses/update', array('status' => $song));
}

function getNowPlaying() {
        global $stationURL;
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $stationURL);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        $output = curl_exec($ch);
        curl_close($ch);

        $entries = parseIcecastStatus($output);
        $np = "";

        foreach ($entries as $entry) {
                if ($entry['listeners'] > 0) {
                        $np = $entry['song'];
                        if ($entry['mountPoint'] == 'Mount Point /live') {
                                $np = "LIVE with " . $entry['title'] . ": " . $np;
                        }
                }
        }

        return $np;
}


function parseIcecastStatus($page) {
        $html = new simple_html_dom();
        $html->load($page);
        $entries = array();

        foreach ($html->find('.newscontent') as $stream) {
                $entry = array();
                $entry['mountPoint'] = $stream->find('.streamheader td h3', 0)->plaintext;

                foreach ($stream->find('tr') as $item) {
                        if ($item->first_child()->plaintext == "Stream Title:") {
                                $entry['title'] = $item->last_child()->plaintext;
                        }
                        else if ($item->first_child()->plaintext == "Current Listeners:") {
                                $entry['listeners'] = $item->last_child()->plaintext;
                        }
                        else if ($item->first_child()->plaintext == "Current Song:") {
                                $entry['song'] = $item->last_child()->plaintext;
                        }
                }
                $entries[] = $entry;
        }

        $html->clear();
        unset($html);

        return $entries;
}


function getLastSong() {
        global $songLog;
        $song = "";
        $handle = @fopen($songLog, "r");
        if ($handle) {
                while (($buffer = fgets($handle, 4096)) !== false) {
                        $song = trim($buffer);
                }
                if (!feof($handle)) {
                        echo "Error: unexpected fgets() fail\n";
                }
                fclose($handle);
        }

        return $song;
}


function updateSong($song) {
        global $songLog;
        if (!$handle = fopen($songLog, 'a')) {
                echo "Cannot open file ($songLog)";
                exit;
        }

        if (fwrite($handle, $song . "\n") === FALSE) {
                echo "Cannot write to file ($songLog)";
                exit;
        }

        fclose($handle);
}
File: license.txt

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

Add a comment

ds106 in[SPIRE]