/** * Browsing iTunesU without installing iTunes * iTunesU xml to html - proxy application * * Proof of concept - free to use, modify and share * * Boris Fritscher boris.fritscher@unil.ch */ import xmlwise.* import groovy.xml.StreamingMarkupBuilder def handle = "http://deimos.apple.com/WebObjects/Core.woa/Browse/unil.ch" if(request.getParameter("handle")){ handle = request.getParameter("handle") } /** fix namespace when outputing xml from MarkupBuilder */ def getXml(item){ def outputBuilder = new StreamingMarkupBuilder() outputBuilder.encoding = "UTF-8" String result = outputBuilder.bind{ mkp.declareNamespace("":"http://www.apple.com/itms/") mkp.yield item } } def xml def conn = new URL(handle).openConnection() conn.setRequestProperty ( "User-Agent", "iTunes/8.1" ) def putBackTogether = new StringBuffer() def r = new InputStreamReader ( conn.getInputStream(), "UTF-8" ) char [ ] cb = new char [ 2048 ] int amtRead = r.read ( cb ) while ( amtRead > 0 ) { putBackTogether.append ( cb, 0, amtRead ) amtRead = r.read ( cb ) } xml = putBackTogether.toString() xml = new XmlSlurper().parseText(xml) /** * find first image and based on it decided which page type * we are on. If it has a reflection attribute we are on a * course page (track listing). */ def topimg = xml.'**'.find{ it.name() == "PictureView"} def reflect = topimg?.'@reflection'==1 //true = course page /** * get font colors */ def titleFontStyle = xml.'**'.find{it.name() == "FontStyle" && it.'@name' == "normalTitleFontStyle"}?.'@color' if(titleFontStyle == null){ titleFontStyle = xml.'**'.find{it.name() == "FontStyle" && it.'@name' == "outlineTitleFontStyle"}?.'@color' } def textFontStyle = xml.'**'.find{it.name() == "FontStyle" && it.'@name' == "normalTextFontStyle"}?.'@color' if(textFontStyle == null){ textFontStyle = xml.'**'.find{it.name() == "FontStyle" && it.'@name' == "outlineTextFontStyle"}?.'@color' } /** * header */ println """ """ html.html(xmlns:"http://www.w3.org/1999/xhtml",lang:"en",'xml:lang':"en") { head { title xml.Path.PathElement.collect{it.'@displayName'}.join(" > ") meta('http-equiv':"X-UA-Compatible", content:"IE=8") meta('http-equiv':"content-type",content:"application/xhtml+xml; charset=UTF-8") link(rel:"stylesheet", href:"css/blueprint/screen.css", type:"text/css", media:"screen, projection") link(rel:"stylesheet", href:"css/blueprint/print.css", type:"text/css", media:"print") println "" link(rel:"stylesheet", href:"css/tablesorter/style.css", type:"text/css") link(rel:"stylesheet", href:"css/main.css", type:"text/css") link(rel:"shortcut icon", href:"favicon.ico", type:"image/x-icon") script(type:"text/javascript", src:"js/jquery-1.3.2.min.js","") script(type:"text/javascript", src:"js/reflection.js","") script(type:"text/javascript", src:"js/jquery.tablesorter.min.js","") script(type:"text/javascript", src:"js/main.js","") if(reflect){ script(type:"text/javascript", src:"js/mootools-1.2.1-core-jm.js","") script(type:"text/javascript", src:"js/mediaboxAdv99f.js","") link(rel:"stylesheet", href:"css/mediaboxAdvBlack.css", type:"text/css") } style(type:"text/css",""" .body, p, li, .section a, .linkbox a{ color: #${textFontStyle} } h1, h2, h3{ color: #${titleFontStyle} } """) } /** * Body */ body(style:"background: #${xml.'*'.find{ it.'@backColor' != ''}.'@backColor'}"){ div(class:"container"){ div(class:"span-24 last nav"){ def list = xml.Path.PathElement def last = list.size()-1 list.eachWithIndex{ pe,i -> if(i == 0){ a(class:"first",href:"index.groovy?handle=${pe.text().trim()}"){ span(){ mkp.yieldUnescaped " " } } }else{ a(class:"${i == last ? 'last' : ''}",href:"index.groovy?handle=${pe.text().trim()}", pe.'@displayName') } } } div(class:"span-24 last header", style:"${reflect ? '' : 'background: url('+topimg?.'@url'+') no-repeat top left'};min-height:${topimg.'@height'}px;"){ div(class:"span-16 course-detail"){ //COURSE PAGE if(reflect){ //only if displaying a course page def topurl = xml.'**'.find{it.name() == "GotoURL"} def content = topurl?.parent()?.'**'.findAll{it.name() == "TextView"}.collect{it.toString().trim()} h1 content[0] div(class:"span-5", style:"height:${Math.round(1.25 * Integer.parseInt(topimg.'@height'.toString()))}px;"){ img(src:topimg.'@url',width:topimg.'@width', height:topimg.'@height', class:"reflect") } def descriptionIndex = content.indexOf("DESCRIPTION") descriptionIndex = descriptionIndex == null ? 3 : descriptionIndex content[1..descriptionIndex-1].each{ text -> p text } //a(href:"${topurl.'@url'}","Open in iTunes") xml.'**'.find{it.name() == "GotoURL" && it.PictureButtonView?.'@alt'=="Subscribe"}.each{ subscribe -> //xml p{ a(href:"${subscribe.'@draggingURL'}"){ img(src:"img/rss-podcast.png") } br() a(href:"http://subscribe.getmiro.com/?url1=${subscribe.'@draggingURL'}", title:"Miro: Internet TV"){ img(src:"http://subscribe.getmiro.com/img/buttons/patrace1.png",alt:"Miro Video Player") } br() a(href:"${topurl.'@url'}",class:"subscribe"){ img(src:"img/logo_itunes_badge.gif") } } } if(content.size()>descriptionIndex){ h2(content[descriptionIndex].toString().trim()) content[descriptionIndex+1..content.size()-1].each{ text -> p text.toString().trim() } } }else{ mkp.yieldUnescaped " " } } //RIGHTBOX div(class:"span-8 last rightbox"){ div(class:"description"){ xml.ScrollView.View.MatrixView.View?.'**'.find{ it?.name()== "VBoxView" && it?.parent()?.name()== "VBoxView" && it?.parent()?.parent()?.name()== "VBoxView" && it?.parent()?.parent()?.parent()?.name()== "VBoxView" }?.'*'.findAll{ it?.name() == "TextView"}.each{ text -> p text } mkp.yieldUnescaped " " } def linkbox = xml.'**'.findAll{it.name() == "VBoxView" && it?.FontStyle.size() == 2 }[reflect? 1 : 0]?.parent()?.parent() //move over recommand if on course page if(linkbox != null){ div(class:"linkbox",style:"background: url(${linkbox.parent()?.'**'.find{it.name() == 'PictureView'}.'@url'}) top left"){ linkbox?.'**'.findAll{it.name() == "VBoxView" && it?.FontStyle.size() == 2 }.each{ cat -> h3(style:"color: #${cat.FontStyle[0].'@color'};border: 1px solid #${cat.View[0].Test[0].View.'@borderColor'}", cat?.'**'.find{it.name() == "TextView"}.toString().trim()) ul{ def els = cat?.'**'.findAll{it.name() == "OpenURL"} if (els.size()==0){ els = cat.parent()?.'**'.findAll{it.name() == "OpenURL"} } els.each{ url -> li{ def urlstring = url.'@url' def matcher = ( urlstring =~ /^itms(.+)/ ) if(matcher.count > 0){ urlstring = "index.groovy?handle=http${matcher[0][1]}" } a(href:urlstring,url.'@draggingName') } } } } } } } }//header //sections xml.'**'.findAll{it.name() == "View" && !it.HBoxView.isEmpty() && !it.HBoxView.PictureView.isEmpty() }.each{ view -> div(class:"span-24 last section"){ div(class:"sectionhead",style:"background: url(${view.HBoxView[0].PictureView[2].'@url'}) no-repeat top right;"){ div(style:"background: url(${view.HBoxView[0].PictureView[0].'@url'}) no-repeat top left;"){ h2(style:"background: url(${view.HBoxView[0].PictureView[1].'@url'}) repeat-x top left;" , view.VBoxView?.'**'.find{it.name() == "TextView" }.text().trim()) } } def border = view.parent().parent().View.VBoxView[0]?.'**'.findAll{ it.name() == "PictureView"} div(class:"span-24 last sectionbody", style:"background: url(${border[0].'@url'}) top left;"){ //items view.parent().parent()?.'**'.findAll{ it.name() == "HBoxView" && !it.GotoURL.isEmpty() && !it.GotoURL.PictureView.isEmpty()}.eachWithIndex{ item, i-> //HBoxView div(class:"span-8 ${(i+1) % 3 == 0 ? 'last' : ''} course"){ a(href:"index.groovy?handle=${item.GotoURL.'@url'}"){ //GotoURL img(src:item.GotoURL.PictureView.'@url',alt:item.GotoURL.'@draggingName') //PictureView } div{ //VBoxView a(href:"index.groovy?handle=${item.VBoxView.GotoURL.'@url'}",item.VBoxView.GotoURL.'@draggingName') //GotoURL p{ item.VBoxView.TextView.each{ tv -> mkp.yield tv.text().trim() br() } } } } } } } } if(reflect){ div(class:"span-24 groups"){ def groups = xml.'**'.findAll{ it.name() == "GotoURL" && !it.PictureView.isEmpty()}.each{ group -> def imgs = group.children()?.'**'.grep{it.'@url' !=""}.'@url'*.text() def color = group?.'**'.find{it.'@color' !=""}?.'@color'.text(); div(style:"background: url(${imgs[2]}) no-repeat top right;"){ div(style:"background: url(${imgs[1]}) no-repeat top left;"){ a(style:"background: url(${imgs[0]}) repeat-x top left;color:#${color};", href:"?handle=${group.'@url'}",group.toString().trim()) } } } } if(xml.'*'.find{it.name() == "TrackList"}.size()>0){ div(class:"span-24 last tracklist"){ table(cellspacing:"1", class:"tablesorter"){ thead{ tr{ th "" th "Name" th "Time" th "Artist" th "Release Date" th "" } } tbody{ def plist = Plist.fromXml(getXml(xml.TrackList.plist)) plist.items.eachWithIndex{ track, i -> /* debug list all keys ul{ track.each{ key,value-> li("${key}: ${value}") } }*/ int seconds = (track["duration"] /1000) int minutes = seconds % 3600 int hours = (seconds - minutes) / 3600 seconds = minutes % 60 minutes = (minutes - seconds) / 60 tr(class:i % 2 == 0 ? "even" : "odd"){ td track["rank"] td track["songName"] td ((hours > 0 ? String.format("%02d",hours) + ":" : "")+ String.format("%02d",minutes) + ":" + String.format("%02d",seconds)) td track["artistName"] td track["releaseDate"][0..9] td{ a(href:track["previewURL"],rel:"lightbox[set 480 380]",title:track["songName"], "view") mkp.yieldUnescaped(" ") a(href:track["previewURL"],title:track["songName"], "download") } } } } } } } } /* div(class:"span-24 last debug"){ xml.'**'.findAll{ it.name() == "TextView"}.each{ item -> p(item.text().trim()) } xml.'**'.findAll{ it.name() == "PictureView"}.each{ item -> a(href:""){ img(src:item.'@url') } } xml.'**'.findAll{ it.name() == "GotoURL"}.each{ item -> a(href:"index.groovy?handle=${item.'@url'}",item.'@draggingName') } } //debug */ } //container } //body }