Update (14/02/2014):
Some folks over at http://www.247drumandbass.com are now utilizing this script on their IRC channel. You should check it out on Quakenet #247drumandbass.

Shoutcast TCL Script

I was recently approached by a friend to help out with a TCL script for his  Shoutcast radio station. He has an eggdrop running on the EFNet IRC server which acts as an announcer for the radio station. It gives users the ability to call commands such as what song is currently playing, what is playing next and  general advertising of the station.

Shoutcast have recently had a big upgrade to their software and in particular the admin interface. While this is always a welcome edition due to new features and bug fixes one of the downsides is that scripts that use the old software tend to be made null and void. In particular changes to the XML and the inclusion of stream IDs broke most of the old TCL scripts.

I wouldn’t say I am particularly skilled in the TCL language but I’m always willing to give something a go and by looking at the old scripts and the newer admin interface I did manage to make a fairly feature rich Shoutcast announcement script.

The below code block outlines how I made the basic commands by using an http connection to browse to the admin xml webpage and then using an exceptional TCL package called tDom to extract particular XML nodes and their inline text. This text is then displayed to an IRC channel.

package require http
package require tdom

bind pub -|- !song currsong

proc currplaying {nick uhost hand chan arg} { 
  global siteurl djchan radiochan adminpass  
  # Create an HTTP request to the admin xml page :
  :http::config -useragent "Mozilla/5.0; Shoutinfo" 
  set http_req [::http::geturl http://$siteurl:8000/admin.cgi?pass=$adminpass&sid=1&mode=viewxml&page=0 -timeout 2000]
  if {[::http::status $http_req] != "ok"} { 
    putnow "PRIVMSG $chan :Stream is unavailable"; 
  } 
  set data [::http::data $http_req] ::http::cleanup $http_req  
  # Create dom document 
  set doc [dom parse $data] set xmlNodes [$doc documentElement]  
  # Grab Stream status from XML to check if the station is currently online or not 
  if {[[$xmlNodes selectNodes /SHOUTCASTSERVER/STREAMSTATUS/text()] data] == "1"} {  
    # If it is online display a message to the channel the current playing song 
    putnow "PRIVMSG $chan :
package require http
package require tdom

bind pub -|- !song currsong

proc currplaying {nick uhost hand chan arg} { 
  global siteurl djchan radiochan adminpass  
  # Create an HTTP request to the admin xml page :
  :http::config -useragent "Mozilla/5.0; Shoutinfo" 
  set http_req [::http::geturl http://$siteurl:8000/admin.cgi?pass=$adminpass&sid=1&mode=viewxml&page=0 -timeout 2000]
  if {[::http::status $http_req] != "ok"} { 
    putnow "PRIVMSG $chan :Stream is unavailable"; 
  } 
  set data [::http::data $http_req] ::http::cleanup $http_req  
  # Create dom document 
  set doc [dom parse $data] set xmlNodes [$doc documentElement]  
  # Grab Stream status from XML to check if the station is currently online or not 
  if {[[$xmlNodes selectNodes /SHOUTCASTSERVER/STREAMSTATUS/text()] data] == "1"} {  
    # If it is online display a message to the channel the current playing song 
    putnow "PRIVMSG $chan :\002Current Song\002: [[$xmlNodes selectNodes /SHOUTCASTSERVER/SONGTITLE/text()] data]" 
  } else {    
    # Otherwise let the user kmow the server is offline 
    if {$chan != $djchan} { 
      putnow "PRIVMSG $chan :Server status is offline..." 
    } else { 
      putnow "NOTICE $nick :Server status is offline..." 
    }
  }
}
2
Current Song
package require http
package require tdom

bind pub -|- !song currsong

proc currplaying {nick uhost hand chan arg} { 
  global siteurl djchan radiochan adminpass  
  # Create an HTTP request to the admin xml page :
  :http::config -useragent "Mozilla/5.0; Shoutinfo" 
  set http_req [::http::geturl http://$siteurl:8000/admin.cgi?pass=$adminpass&sid=1&mode=viewxml&page=0 -timeout 2000]
  if {[::http::status $http_req] != "ok"} { 
    putnow "PRIVMSG $chan :Stream is unavailable"; 
  } 
  set data [::http::data $http_req] ::http::cleanup $http_req  
  # Create dom document 
  set doc [dom parse $data] set xmlNodes [$doc documentElement]  
  # Grab Stream status from XML to check if the station is currently online or not 
  if {[[$xmlNodes selectNodes /SHOUTCASTSERVER/STREAMSTATUS/text()] data] == "1"} {  
    # If it is online display a message to the channel the current playing song 
    putnow "PRIVMSG $chan :\002Current Song\002: [[$xmlNodes selectNodes /SHOUTCASTSERVER/SONGTITLE/text()] data]" 
  } else {    
    # Otherwise let the user kmow the server is offline 
    if {$chan != $djchan} { 
      putnow "PRIVMSG $chan :Server status is offline..." 
    } else { 
      putnow "NOTICE $nick :Server status is offline..." 
    }
  }
}
2
: [[$xmlNodes selectNodes /SHOUTCASTSERVER/SONGTITLE/text()] data]"
} else {     # Otherwise let the user kmow the server is offline if {$chan != $djchan} { putnow "PRIVMSG $chan :Server status is offline..." } else { putnow "NOTICE $nick :Server status is offline..." } } }
TCL

I won’t go through it line by line as most of it is standard TCL code but I will point out the bits important to this post. Firstly you can see I include two packages. HTTP and tDom. As mentioned previously this allows me to make http calls and then extract and use the XML nodes.

package require http
package requite tdom
TCL

Next up  I make an http call to the servers admin interface passing through the $siteurl and $adminpass variables. These are set at the top of the script as global variables. As you can see in the url I am also calling &mode=viewxml to display the output in XML.

::http::config -useragent "Mozilla/5.0; Shoutinfo" 
set http_req [::http::geturl http://$siteurl:8000/admin.cgi?pass=$adminpass&sid=1&mode=viewxml&page=0 -timeout 2000] 
if {[::http::status $http_req] != "ok"} { 
  putnow "PRIVMSG $chan :Stream is unavailable"; 
} 
set data [::http::data $http_req] ::http::cleanup $http_req
TCL

Finally I create a Dom document from the returned http data and use that to extract particular nodes.

set doc [dom parse $data]
set xmlNodes [$doc documentElement]
putnow "PRIVMSG $chan :
set doc [dom parse $data]
set xmlNodes [$doc documentElement]
putnow "PRIVMSG $chan :\002Current Song\002: [[$xmlNodes selectNodes /SHOUTCASTSERVER/SONGTITLE/text()] data]"
2
Current Song
set doc [dom parse $data]
set xmlNodes [$doc documentElement]
putnow "PRIVMSG $chan :\002Current Song\002: [[$xmlNodes selectNodes /SHOUTCASTSERVER/SONGTITLE/text()] data]"
2
: [[$xmlNodes selectNodes /SHOUTCASTSERVER/SONGTITLE/text()] data]"
TCL

This similar technique is also used to display the next song, current bitrate, current listeners which are all grabbed from the XML output. Other features of the script include:

  • Topic changes for when the radio station comes online/goes offline
  • A periodic timer to check and show song changes
  • A timer to check if the peak listeners changes
  • Commands to show current song, next song, server status, radio url and radio url
  • Ability for operators to disconnect current dj
  • Ability for users to request songs (which are displayed to a separate dj channel)
  • List the 10 previous songs
  • Display the current and peak listeners

I add the script to my github page soon, so feel free to check it out and replace any old scripts you may have. I’m always up for a challenge so if you have any ideas for new features drop a comment or tweet and I’ll be happy to discuss it.

If you want to see it in action jump on EFNet and join the #android-radio channel.