Skip to content

Instantly share code, notes, and snippets.

@founddrama
Created January 1, 2012 17:28
Show Gist options
  • Save founddrama/1547848 to your computer and use it in GitHub Desktop.
Save founddrama/1547848 to your computer and use it in GitHub Desktop.
Goodreads.com reading timeline (stub)
(function($){
$(document).ready(function(){
var data = window['data'],
// for reviews:
rate = function(r){
if (r === 0) {
return '<em>unrated</em>';
}
var str = '';
// Stars...
while (str.length < r) {
str += '\u2605';
}
while (str.length < 5) {
str += '\u2606';
}
return str;
};
$('.review').tipsy({
delayOut: 1000,
gravity: 'w',
fade:true,
html:true,
title: function(){
var book = data[this.getAttribute('id')];
return '<div style="width:125px;">' + book.started + ' to ' + book.finished + '<br/>' +
'<a href="' + book.url + '" title="View ' + book.title + ' on Goodreads.com">' +
'<img src="' + book.cover + '" /><br/><em>' + book.title + '</em><br/>by ' + book.author +
'<br/>' + rate(book.rating) +'</a></div>';
}
});
$('#preload-shim').append(function(){
var o, i, imgs = [];
for (o in data) {
i = new Image();
i.src = data[o].cover;
imgs.push(i);
}
return imgs;
});
});
})(jQuery);
//#!/usr/bin/env groovy
// commented out for the Gist syntax highlighting
import static java.util.Calendar.YEAR
import groovy.text.SimpleTemplateEngine
def usage = """\nUSAGE:
Give it your API key, your Goodreads.com user ID, and the name of the shelf
you want to convert into a timeline:
./GetTimeline.groovy api_key user_id [shelf-name]
The default shelf name is '2011'
"""
if (args.grep('--help') || args.grep('-h') || args.grep('-?')) {
println usage
System.exit(0)
}
def apiKey
def user
def shelf
def assignFromArgs = { theArgs ->
(theArgs.size() >= 2) {
apiKey = theArgs[0]
user = theArgs[1]
shelf = theArgs.last() : '2011'
} else {
println "[ERROR] You did not supply the correct number of arguments!\n\n${usage}"
System.exit(1)
}
}
assignFromArgs(args as List)
def url = "http://www.goodreads.com/review/list?v=2&key=${apiKey}&id=${user}&shelf=${shelf}&sort=date_read&per_page=100&order=a"
def sanitizeDate(def dateStr) {
Date d = new Date(0).parse('EEE MMM dd HH:mm:ss Z yyyy', dateStr)
return d
}
def reviewList = []
try {
def xml = new XmlParser().parse( url.toURL().newReader() )
xml.reviews.review.each {
def summary = [
id: it.id.text(),
url: it.url.text(),
title: it.book.title.text().replaceAll(/(\s\(.+\s#\d.*\))$/, ''),
cover: it.book.image_url?.text() ?: null,
authors: it.book.authors.toList().collect { a -> a.author.name.text() },
started: it.started_at?.text() ? sanitizeDate(it.started_at?.text()) : new Date().parse('yyyy-MM-dd', '2011-01-01'),
finished: it.read_at?.text() ? sanitizeDate(it.read_at.text()) : new Date().parse('yyyy-MM-dd', '2011-12-31'),
read_count: it.read_count?.text() ? (it.read_count.text() as String).split(' ').first().replaceAll(/[\D]/, '') as int : 0,
rating: it.rating?.text() as int
]
// My special exception for 'The Joy of Clojure' :-\
if (shelf == '2011' && summary.title.contains('Joy') && summary.title.contains('Clojure')) {
summary.started = new Date().parse('yyyy-MM-dd', '2011-07-07')
summary.finished = new Date().parse('yyyy-MM-dd', '2011-12-31')
}
reviewList << summary
}
} catch(Throwable e) {
println "Something went wrong trying to parse ${url}: ${e}"
}
reviewList.sort { a, b ->
a.started > b.started ? 1 : -1
}
def divClassBuilder = { review ->
def cls = ['review']
if (review.read_count < 2) {
cls << 'new'
}
return "${cls.join(' ')}"
}
def shelfYear = ({ y ->
try {
return y as int
} catch(e){
return new Date()[YEAR] as int
}
})(shelf)
def styleBuilder = { review ->
def started = review.started.format('D') as int
def finished = review.finished.format('D') as int
if (review.started[YEAR] < shelfYear) {
// Jan 1st
started = 1
} else if (review.finished[YEAR] > shelfYear) {
finished = new Date().parse('yyyy-M-d', "${review.started[YEAR]}-12-31").format('D') as int
}
def style = ""
style += "top:${(started - 1) * 10}px;"
style += "height:${((finished - started) * 10) + 10};"
return style
}
def markup = ''
def jsData = []
reviewList.each {
jsData << """'${it.id}':{
started: '${it.started.format('M/d')}',
finished: '${it.finished.format('M/d')}',
rating: ${it.rating},
title: '${it.title.replaceAll(/[']/, '\\\\\'')}',
author: '${it.authors.first().replaceAll(/[']/, '\\\\\'')}',
cover: '${it.cover}',
url: '${it.url}'
}"""
markup += """<div class="${divClassBuilder(it)}" style="${styleBuilder(it)}" title="<img src='${it.cover}' /><br/><em>${it.title}</em> by ${it.authors.first()}" id="${it.id}">&nbsp;</div>\n"""
}
File f = new File("target/${shelf}.htm")
def tpl = new File('templates/timeline.template').getText('UTF-8')
def binding = [jsData: "{${jsData.join(',')}}", shelf: shelf, markup: markup]
SimpleTemplateEngine engine = new SimpleTemplateEngine()
f.write(engine.createTemplate(tpl).make(binding).toString())
System.exit(0)
html, body {
font-family: 'Lucida Grand' 'Helvetica' 'Verdana' sans-serif;
font-size: 12px;
margin: 0;
padding: 0;
}
.masthead {
color: rgb(33,86,37);
border-bottom: 3px double rgb(33,86,37);
}
.masthead h1 {
font-size: 3em;
margin: 1em 20px;
}
.footer {
position: relative;
}
.footer .badge {
position: absolute;
bottom: 2px;
right: 2px;
height: 41px;
width: 130px;
background: transparent url(goodreads-badge-read-reviews.png) no-repeat scroll center center;
}
a, a:hover, a:visited {
color: #E3DFC9;
text-decoration: none;
}
.month-marker {
position: absolute;
left: 0;
right: 0;
height: 0px;
border-top: 1px dashed rgb(56, 33, 16);
border-top-color: rgba(56, 33, 16, 0.8);
}
.month-marker span {
display: block;
width: 100px;
padding: 4px 10px;
background: rgb(56, 33, 16);
background: rgba(56, 33, 16, 0.8);
color: rgb(250, 250, 250);
color: rgba(250, 250, 250, 0.8);
}
#jan { top: 0; border: none;}
#feb { top: 310px; }
#mar { top: 590px; }
#apr { top: 900px; }
#may { top: 1200px; }
#jun { top: 1510px; }
#jul { top: 1810px; }
#aug { top: 2120px; }
#sep { top: 2430px; }
#oct { top: 2730px; }
#nov { top: 3040px; }
#dec { top: 3340px; }
.reviews-ct {
border: 1px solid rgb(56, 33, 16);
border-color: rgba(56, 33, 16, 0.8);
margin: 1px auto 50px;
position: relative;
overflow: hidden;
height: 3650px;
width: 900px;
background: #E3DFC9;
background-image: linear-gradient(bottom, rgb(244,243,235) 28%, rgb(227,223,201) 82%);
background-image: -o-linear-gradient(bottom, rgb(244,243,235) 28%, rgb(227,223,201) 82%);
background-image: -moz-linear-gradient(bottom, rgb(244,243,235) 28%, rgb(227,223,201) 82%);
background-image: -webkit-linear-gradient(bottom, rgb(244,243,235) 28%, rgb(227,223,201) 82%);
background-image: -ms-linear-gradient(bottom, rgb(244,243,235) 28%, rgb(227,223,201) 82%);
}
.review {
position: relative;
float: left;
width: 20px;
border-radius: 6px / 4px;
background: rgb(33, 86, 37);
background: rgba(33, 86, 37, 0.9);
background-image: linear-gradient(right, rgb(33,86,37) 28%, rgb(12,31,13) 82%);
background-image: -o-linear-gradient(right, rgb(33,86,37) 28%, rgb(12,31,13) 82%);
background-image: -moz-linear-gradient(right, rgb(33,86,37) 28%, rgb(12,31,13) 82%);
background-image: -webkit-linear-gradient(right, rgb(33,86,37) 28%, rgb(12,31,13) 82%);
background-image: -ms-linear-gradient(right, rgb(33,86,37) 28%, rgb(12,31,13) 82%);
}
.review:hover {
box-shadow: 0 12px 8px rgba(157, 217, 161, 0.9);
}
#preload-shim {
height: 1px;
width: 1px;
position: absolute;
left: -9999px;
overflow: hidden;
}
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link rel="stylesheet" type="text/css" href="assets/timeline.css" />
<link rel="stylesheet" type="text/css" href="assets/tipsy.css" />
<script type="text/javascript" charset="UTF-8">
data = ${jsData};
</script>
</head>
<body>
<div class="masthead">
<h1>${shelf} reading list</h1>
</div>
<div class="reviews-ct">
${markup}
<div class="month-marker" id="jan"><span>January</span></div>
<div class="month-marker" id="feb"><span>February</span></div>
<div class="month-marker" id="mar"><span>March</span></div>
<div class="month-marker" id="apr"><span>April</span></div>
<div class="month-marker" id="may"><span>May</span></div>
<div class="month-marker" id="jun"><span>June</span></div>
<div class="month-marker" id="jul"><span>July</span></div>
<div class="month-marker" id="aug"><span>August</span></div>
<div class="month-marker" id="sep"><span>September</span></div>
<div class="month-marker" id="oct"><span>October</span></div>
<div class="month-marker" id="nov"><span>November</span></div>
<div class="month-marker" id="dec"><span>December</span></div>
</div>
<div class="footer">
<a href="http://www.goodreads.com/"><div class="badge"></div></a>
<div id="preload-shim"></div>
</div>
<script type="text/javascript" src="assets/jquery.min.js"></script>
<script type="text/javascript" src="assets/jquery.tipsy.js"></script>
<script type="text/javascript" src="assets/br-init.js"></script>
</body>
</html>
@founddrama
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment