# $Id: RssReader-0.5d.tcl.txt,v 1.1 2008/01/08 05:53:58 aegrumet Exp $ # RssReader module for Tivo Control Station # version 0.5d -- Experimental # # by aegrumet@alum.mit.edu on 2004-03-20 # # Reads a RSS2.0 feed and displays it on your TV screen. # # Bug reports, comments etc go here: # http://grumet.net/weblog/archives/2004/03/20/rssreader_04d.html # # This code is based loosely on the WebTemplate.tcl file # in tcs/modules/samples. # # You have to have a networked Tivo running Tivo Control Station # (http://www.zirakzigil.net/tivo/TCS.html) for this to work. # # Installation instructions # 1. Copy this file to your hard drive and rename to RssReader.tcl. # 2. Edit the CONSTANTS below. # Optional: edit the remotecommand. # 3. Stop TCS # 4. Copy the edited file to the TCS modules/modules subdirectory on Tivo. # 5. Create a link # cd tcs/modules # ln modules/RssReader.tcl # 6. /var/hack/tcs/starttcs & #Builds a list of lists containing information about the active channels. #Each sub-list is in the following format # # sfsid number CallSign Affiliation # proc RssBuildActiveChannelsInfo {} { global db global RssActiveChannelsInfo set RssActiveChannelsInfo [list] set numbers_checked [list] set added 1 set start 0 #We assume the channel list will be returned in a consistent order each time. while { $added > 0 } { set added 0 RetryTransaction { set Setup [db $db open /Setup] set Source [dbobj $Setup get Source] set channels [dbobj $Source get Channel] for {set i 0} {$i < [llength $channels]} {incr i} { if { $i < $start } continue if { $added > 25 } continue set chan [lindex $channels $i] set number [dbobj $chan get Number] set station [dbobj $chan get Station] set sfsid [dbobj $station fsid] set CallSign [dbobj $station get CallSign] set Affiliation [dbobj $station get Affiliation] lappend RssActiveChannelsInfo [list $sfsid $number $CallSign $Affiliation] incr added } } incr start $added } } # Convert XMLTV date to tivo date. proc RssXmltvToTivoTime {datestr} { set unixdate "[string range $datestr 0 7] [string range $datestr 8 9]:[string range $datestr 10 11]:[string range $datestr 12 13][string range $datestr 14 end]" #'date' is just the GNU date utility that ships with tivo linux. return [exec date -d $unixdate +%s] } # Convert XMLTV codes to ones that Tivo can understand, and # try to schedule a ToDo item. Returns the recording fsid if successful, # 0 otherwise. # # Sample XMLTV codes # channel="C1vod.zap2it.com" # start="20040204020000 EST" # stop="20040204060000 EST" # # Returns: [list "Status String" Fsid] proc RssTryToSchedule {TvChannel TvStart TvStop title subtitle description} { global RssScheduleAttempts global RssRecordingLegalDaysAhead global RssStationFsidCallback #Parse start and stop times. if { [catch { set starttime [RssXmltvToTivoTime $TvStart] set stoptime [RssXmltvToTivoTime $TvStop] }] } { return [list "Bad start or stop time" 0] } #Check start and stop times integrity if { $starttime > $stoptime || $starttime < [clock seconds] } { return [list "Time interval is reversed or in the past" 0] } set startday [expr $starttime / 86400] set nowday [expr [clock seconds] / 86400] if { $startday > ($nowday + $RssRecordingLegalDaysAhead) } { return [list "Start time exceeds Legal Days Ahead from now" 0] } #Check if we've already tried to schedule this one. foreach attemptinfo $RssScheduleAttempts { set att_st [lindex $attemptinfo 0] if { $att_st == $starttime } { return [lrange $attemptinfo 1 2] } } #Look up the fsid for the channel. set sfsid_lookup_info [$RssStationFsidCallback $TvChannel] set sfsid_lookup_success_p [lindex $sfsid_lookup_info 0] set sfsid [lindex $sfsid_lookup_info 1] if { !$sfsid_lookup_success_p } { return [list "Bad TV channel" 0] } #If we get here, it's time to look for conflicts. #RecConflictsList is a Tivoweb proc. set cancellists [RecConflictsList $starttime $stoptime] set cancellist [lindex $cancellists 0] if { [llength $cancellist] > 0 } { #Fail on conflicts -- we're not gonna overwrite normally #scheduled stuff. lappend RssScheduleAttempts [list $starttime "Conflict" 0] return [list "Conflict" 0] } #Cancel conflicting suggestions. #DeleteTodoRec is a Tivoweb proc. set cancellistsug [lindex $cancellists 1] foreach cancelfsid $cancellistsug { DeleteTodoRec $cancelfsid 10 "Deleting to record another program" } #Do the deed. set recfsid [RssMakeTodoRec $sfsid $starttime $stoptime $title $subtitle $description] lappend RssScheduleAttempts [list $starttime "Ok" $recfsid] return [list "Ok" $recfsid] } #Returns a tcl list containing: success_p sfsid proc RssCallSignCallback TvChannel { if { [regexp {^C([0-9]+)([^\.]+)} $TvChannel match snum csign] } { return [RssFindAStationByCallSign $csign] } else { return [list 0 0] } } proc RssFindAStationByNumber { snum } { return [RssFindAStationLookup 1 $snum] } proc RssFindAStationByCallSign { csign } { return [RssFindAStationLookup 2 $csign] } proc RssFindAStationByAffiliation { affil } { return [RssFindAStationLookup 3 "{$affil Affiliate}"] } proc RssFindAStationLookup { offset val } { global RssActiveChannelsInfo foreach info $RssActiveChannelsInfo { if { [string tolower [lindex $info $offset]] == [string tolower $val] } { return [list 1 [lindex $info 0]] } } return [list 0 0] } #See #http://alt.org/forum/index.php?t=msg&goto=666&rid=246&S=8d6619190ab09dd8803c11b44328ba17 proc RssMakeTodoRec { sfsid start stop title subtitle description } { global version3 db if { $title == "" } { set title "unknown title - inserting" } set startdate [expr $start / 86400] set starttime [expr $start % 86400] set stopdate [expr $stop / 86400] set stoptime [expr $stop % 86400] set Duration [expr $stop - $start] RetryTransaction { set recording [db $db create Recording] set recordingfsid [ dbobj $recording fsid ] dbobj $recording set BitRate 0 dbobj $recording set ExpirationTime 0 dbobj $recording set RecordQuality 40 dbobj $recording set Score 100 dbobj $recording set State 6 dbobj $recording set ExpirationDate [expr $startdate + 7] dbobj $recording set SelectionType 3 dbobj $recording set StartDate $startdate dbobj $recording set StopDate $stopdate dbobj $recording set StartTime $starttime dbobj $recording set StopTime $stoptime set station [db $db openid $sfsid] if { $version3 } { dbobj $recording set UsedBy 1 set recordingbehavior [db $db createsub RecordingBehavior $recording] foreach Behavior {DiskBehavior PresentationBehavior ProgramGuideBehavior} { dbobj $recordingbehavior set $Behavior 1 } dbobj $recordingbehavior set TunerBehavior 3 dbobj $recording set RecordingBehavior $recordingbehavior } set showing [db $db createsub Showing $recording] dbobj $showing set Date $startdate dbobj $showing set Duration $Duration dbobj $showing set Station $station dbobj $showing set Time $starttime dbobj $recording set Showing $showing dbobj $recording set StreamFileSize 0 set program [db $db create Program] dbobj $program set Title "*$title" dbobj $program set EpisodeTitle $subtitle dbobj $program set Description $description dbobj $program set OriginalAirDate "$startdate" dbobj $showing set Program $program } return $recordingfsid } ################################### #Remove tags, unescape HTML entities, etc. proc RssCleanContent str { regsub -all {<} $str {<} str regsub -all {>} $str {>} str regsub -all {<[^>]*>} $str {} str regsub -all {"} $str {"} str regsub -all {&} $str {&} str return [string trim $str] } proc ProcessRssLine { s } { global WebFinished global RssStreamBuf global RssChanTitle global RssItems global RssRecordingEnabledP global RssMaxItems # EOF MAY never be set on the socket if {[eof $s] || [catch {gets $s line}]} { set WebFinished 1 catch {close $s} return } append RssStreamBuf $line #Try to get the channel title if we don't have it. if {[string compare $RssChanTitle ""] == 0 } { set StartTitle [string first "