Dynix2RSS

Keith Vetter 2004-11-26 -- Are you like me and are always forgetting when library books are due?

Well, here's a short script I wrote a while ago that connects to your libraries web-based catalog, logs in as you, and produces either a text or RSS stream of all your checked out items. You'll need to either edit the script to add your username, password and library URL or supply them on the command line.

It was designed for libraries running a web-based system written by Dynix (also known as iPAC or oPAC), and it's only been tested on the libraries I use.


 ##+##########################################################################
 #
 # Dynix2RSS.tcl -- logs into a Dynix web-based library and produces
 #                  an RSS feed of all checked out items.
 # by Keith Vetter  June 17, 2004
 #
 ##+##########################################################################
 #############################################################################

 package require http
 if {! [catch {package require autoproxy}]} {
    ::autoproxy::init
 }

 set username "" ;# Typically your library card number
 set password "" ;# PIN - try last four digits of your phone
 set dynix  ""   ;# Base URL of your library's Dynix catalog
                 ;# for me its: http://66.213.81.15:8080/ipac20/ipac.jsp

 proc GetBooks {} {
    global username password dynix2 books whoami

    set books {}

    #
    # Get the authentication page which gives us the session variable
    #
    set url $dynix2
    set token [::http::geturl $url]
    set page [::http::data $token] ; ::http::cleanup $token
    set n [regexp -nocase {[?&]session=(.*?)&} $page => session]
    if {! $n} {DIE "ERROR: cannot extract session ID from initial page"}

    #
    # Get the items out page
    #
    set url "$dynix2&session=$session&sec1=$username&sec2=$password"
    set token [::http::geturl $url] 
    set page [::http::data $token] ; ::http::cleanup $token

    #
    # Now scrape this page for the books we have out
    #
    set whoami "?"
    regexp {Welcome(.*?)<} $page => whoami
    regsub -all {&nbsp;} $whoami " " whoami
    set whoami [string trim $whoami]

    set start 0
    set bcnt 0
    while {1} {
        set n [regexp -start $start -indices -nocase \
                   {<a[^>]*mediumboldanchor[^>]*>} $page idxs]
        if {! $n} break

        incr bcnt
        set anchor [eval string range [list $page] $idxs]
        set n [regexp {href="(.*?)"} $anchor => link]
        if {! $n} {DIE "Cannot parse book $bcnt"}
        set link [string map {&amp; &} $link]
        regsub {session=.*?&} $link {} link

        # Extract title
        foreach {a b} $idxs break
        set n [regexp -start $b {.(.*?)</a>} $page => title]
        if {! $n} {DIE "Cannot parse title for book \#$bcnt"}
        set title [string trimright $title "/ "]

        # Extract checkout date
        set n [regexp -start $b -indices -nocase \
                   {normalBlackfont2.*?>(.*?)</a>} $page => idxs]
        if {! $n} {DIE "Cannot parse checkout date for book \#$bcnt"}
        set out [eval string range [list $page] $idxs]

        # Extract due date
        foreach {a b} $idxs break
        set n [regexp -start $b -indices -nocase \
                   {normalBlackfont2.*?>(.*?)</a>} $page => idxs]
        if {! $n} {DIE "Cannot parse due date for book \#$bcnt"}
        set due [eval string range [list $page] $idxs]
        set due2 [clock scan $due]


        # Extract times renewed
        foreach {a b} $idxs break
        set n [regexp -start $b -indices -nocase \
                   {normalBlackfont2.*?>(.*?)</a>} $page => idxs]
        if {! $n} {DIE "Cannot parse times renewed for book \#$bcnt"}
        set renewed [eval string range [list $page] $idxs]

        # Save the result
        lappend books [list $due2 $title $link $out $due $renewed]
        set start $b
    }
    set books [lsort -integer -index 0 $books]
    return
 }

 proc DIE {msg} {puts stderr $msg; exit}
 proc htmlQuote {s} { return [string map { & &amp; < &lt; > &gt; } $s] }

 proc DoTxt {} {
    global books whoami

    set txt "Books checked out for $whoami"
    foreach book $books {
        foreach {. title link out due renewed} $book break
        puts "$title"
        puts "    out: $out"
        puts "    due: $due"
    }
 }


 proc DoRSS {} {
    global books whoami dynix2 date
    set title "Library books out"
    if {$whoami ne ""} { append title " for $whoami"}

    puts "<?xml version=\"1.0\" encoding=\"ISO-8859-1\" standalone=\"yes\"?>"
    puts -nonewline "<!DOCTYPE rss PUBLIC "
    puts -nonewline "\"-//Netscape Communications//DTD RSS 0.91//EN\" "
    puts "\"http://my.netscape.com/publish/formats/rss-0.91.dtd\">"
    puts "<rss version=\"0.91\">"
    puts "  <channel>"
    puts "    <title>[htmlQuote $title]</title>"
    puts "    <lastBuildDate>$date</lastBuildDate>"
    puts "    <link>[htmlQuote $dynix2]</link>"
    puts "    <description>Books checked out from the library</description>"

    foreach book $books {
        foreach {. title link out due renewed} $book break
        set txt "Checked out: $out  Due back: $due"

        puts "    <item>"
        puts "      <title>[htmlQuote $title]</title>"
        puts "      <link>[htmlQuote $link]</link>"
        puts "      <description>$txt</description>"
        puts "    </item>"
    }

    puts "  </channel>"
    puts "</rss>"

    return
 }
 set rss 1
 if {$argc > 0 && [lindex $argv 0] eq "-t"} {
    set rss 0
    set argc [llength [set argv [lrange $argv 1 end]]]
 }

 if {$argc == 1} { foreach username $argv break}
 if {$argc == 2} { foreach {username password} $argv break}
 if {$argc == 3} { foreach {username password dynix} $argv break}

 set dynix2 "$dynix?profile=pac&menu=account&submenu=itemsout"
 set date [clock format [clock seconds] -format "%Y-%m-%dT%H:%M:%SZ"]

 GetBooks
 if {$rss} {
    DoRSS
 } else {
    DoTxt
 }