steamcast.tcl

steamcast.tcl

Steamcast BotScript v1.0 that connects to shoutcast stream station and post song information on irc channel. Designed to operate with Eggdrop 1.6.15

Postat de Copyright Categorie Review user Vizualizari Data
btc ^DooM^ media Cod netestat 559 2023-12-25 12:02:53

# Steamcast BotScript v1.0                                                 #
# By ^DooM^ modded by coldfyre - #dialtone efnet                           #
# ------------------------------------------------------------------------ #
# Designed to operate with Eggdrop 1.6.15                                  #
# ------------------------------------------------------------------------ #
set shoutcast(vershort)             "1.0"
set shoutcast(verlong)              "010000"

# ------------------------------------------------------------------------
# Configuration settings!
# ------------------------------------------------------------------------
# Access flag specifiers
set shoutcast(perm_operator)         "D"
set shoutcast(perm_anyone)           "-"

# Command Character (Prepends the command itself, i.e. '!'addserver
set shoutcast(command_char)          "!"

# Help trigger
set shoutcast(help_trigger)          "$shoutcast(command_char)help"

# The details for the interface servers
set shoutcast(interface_page)        "/status.xml"
set shoutcast(kickdj_page)           "/admin.cgi?mode=kicksrc"
set shoutcast(admin_page)            "/admin.cgi"
set shoutcast(listen_page)           "/listen.m3u?src=0"
set shoutcast(interface_user_agent)  "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.0)"

# Channel to display in
set shoutcast(broadcast_channel)     "#yuorchan"

# Not designed to be filled out by you... Yes that means you!
set sc_servercount  0

# Server Lists (Host, Name, Port, Password etc)
set sc_servers($sc_servercount,Host) "serverip"
set sc_servers($sc_servercount,Name) "FQHN"
set sc_servers($sc_servercount,Port) "8000"
set sc_servers($sc_servercount,Pass) "pass"
incr sc_servercount

# Add as many as you want.
#set sc_servers($sc_servercount,Host) "192.168.0.100"
#set sc_servers($sc_servercount,Name) "shoutcast2.shoutcast.com"
#set sc_servers($sc_servercount,Port) "8000"
#set sc_servers($sc_servercount,Pass) "password"
#incr sc_servercount

# Which is the primary server (retrieves playing stats etc from)
set shoutcast(primary_server)        0

# Number of milliseconds to wait for page request timeout
set shoutcast(page_timeout)          4000

# ------------------------------------------------------------------------
# Eggdrop Bindings (Pubs and Msgs are bound separately to ensure full
# support on most eggdrops, and to workaround several painful bugs).
# ------------------------------------------------------------------------
bind pub $shoutcast(perm_anyone)   [string trim $shoutcast(command_char)]stream         sc_stream
bind pub $shoutcast(perm_anyone)   [string trim $shoutcast(command_char)]playing        sc_playing
bind pub $shoutcast(perm_anyone)   [string trim $shoutcast(command_char)]users          sc_userstats
bind pub $shoutcast(perm_anyone)   [string trim $shoutcast(command_char)]help           sc_help
bind pub $shoutcast(perm_anyone)   [string trim $shoutcast(command_char)]site		sc_site
bind pub $shoutcast(perm_operator) [string trim $shoutcast(command_char)]sckickdj       sc_kickdj
bind pub $shoutcast(perm_operator) [string trim $shoutcast(command_char)]scwho          sc_who

#bind time - "* * * * *" sc_update

# --------------------------------
# Global Variables - DO NOT TOUCH!
# --------------------------------
set sc_sock               0
set sc_packet_count       0
set sc_page_dump_complete 0
set sc_buffer             ""
set sc_laststatus         0
set sc_lasttitle          ""
set sc_lastsong           ""
set sc_lastbitrate        0

# Setup global array data
for {set i 0} {$i < $sc_servercount} {incr i} {
    set sc_responses($i,lastlistenercount) 0
}
proc sc_site { nick channel } {
   set strCommand "site"
   set strHelp "\037 YOUR SITE HERE!!!\037"
   sc_output $nick $channel $strHelp
   return
}
proc sc_help { nick uhost handle channel args } {

    set strCommand "help"
    set args [string map -nocase {"\\" "\\\\"} $args]
    set args [lindex $args 0]
    set strHelp [sc_get_usage [lindex $args 0] [sc_isop $nick]]
    sc_output $nick $channel $strHelp
    return
}

proc sc_update { minute hour day month year } {

    global shoutcast botnick sc_responses sc_servers sc_servercount
    global sc_lastsong sc_lastbitrate sc_laststatus sc_lasttitle

    set strCommand "update"
    set outArgs ""
    if { [sc_execute_command [clock seconds] $strCommand "" "" $shoutcast(broadcast_channel) $outArgs] == 0 } {
        putlog "Unable to execute update command."
        return
    }

    set iServer     $shoutcast(primary_server)
    set strTopic    ""
    set updateTopic 0
    if { $sc_responses($iServer,streamstatus) == 1 } {       
       
        if { $sc_responses($iServer,songtitle) != $sc_lastsong } {
            set sc_lastsong $sc_responses($iServer,songtitle)
            set strStream "Now Playing:\00313\002\037 $sc_responses($iServer,songtitle)"
            sc_output "" $shoutcast(broadcast_channel) $strStream
        }

       
        if { $sc_laststatus != 1 || $sc_lasttitle != $sc_responses($iServer,servertitle) || $sc_lastbitrate != $sc_responses($iServer,bitrate) } {
            set strTopic "$sc_responses($iServer,servertitle) Online @ $sc_responses($iServer,bitrate)kbps - "
 #           append strTopic "http://$sc_servers($iServer,Name):$sc_servers($iServer,Port)$shoutcast(listen_page)"
            set updateTopic 0
        }
        set sc_laststatus  1
        set sc_lastbitrate $sc_responses($iServer,bitrate)
        set sc_lasttitle   $sc_responses($iServer,servertitle)
    } else {
        if { $sc_laststatus != 0 } {
            set strTopic "Shoutcast: Offline"
            set updateTopic 0
        }
        set sc_laststatus  1
        set sc_lastbitrate 0
    }
    if { $updateTopic == 1 } {
 #       putserv "PRIVMSG $shoutcast(broadcast_channel) :$strTopic"
        }
    for { set i 0 } { $i < $sc_servercount } { incr i } {
       if { $sc_responses($i,streamstatus) == 1 } {     
            if { $sc_responses($i,currentlisteners) != $sc_responses($i,lastlistenercount) } {
                set sc_responses($i,lastlistenercount) $sc_responses($i,currentlisteners)
                set strStream "$sc_servers($i,Name) - $sc_responses($i,currentlisteners) of $sc_responses($i,maxlisteners) connected (peaking at $sc_responses($i,peaklisteners))"
                if { $sc_responses($i,currentlisteners) > 0 } {
                    append strStream ", utilising "
                    set iBandwidth [expr [expr $sc_responses($i,currentlisteners) * $sc_responses($i,bitrate)] / 8]
                    append strStream "$iBandwidth k/sec, for an average of [duration $sc_responses($i,averagetime)]."
                }
                sc_output "" $shoutcast(broadcast_channel) $strStream
            }
        }
    }
    return
}

proc sc_stream { nick uhost handle channel args } {

    global shoutcast botnick sc_servers sc_responses sc_servercount
    set strCommand "stream"
    set args [string map -nocase {"\\" "\\\\"} $args]
    set args [lindex $args 0]
    set outArgs ""
    if { [sc_execute_command [clock seconds] $strCommand $nick $uhost $channel $outArgs] == 0 } {
        sc_output $nick $channel "Unable to execute command."
        return
    }
    for { set i 0 } { $i < $sc_servercount } { incr i } {
        set strStream "[expr $i + 1]) http://$sc_servers($i,Name):$sc_servers($i,Port)$shoutcast(listen_page) - "
        if { $sc_responses($i,streamstatus) == 0 } {
            append strStream "Offline"
            sc_output $nick $channel $strStream
            continue
        } else {
            append strStream "Online @ $sc_responses($i,bitrate)kbps - "
        }
        set iSlotsFree [expr $sc_responses($i,maxlisteners) - $sc_responses($i,currentlisteners)]
        if { $iSlotsFree == 0 } {
            append strStream "Server full."
            sc_output $nick $channel $strStream
            continue
        } else {
            set strAppend ""
            if { $iSlotsFree > 1 } { set strAppend "s" }
            append strStream "$iSlotsFree slot$strAppend remaining."
        }
        sc_output $nick $channel $strStream
    }
    return
}

proc sc_playing { nick uhost handle channel args } {

    global shoutcast botnick sc_servers sc_responses sc_servercount
    set strCommand "playing"
    set args [string map -nocase {"\\" "\\\\"} $args]
    set args [lindex $args 0]
    set outArgs ""
    if { [sc_execute_command [clock seconds] $strCommand $nick $uhost $channel $outArgs] == 0 } {
        sc_output $nick $channel "Unable to execute command."
        return
    }
    set iServer $shoutcast(primary_server)
    set strStream "Primary server is currently offline."
    if { $sc_responses($iServer,streamstatus) == 1 } {
        set strStream "\00313,1\037\002Currently Playing: $sc_responses($iServer,songtitle)"
    }
    sc_output $nick $channel $strStream
}

proc sc_userstats { nick uhost handle channel args } {

    global shoutcast botnick sc_servers sc_responses sc_servercount
    set strCommand "users"
    set args [string map -nocase {"\\" "\\\\"} $args]
    set args [lindex $args 0]
    set outArgs ""
    if { [sc_execute_command [clock seconds] $strCommand $nick $uhost $channel $outArgs] == 0 } {
        sc_output $nick $channel "Unable to execute command."
        return
    }
    set iServersFound 0
    for { set i 0 } { $i < $sc_servercount } { incr i } {
        if { $sc_responses($i,streamstatus) == 0 } { continue }
        set strStream "$sc_servers($i,Name) - $sc_responses($i,currentlisteners) of $sc_responses($i,maxlisteners) connected (peaking at $sc_responses($i,peaklisteners)), utilising "
        set iBandwidth [expr [expr $sc_responses($i,currentlisteners) * $sc_responses($i,bitrate)] / 8]
        append strStream "$iBandwidth k/sec, for an average of [duration $sc_responses($i,averagetime)]."
        sc_output $nick $channel $strStream
        incr iServersFound
    }
    if { $iServersFound == 0 } { sc_output $nick $channel "No active servers to list." }
    return
}

proc sc_kickdj { nick uhost handle channel args } {

    global shoutcast botnick sc_servers
    set strCommand "sckickdj"
    set args [string map -nocase {"\\" "\\\\"} $args]
    set args [lindex $args 0]
    set outArgs ""
    if { [sc_execute_command [clock seconds] $strCommand $nick $uhost $channel $outArgs] == 0 } {
        sc_output $nick $channel "Unable to execute command."
        return
    }
    sc_output $nick $channel "DJ kicked from primary server."
}

proc sc_who { nick uhost handle channel args } {

    global shoutcast botnick sc_servers sc_responses sc_servercount
    set strCommand "scwho"
    set args [string map -nocase {"\\" "\\\\"} $args]
    set args [lindex $args 0]
    set outArgs ""
    if { [sc_execute_command [clock seconds] $strCommand $nick $uhost $channel $outArgs] == 0 } {
        sc_output $nick $channel "Unable to execute command."
        return
    }
    set iServersFound 0
    for { set i 0 } { $i < $sc_servercount } { incr i } {
        if { $sc_responses($i,streamstatus) == 0 } { continue }
        set    strStream "http://$sc_servers($i,Name):$sc_servers($i,Port)$shoutcast(admin_page)\n"
        append strStream "[string repeat "-" [expr [string length $strStream] - 1]][string repeat " " $i]\n"
        if { $sc_responses($i,listenerhosts) == "" } {
            append strStream "No listeners currently connected."
        } else {
            set counter 1
            foreach listener $sc_responses($i,listenerhosts) {
                append strStream "$counter) [sc_nickfromhost $listener][string repeat " " $i][string repeat " " $counter]\n"
                incr counter
            }           
        }
        append strStream "[string repeat " " [expr $i + 1]]\n"
        sc_output $nick $channel $strStream
        incr iServersFound
    }
    if { $iServersFound == 0 } { sc_output $nick $channel "No active servers to list." }
    return
}

# ************************************************************************
#  Script utility functions.
# ************************************************************************
proc sc_get_usage { command ischannelop } {

    global shoutcast botnick
    set command [string tolower $command]
    set strMessage ""
    if { $ischannelop && $command == "scwho" } {
        append strMessage "Usage for $shoutcast(command_char)scwho follows.\n"
        append strMessage "------------------------------------------------------------------\n"
        append strMessage "Retrieves the list of people (nicks / hosts) currently listening\n"
        append strMessage "------------------------------------------------------------------  "
       
    } elseif { $command == "sckickdj" } {
           
        append strMessage "Usage for $shoutcast(command_char)sckickdj follows.\n"
        append strMessage "------------------------------------------------------------------\n"
        append strMessage "Kicks the source from the server!\n"
        append strMessage "------------------------------------------------------------------  "

    } elseif { $command == "stream" } {
           
        append strMessage "Usage for $shoutcast(command_char)stream follows.\n"
        append strMessage "------------------------------------------------------------------\n"
        append strMessage "Lists current streams.\n"
        append strMessage "------------------------------------------------------------------  "
   
    } elseif { $command == "playing" } {
           
        append strMessage "Usage for $shoutcast(command_char)playing follows.\n"
        append strMessage "------------------------------------------------------------------\n"
        append strMessage "Shows current song.\n"
        append strMessage "------------------------------------------------------------------  "
   
    } elseif { $command == "users" } {
           
        append strMessage "Usage for $shoutcast(command_char)users follows.\n"
        append strMessage "------------------------------------------------------------------\n"
        append strMessage "Shows user stats for all streams.\n"
        append strMessage "------------------------------------------------------------------  "

    } elseif { $ischannelop } {
           
        #crap ....

    } else {
           
        #crap ....
       
    }

    return [string trim $strMessage "\n"]
}

proc sc_execute_command { timestamp command nick uhost channel args } {

    global shoutcast sc_servers
    set args [lindex $args 0]
    switch -- $command {
        "stream" {
            sc_get_stats 0
        }
        "users" {
            sc_get_stats 0
        }
        "scwho" {
            sc_get_stats 0
        }
        "update" {
            sc_get_stats 0
        }
        "playing" {
            sc_get_stats 1
        }
        "sckickdj" {
            set strHost $sc_servers($shoutcast(primary_server),Host);
            set strPort $sc_servers($shoutcast(primary_server),Port);
            set strPass $sc_servers($shoutcast(primary_server),Pass);
            set strPageData [sc_get_page $strHost $strPort "$shoutcast(kickdj_page)&pass=$strPass"]
            if { $strPageData == "" } { return 0 }
        }
        default {
            return 1
        }

    }
    return 1
}

proc sc_get_stats { primary_only } {

    global shoutcast sc_servers sc_responses sc_servercount
    for { set i 0 } { $i < $sc_servercount } { incr i } {
        if { $primary_only == 1 && $i != $shoutcast(primary_server) } { continue }
        set strHost $sc_servers($i,Host);
        set strPort $sc_servers($i,Port);
        set strPass $sc_servers($i,Pass);
        set strPageData [sc_get_page $strHost $strPort "$shoutcast(interface_page)"]
        if { $strPageData == "" } {
            set sc_responses($i,streamstatus)        0
            set sc_responses($i,currentlisteners)    0
            set sc_responses($i,maxlisteners)        0
            set sc_responses($i,peaklisteners)       0
            set sc_responses($i,averagetime)         0
            set sc_responses($i,servergenre)         ""
            set sc_responses($i,serverurl)           "http://$sc_servers($i,Name):$sc_servers($i,Port)$shoutcast(listen_page)"
            set sc_responses($i,servertitle)         ""
            set sc_responses($i,songtitle)           ""
            set sc_responses($i,bitrate)             ""
            set sc_responses($i,version)             ""
            set sc_responses($i,listenerhosts)       ""
            set sc_responses($i,lastlistenercount)   0

            # Bail
            continue
           
        }
        sc_parse_xml $strPageData $i sc_responses
    }
}

proc sc_parse_xml { pagedata serverNumber outArray } {

    upvar $outArray serverData
    regexp "<status>(.*?)</status>" $pagedata data value
    set serverData($serverNumber,streamstatus) $value
    regexp "<total_nodes>(.*?)</total_nodes>" $pagedata data value
    set serverData($serverNumber,currentlisteners) $value
    regexp "<total_max_nodes>(.*?)</total_max_nodes>" $pagedata data value
    set serverData($serverNumber,maxlisteners) $value
    regexp "<total_peak_nodes>(.*?)</total_peak_nodes>" $pagedata data value
    set serverData($serverNumber,peaklisteners) $value
    regexp "<genre>(.*?)</genre>" $pagedata data value
    set serverData($serverNumber,servergenre) $value
    regexp "<url>(.*?)</url>" $pagedata data value
    set serverData($serverNumber,serverurl) $value
    regexp "<name>(.*?)</name>" $pagedata data value
    set serverData($serverNumber,servertitle) $value
    regexp "<meta_song>(.*?)</meta_song>" $pagedata data value

    # Repair the song title (i.e. remove all XML elements)
    # Specify remap template
    set charRemap {
        "&#x3C;" "<"
        "&#x3E;" ">"
        "&#x26;" "&"
        "&#x22;" "\\"
        "&#x27;" "'"
        "&#xFF;" ""
    }

    set value [string map -nocase $charRemap $value]
    set serverData($serverNumber,songtitle) $value
    regexp "<bitrate>(.*?)</bitrate>" $pagedata data value
    set serverData($serverNumber,bitrate) $value
    regexp "<version>(.*?)</version>" $pagedata data value
    set serverData($serverNumber,version) $value
    set serverData($serverNumber,averagetime)   0
    set serverData($serverNumber,listenerhosts) ""
    set lstListener                             ""
    set connectCount                            0
    if { [regexp "<total_nodes>(.*?)</total_nodes>" $pagedata data value] } {
        set listenerdata $value
        regsub -all "</total_node>" $listenerdata "</total_node>\n" listenerdata
        set listenerdata [split $listenerdata \n]
        foreach strListener $listenerdata {

            if { [regexp "<name>(.*?)</name>" $strListener data value] } { lappend lstListener $value }
            if { [regexp "<connect_time>(.*?)</connect_time>" $strListener data value] } {
                incr serverData($serverNumber,averagetime) $value
                incr connectCount
            }
        }
        if { $connectCount > 0 } { set serverData($serverNumber,averagetime) [expr $serverData($serverNumber,averagetime) / $connectCount] }
    }
    set serverData($serverNumber,listenerhosts) $lstListener
}

proc sc_output { nick channel msg } {

    global shoutcast
    set strPrepend "PRIVMSG #shoutcast(broadcast_channel) :"
    set line_array [split $msg "\n"]
    foreach line $line_array { putserv "$strPrepend$line" }
}

# ------------------------------------------------------------------------
# Desc : Retrieve any data from any arbitrary page on any server / port.
# Note : Yes I know TCL 8 has an http API, but I don't want to rely
#        on it being available. It's also not quite as flexible as we need.
# ------------------------------------------------------------------------
proc sc_get_page { host port page } {

    global shoutcast sc_packet_count sc_page_dump_complete sc_sock sc_buffer errorInfo
    set sc_page_dump_complete 0
    set sc_packet_count 0
    set sc_buffer ""
    set urldump_Task [ after $shoutcast(page_timeout) { set sc_page_dump_complete 1; putlog "*** Page data retrieval timed out!" } ]
    if [catch {socket -async $host $port} sc_sock] {
        putlog "Shoutcast Error : $errorInfo"
        after cancel $urldump_Task
        return ""
    }
    fconfigure $sc_sock -blocking 0
    fileevent $sc_sock readable "sc_read_page $sc_sock"
    puts $sc_sock "GET $page HTTP/1.0\nHost: $host\nContent-type: text/plain\nUser-Agent: $shoutcast(interface_user_agent)\n\n"
    if [catch {flush $sc_sock} x] {
        putlog "Shoutcast Error : $errorInfo"
        close $sc_sock
        after cancel $urldump_Task
        return ""
    }
    vwait sc_page_dump_complete
    after cancel $urldump_Task
    catch {close $sc_sock}
    set sc_packet_count 0
    set line_array      [split $sc_buffer "\n"]
    set header_closed   0
    set sc_buffer       ""
    set i               0
    foreach line $line_array {
        if { $header_closed == 0 } {
            switch $i {
                0 {
                    set tokens [split $line " "]
                    set server_code [lindex $tokens 1]
                    #putlog "Server responded with $server_code [lrange $tokens 2 end]"
                    if { $server_code != 200 && $server_code != 302 } { return "" }
                }
            }
            if { $line == "" } { set header_closed 1 }
        } else {
            append sc_buffer "$line\n"
        }
        incr i
    }
    regsub -all "\r" $sc_buffer "" sc_buffer
    return [string trim $sc_buffer "\n"]
}

proc sc_read_page socket {

    global sc_packet_count sc_page_dump_complete sc_buffer
    if [eof $socket] {
        close $socket;
        set sc_packet_count 0
        after idle set sc_page_dump_complete 1
        return;
    }
    gets $socket data
    append sc_buffer "$data\n"
    incr sc_packet_count
}

proc urlencode { text } {
 
    regsub -all {\%} $text "%25" text
    set i [string length $text]
    while {([regexp -nocase {[^a-z0-9%]} $text toh])&&!($i < 0 )} {
        scan "$toh" %c dec
        regsub -all "\\${toh}" $text "%[format %X $dec]" text
        incr i -1
    }
    if {$i < 0} {putlog "Infinite loop something is very wrong: $text"}
    return $text
}

proc sc_nickfromhost { host } {
   
    global shoutcast

    set FoundUsers 0
    set NickList   ""
    set DuplicateList ""
    foreach chan [channels] {
        if { ![botonchan $chan] } { continue }
        foreach user [chanlist $chan] {
            if { [string match "* $user *" $DuplicateList] == 1} { continue }
            set uhost "[getchanhost $user $chan]"
            if { [string match -nocase "*@$host" $uhost] } {
                append DuplicateList " $user "
                if { $FoundUsers == 0 } {
                    append NickList $user
                } else {
                   append NickList ", $user"
                }
                incr FoundUsers
            }
        }
    }
    if { $FoundUsers == 0 } { return "*!*@$host" }
    set CharPos [string last , $NickList]
    if { $CharPos == -1} { return $NickList }
    set NickList "[string range $NickList 0 [expr $CharPos - 1]] or [string range $NickList [expr $CharPos + 2] 99999999]"
    return $NickList
}

proc sc_isop { nick } {
   
    global shoutcast

    foreach chan [channels] {
        if { ![botonchan $chan] } { continue }
        if { [onchan $nick $chan] } { 
            if { [isop $nick $chan] } { return 1 }
        }
    }
    return 0
}

putlog "** Shoutcast Stats Initialising $shoutcast(vershort) By ^DooM^ modded by coldfyre"