webif/webif/lib/tvdb.class

427 lines
9.4 KiB
Tcl

source /mod/webif/lib/setup
package require cgi
if {![exists -command class]} { package require oo }
if {![exists -command sqlite3.open]} { package require sqlite3 }
require system.class xml.class
set ::tvdb::apikey 1764335F804A5A91
set ::tvdb::mirror "thetvdb.com"
set ::tvdb::artwork "artworks.thetvdb.com"
set ::tvdb::cache "/mod/var/tvdb"
set ::tvdb::icache "/mod/webif/cache/tvdb/img"
set ::tvdb::cacheage 86400
set ::tvdb::debug 0
if {![file isdirectory $::tvdb::cache]} {
file mkdir $::tvdb::cache
}
if {![file isdirectory $::tvdb::icache]} {
system mkdir_p $::tvdb::icache
}
set ::tvdb::indexdb [sqlite3.open "$::tvdb::cache/series.db"]
$::tvdb::indexdb query {
create table if not exists nseries(
[name] text primary key
)
}
class tvdb {
seriesid 0
imdb_id ""
name ""
overview ""
dat ""
banner ""
_matches {}
_smatches {}
_phrases {}
}
proc ::tvdb::dlog {msg} {
if {$::tvdb::debug} {
puts $msg
}
}
tvdb method setseries {sid} {
set seriesid $sid
}
#tvdb method _fetch {url} {
# set f [socket stream "${::tvdb::mirror}:443"]
# $f ssl
# $f puts -nonewline "GET $url HTTP/1.1\r\n"
# $f puts -nonewline "Host: $::tvdb::mirror\r\n"
# $f puts -nonewline "Connection: close\r\n"
# $f puts -nonewline "\r\n"
#
# set line [string trim [$f gets]]
# while {[string length $line]} {
# #puts "Web Header: $line"
# set line [string trim [$f gets]]
# }
#
# # Save the body
# set ret [$f read]
# $f close
# return $ret
#}
proc {tvdb fetchfile} {url} {
set cmd {wget -q -4 -O-}
lappend cmd $url
::tvdb::dlog "Executing $cmd"
return [exec {*}$cmd]
}
tvdb method _fetch {path} {
return [tvdb fetchfile "https://$::tvdb::mirror$path"]
}
tvdb method _parse {xml vars {end "XX"}} {
set x [xml init $xml]
set cattr 0
while {1} {
lassign [$x next] type val attr etype
if {$type == "EOF"} break
switch $etype {
START { set cattr $val }
END {
if {$val == $end} break
set cattr 0
}
default {
if {$type == "TXT"} {
if {$cattr in $vars} {
set $cattr $val
}
::tvdb::dlog "$cattr - $val"
}
}
}
}
}
#<Series>
#<seriesid>261451</seriesid>
#<language>en</language>
#<SeriesName>Teenage Mutant Ninja Turtles (2012)</SeriesName>
#<banner>graphical/261451-g2.jpg</banner>
#<Overview>Four ninja turtles, mutated by a mysterious alien substance, must rise up out of the sewers and defend their city against evil forces from both the past and present.</Overview>
#<FirstAired>2012-09-29</FirstAired>
#<Network>Nickelodeon</Network>
#<id>261451</id>
#</Series>
tvdb method _extract {xml end vars} {
set x [xml init $xml]
set cattr 0
set ret {}
set attrs {}
while {1} {
lassign [$x next] type val attr etype
if {$type == "EOF"} break
switch $etype {
START {
set cattr $val
}
END {
if {$val == $end} {
lappend ret $attrs
set attrs {}
}
set cattr 0
}
default {
if {$type == "TXT"} {
if {$cattr in $vars} {
set attrs([\
string tolower $cattr]) $val
}
}
}
}
}
return $ret
}
tvdb method fetchseries {} {
if {!$seriesid} return
set base "$::tvdb::cache/$seriesid"
# Expire old files
if {[file exists "$base.xml"]} {
set age $([clock seconds] - [file mtime "$base.xml"])
::tvdb::dlog "$base.xml age $age"
if {$age > $::tvdb::cacheage} {
file delete "$base.xml"
::tvdb::dlog "Expiring $base.xml"
}
}
if {![file exists "$base.xml"]} {
::tvdb::dlog "Downloading"
# Download the series info
set f [open "$base.zip" w]
puts $f [$self _fetch \
"/api/$::tvdb::apikey/series/$seriesid/all/en.zip"]
$f close
catch {exec /mod/bin/unzip -o -q "$base.zip" en.xml -d $::tvdb::cache}
if {[file exists "$::tvdb::cache/en.xml"]} {
file rename "$::tvdb::cache/en.xml" $base.xml
# Extract episode info
puts [exec /mod/webif/lib/bin/tvdb "$base.xml"]
}
}
}
tvdb method loadseries {} {
if {!$seriesid} return
set ret [$::tvdb::indexdb query {
select * from series where series_id = %s} $seriesid]
if {[llength $ret] == 1} {
foreach {k v} [lindex $ret 0] {
set $k $v
}
}
}
tvdb method searchseries {series} {
::tvdb::dlog "Searching TVDB for $series"
set seriesxml [$self _fetch \
"/api/GetSeries.php?seriesname=[cgi_quote_url $series]"]
return [$self _extract $seriesxml Series \
{seriesid SeriesName banner Overview}]
}
tvdb method findseries {series} {
set ret ""
catch {
set ret [$::tvdb::indexdb query {
select series_id from series where name = '%s'
} $series]
}
if {[llength $ret]} {
lassign [lindex $ret 0] x seriesid
::tvdb::dlog "Cached ID for '$series' = $seriesid"
}
if {!$seriesid} {
# Attempt to determine series ID
::tvdb::dlog "Trying to find series in TVDB"
set seriesxml [$self _fetch \
"/api/GetSeries.php?seriesname=[cgi_quote_url $series]"]
$self _parse $seriesxml {seriesid} Series
}
if {!$seriesid} {
# Negative caching
$::tvdb::indexdb query {
replace into nseries values('%s')
} $series
}
}
tvdb method dbhandle {} {
if {![file exists "$::tvdb::cache/$seriesid.db"]} { return {} }
set db [sqlite3.open "$::tvdb::cache/$seriesid.db"]
return $db
}
proc {tvdb tolike} {str {extra ""}} {
set map {" and " "%" " the " "%"}
if {[llength $extra]} { lappend map {*}$extra }
return "%[string map $map $str]%"
}
tvdb method episodebynum {series episode} {
if {![llength [set db [$self dbhandle]]]} return {}
set ret [$db query {
select * from episode where series = %s and episode = %s
} $series $episode]
if {[llength $ret]} { return [lindex $ret 0] }
return {}
}
tvdb method episodebyname {str {series 0} {episode 0}} {
if {![llength [set db [$self dbhandle]]]} return {}
set q "select * from episode where name like '%s'"
if {$series} { append q " and series = $series" }
if {$episode} { append q " and episode = $episode" }
append q " order by length(name) desc"
set ret [$db query $q [tvdb tolike $str]]
if {[llength $ret]} { return [lindex $ret 0] }
# Try a match with all spaces removed
set q "select * from episode where replace(name, ' ', '') like '%s'"
if {$series} { append q " and series = $series" }
if {$episode} { append q " and episode = $episode" }
append q " order by length(name) desc"
set ret [$db query $q [tvdb tolike $str {" " ""}]]
if {[llength $ret]} { return [lindex $ret 0] }
return {}
}
tvdb method wordcount {db words extra} {
set _matches {}
loop i 5 1 -1 {
#puts "Trying phrases of length $i<br>"
set wlen $([llength $words] - $i - 1)
for {set a 0} {$a < $wlen} {incr a} {
set w [lrange $words $a $($a + $i - 1)]
#puts "Phrase: $w<br>"
set ret [$db query {
select episode_id,
case
when name like '%%%s%%' then 1
else 0
end as inname
from episode
where overview like '%%%s%%'
or name like '%%%s%%'
%s
} $w $w $w $extra]
foreach row $ret {
#puts "&nbsp;&nbsp;> FOUND: ($w)<br>"
ladd _phrases $w
lassign $row x id x inname
if {$inname eq "1"} {
# If found in name then score is 10x
# phrase length
incr _matches($id) $($i * 10)
} else {
# Score is twice phrase length
incr _matches($id) $($i * 2)
}
}
}
}
}
tvdb method seek {synopsis {extra ""}} {
if {![llength [set db [$self dbhandle]]]} return {}
set words [split $synopsis " "]
# Count phrase occurence
$self wordcount $db $words $extra
if {![llength $_matches]} {
return {}
}
# Find best match
set _smatches [lsort -integer -decreasing -index end [\
lmap {k v} $_matches {
list $k $v
}]]
set eid [lindex [lindex $_smatches 0] 0]
set ret [$db query {
select * from episode where episode_id = %s
} $eid]
if {[llength $ret]} { return [lindex $ret 0] }
return {}
}
tvdb method episodebyid {episode_id} {
if {![llength [set db [$self dbhandle]]]} return {}
set ret [$db query {
select * from episode where episode_id = %s
} $episode_id]
if {[llength $ret]} { return [lindex $ret 0] }
return {}
}
tvdb method episodebyepnum {episode synopsis} {
return [$self seek $synopsis "and episode = $episode"]
}
tvdb method episodebyseries {series synopsis} {
return [$self seek $synopsis "and series = $series"]
}
tvdb method episodebysynopsis {synopsis} {
return [$self seek $synopsis]
}
tvdb method episodesearch {search} {
if {![llength [set db [$self dbhandle]]]} return {}
set ret [$db query {
select episode_id,
case
when name like '%%%s%%' then 1
else 0
end as inname
from episode
where overview like '%%%s%%'
or name like '%%%s%%'
} $search $search $search]
return $ret
}
tvdb method series_count {} {
if {![llength [set db [$self dbhandle]]]} { return 0 }
set ret [$db query {
select max(series) from episode
}]
set num 0
if {[llength $ret]} {
lassign [lindex $ret 0] x num
}
return $num
}
proc {tvdb series} {series {sid 0}} {
set t [tvdb]
if {$sid} {
$t setseries $sid
} else {
$t findseries $series
}
if {[$t get seriesid] ne "0"} {
$t fetchseries
$t loadseries
if {[$t get name] eq ""} {
$t setseries 0
}
}
return $t
}
proc {tvdb cachebanner} {banner} {
if {![string match {/banners/*} $banner]} {
set _banner "/banners/$banner"
} else {
set _banner $banner
}
if {![file exists "$::tvdb::icache/$banner"]} {
set ret [tvdb fetchfile "https://$::tvdb::artwork$_banner"]
if {[string length $ret] > 50} {
system mkdir_p "$::tvdb::icache/[file dirname $banner]"
file write "$::tvdb::icache/$banner" $ret
}
}
}
proc {tvdb bannerurl} {banner} {
return "/cgi-bin/tvdbimg.jim?file=[cgi_quote_url $banner]"
}
proc {tvdb seriesurl} {seriesid} {
return "https://$::tvdb::mirror/?tab=series&id=$seriesid"
}