1 # $Id: RssReader-numbered.tcl.txt,v 1.1 2005/07/17 04:46:18 aegrumet Exp $ 2 # RssReader module for Tivo Control Station 3 # version 0.5d -- Experimental 4 # 5 # by aegrumet@alum.mit.edu on 2004-03-20 6 # 7 # Reads a RSS2.0 feed and displays it on your TV screen. 8 # 9 # Bug reports, comments etc go here: 10 # http://grumet.net/weblog/archives/2004/03/20/rssreader_04d.html 11 # 12 # This code is based loosely on the WebTemplate.tcl file 13 # in tcs/modules/samples. 14 # 15 # You have to have a networked Tivo running Tivo Control Station 16 # (http://www.zirakzigil.net/tivo/TCS.html) for this to work. 17 # 18 # Installation instructions 19 # 1. Copy this file to your hard drive and rename to RssReader.tcl. 20 # 2. Edit the CONSTANTS below. 21 # Optional: edit the remotecommand. 22 # 3. Stop TCS 23 # 4. Copy the edited file to the TCS modules/modules subdirectory on Tivo. 24 # 5. Create a link 25 # cd tcs/modules 26 # ln modules/RssReader.tcl 27 # 6. /var/hack/tcs/starttcs & 28 #Builds a list of lists containing information about the active channels. 29 #Each sub-list is in the following format 30 # 31 # sfsid number CallSign Affiliation 32 # 33 proc RssBuildActiveChannelsInfo {} { 34 global db 35 global RssActiveChannelsInfo 36 set RssActiveChannelsInfo [list] 37 set numbers_checked [list] 38 set added 1 39 set start 0 40 #We assume the channel list will be returned in a consistent order each time. 41 while { $added > 0 } { 42 set added 0 43 RetryTransaction { 44 set Setup [db $db open /Setup] 45 set Source [dbobj $Setup get Source] 46 set channels [dbobj $Source get Channel] 47 for {set i 0} {$i < [llength $channels]} {incr i} { 48 if { $i < $start } continue 49 if { $added > 25 } continue 50 set chan [lindex $channels $i] 51 set number [dbobj $chan get Number] 52 set station [dbobj $chan get Station] 53 set sfsid [dbobj $station fsid] 54 set CallSign [dbobj $station get CallSign] 55 set Affiliation [dbobj $station get Affiliation] 56 lappend RssActiveChannelsInfo [list $sfsid $number $CallSign $Affiliation] 57 incr added 58 } 59 } 60 incr start $added 61 } 62 } 63 # Convert XMLTV date to tivo date. 64 proc RssXmltvToTivoTime {datestr} { 65 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]" 66 #'date' is just the GNU date utility that ships with tivo linux. 67 return [exec date -d $unixdate +%s] 68 } 69 # Convert XMLTV codes to ones that Tivo can understand, and 70 # try to schedule a ToDo item. Returns the recording fsid if successful, 71 # 0 otherwise. 72 # 73 # Sample XMLTV codes 74 # channel="C1vod.zap2it.com" 75 # start="20040204020000 EST" 76 # stop="20040204060000 EST" 77 # 78 # Returns: [list "Status String" Fsid] 79 proc RssTryToSchedule {TvChannel TvStart TvStop title subtitle description} { 80 global RssScheduleAttempts 81 global RssRecordingLegalDaysAhead 82 global RssStationFsidCallback 83 #Parse start and stop times. 84 if { [catch { 85 set starttime [RssXmltvToTivoTime $TvStart] 86 set stoptime [RssXmltvToTivoTime $TvStop] 87 }] } { 88 return [list "Bad start or stop time" 0] 89 } 90 #Check start and stop times integrity 91 if { $starttime > $stoptime || $starttime < [clock seconds] } { 92 return [list "Time interval is reversed or in the past" 0] 93 } 94 set startday [expr $starttime / 86400] 95 set nowday [expr [clock seconds] / 86400] 96 if { $startday > ($nowday + $RssRecordingLegalDaysAhead) } { 97 return [list "Start time exceeds Legal Days Ahead from now" 0] 98 } 99 #Check if we've already tried to schedule this one. 100 foreach attemptinfo $RssScheduleAttempts { 101 set att_st [lindex $attemptinfo 0] 102 if { $att_st == $starttime } { 103 return [lrange $attemptinfo 1 2] 104 } 105 } 106 #Look up the fsid for the channel. 107 set sfsid_lookup_info [$RssStationFsidCallback $TvChannel] 108 set sfsid_lookup_success_p [lindex $sfsid_lookup_info 0] 109 set sfsid [lindex $sfsid_lookup_info 1] 110 if { !$sfsid_lookup_success_p } { 111 return [list "Bad TV channel" 0] 112 } 113 #If we get here, it's time to look for conflicts. 114 #RecConflictsList is a Tivoweb proc. 115 set cancellists [RecConflictsList $starttime $stoptime] 116 set cancellist [lindex $cancellists 0] 117 if { [llength $cancellist] > 0 } { 118 #Fail on conflicts -- we're not gonna overwrite normally 119 #scheduled stuff. 120 lappend RssScheduleAttempts [list $starttime "Conflict" 0] 121 return [list "Conflict" 0] 122 } 123 #Cancel conflicting suggestions. 124 #DeleteTodoRec is a Tivoweb proc. 125 set cancellistsug [lindex $cancellists 1] 126 foreach cancelfsid $cancellistsug { 127 DeleteTodoRec $cancelfsid 10 "Deleting to record another program" 128 } 129 #Do the deed. 130 set recfsid [RssMakeTodoRec $sfsid $starttime $stoptime $title $subtitle $description] 131 lappend RssScheduleAttempts [list $starttime "Ok" $recfsid] 132 return [list "Ok" $recfsid] 133 } 134 #Returns a tcl list containing: success_p sfsid 135 proc RssCallSignCallback TvChannel { 136 if { [regexp {^C([0-9]+)([^\.]+)} $TvChannel match snum csign] } { 137 return [RssFindAStationByCallSign $csign] 138 } else { 139 return [list 0 0] 140 } 141 } 142 proc RssFindAStationByNumber { snum } { 143 return [RssFindAStationLookup 1 $snum] 144 } 145 proc RssFindAStationByCallSign { csign } { 146 return [RssFindAStationLookup 2 $csign] 147 } 148 proc RssFindAStationByAffiliation { affil } { 149 return [RssFindAStationLookup 3 "{$affil Affiliate}"] 150 } 151 proc RssFindAStationLookup { offset val } { 152 global RssActiveChannelsInfo 153 foreach info $RssActiveChannelsInfo { 154 if { [string tolower [lindex $info $offset]] == [string tolower $val] } { 155 return [list 1 [lindex $info 0]] 156 } 157 } 158 return [list 0 0] 159 } 160 #See #http://alt.org/forum/index.php?t=msg&goto=666&rid=246&S=8d6619190ab09dd8803c11b44328ba17 161 proc RssMakeTodoRec { sfsid start stop title subtitle description } { 162 global version3 db 163 if { $title == "" } { set title "unknown title - inserting" } 164 set startdate [expr $start / 86400] 165 set starttime [expr $start % 86400] 166 set stopdate [expr $stop / 86400] 167 set stoptime [expr $stop % 86400] 168 set Duration [expr $stop - $start] 169 RetryTransaction { 170 set recording [db $db create Recording] 171 set recordingfsid [ dbobj $recording fsid ] 172 dbobj $recording set BitRate 0 173 dbobj $recording set ExpirationTime 0 174 dbobj $recording set RecordQuality 40 175 dbobj $recording set Score 100 176 dbobj $recording set State 6 177 dbobj $recording set ExpirationDate [expr $startdate + 7] 178 dbobj $recording set SelectionType 3 179 dbobj $recording set StartDate $startdate 180 dbobj $recording set StopDate $stopdate 181 dbobj $recording set StartTime $starttime 182 dbobj $recording set StopTime $stoptime 183 set station [db $db openid $sfsid] 184 if { $version3 } { 185 dbobj $recording set UsedBy 1 186 set recordingbehavior [db $db createsub RecordingBehavior $recording] 187 foreach Behavior {DiskBehavior PresentationBehavior ProgramGuideBehavior} { dbobj $recordingbehavior set $Behavior 1 } 188 dbobj $recordingbehavior set TunerBehavior 3 189 dbobj $recording set RecordingBehavior $recordingbehavior 190 } 191 set showing [db $db createsub Showing $recording] 192 dbobj $showing set Date $startdate 193 dbobj $showing set Duration $Duration 194 dbobj $showing set Station $station 195 dbobj $showing set Time $starttime 196 dbobj $recording set Showing $showing 197 dbobj $recording set StreamFileSize 0 198 set program [db $db create Program] 199 dbobj $program set Title "*$title" 200 dbobj $program set EpisodeTitle $subtitle 201 dbobj $program set Description $description 202 dbobj $program set OriginalAirDate "$startdate" 203 dbobj $showing set Program $program 204 } 205 return $recordingfsid 206 } 207 ################################### 208 #Remove tags, unescape HTML entities, etc. 209 proc RssCleanContent str { 210 regsub -all {<} $str {<} str 211 regsub -all {>} $str {>} str 212 regsub -all {<[^>]*>} $str {} str 213 regsub -all {"} $str {"} str 214 regsub -all {&} $str {&} str 215 return [string trim $str] 216 } 217 proc ProcessRssLine { s } { 218 global WebFinished 219 global RssStreamBuf 220 global RssChanTitle 221 global RssItems 222 global RssRecordingEnabledP 223 global RssMaxItems 224 # EOF MAY never be set on the socket 225 if {[eof $s] || [catch {gets $s line}]} { 226 set WebFinished 1 227 catch {close $s} 228 return 229 } 230 append RssStreamBuf $line 231 #Try to get the channel title if we don't have it. 232 if {[string compare $RssChanTitle ""] == 0 } { 233 set StartTitle [string first "