Created
August 18, 2020 12:51
-
-
Save Potherca/27a35c1eeba6e0953e0fcce8cb00cdf2 to your computer and use it in GitHub Desktop.
Timeline of books read by Goodreads user. Requires a GoodReads API key
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!doctype html> | |
<meta charset="utf-8"> | |
<meta name=viewport content="width=device-width, initial-scale=1"> | |
<meta name="description" content="Timeline of books read by Goodreads user" /> | |
<meta name="twitter:site" content="@potherca"> | |
<meta name="twitter:title" content="Goodreads Timeline"> | |
<meta name="twitter:description" content="Timeline of books read by Goodreads user"> | |
<meta name="twitter:creator" content="@potherca"> | |
<meta property="og:title" content="Goodreads Timeline" /> | |
<meta property="og:type" content="article" /> | |
<meta property="og:url" content="https://blog.pother.ca/goodreads-timeline" /> | |
<meta property="og:description" content="Timeline of books read by Goodreads user" /> | |
<!-- <link rel="stylesheet" href="src/main.css" type="text/css" /> --> | |
<style> | |
title {display: inline;} | |
.books { | |
align-items: center; | |
display: flex; | |
flex-wrap: wrap; | |
font-family: "Lato", "Helvetica Neue", Helvetica, Arial, sans-serif; | |
justify-content: start; | |
} | |
.book { | |
list-style: none; | |
position: relative; | |
width: 8em; | |
margin: 1em; | |
box-shadow: | |
-1px -1px 0 0 black, | |
-2px -2px 0 0 white, | |
-3px -3px 0 0 black, | |
-4px -4px 0 0 white, | |
-5px -5px 0 0 black, | |
-6px -6px 0 0 white, | |
-7px -7px 0 0 black, | |
-8px -8px 0 0 white, | |
-9px -9px 0 0 black | |
; | |
} | |
.book::after { | |
background-color: #000; | |
content: "\00a0"; | |
display: block; | |
height: calc(100% - 2px); | |
left: -10px; | |
position: absolute; | |
top: -4px; | |
transform: skewY(45deg); | |
width: 10px; | |
} | |
.book img { | |
width: 100%; | |
} | |
.book__title, | |
.book__published { | |
position: absolute; | |
background-color: rgba(51,51,51,0.7); | |
border: 1px solid #181818; | |
color: #FFFFFF; | |
font-size: 13px; | |
padding: 0.2em; | |
} | |
.book__title { | |
border-radius: 0.5em; | |
display: block; | |
left: 0; | |
margin: 0 0.2em; | |
opacity: 0; | |
text-align: center; | |
text-decoration: none; | |
top: 50%; | |
transform: translateY(-50%); | |
transition-duration: 0.5s; | |
transition-property: opacity; | |
width: calc(100% - 1em); | |
} | |
.book--without-cover .book__title, | |
.book:hover .book__title { | |
opacity: 1; | |
} | |
.book__published { | |
border-top-left-radius: 0.5em; | |
bottom: 0.35em; | |
opacity: 0.5; | |
right: 0; | |
} | |
</style> | |
<h1><title>Goodreads Timeline</title></h1> | |
<ul class="books"> | |
</ul> | |
<script> | |
(function(a,b){if(typeof define==="function"&&define.amd){define([],b);}else{if(typeof exports==="object"){module.exports=b();}else{a.X2JS=b();}}}(this,function(){return function(z){var t="1.2.0";z=z||{};i();u();function i(){if(z.escapeMode===undefined){z.escapeMode=true;}z.attributePrefix=z.attributePrefix||"_";z.arrayAccessForm=z.arrayAccessForm||"none";z.emptyNodeForm=z.emptyNodeForm||"text";if(z.enableToStringFunc===undefined){z.enableToStringFunc=true;}z.arrayAccessFormPaths=z.arrayAccessFormPaths||[];if(z.skipEmptyTextNodesForObj===undefined){z.skipEmptyTextNodesForObj=true;}if(z.stripWhitespaces===undefined){z.stripWhitespaces=true;}z.datetimeAccessFormPaths=z.datetimeAccessFormPaths||[];if(z.useDoubleQuotes===undefined){z.useDoubleQuotes=false;}z.xmlElementsFilter=z.xmlElementsFilter||[];z.jsonPropertiesFilter=z.jsonPropertiesFilter||[];if(z.keepCData===undefined){z.keepCData=false;}}var h={ELEMENT_NODE:1,TEXT_NODE:3,CDATA_SECTION_NODE:4,COMMENT_NODE:8,DOCUMENT_NODE:9};function u(){}function x(B){var C=B.localName;if(C==null){C=B.baseName;}if(C==null||C==""){C=B.nodeName;}return C;}function r(B){return B.prefix;}function s(B){if(typeof(B)=="string"){return B.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'");}else{return B;}}function k(B){return B.replace(/</g,"<").replace(/>/g,">").replace(/"/g,'"').replace(/'/g,"'").replace(/&/g,"&");}function w(C,F,D,E){var B=0;for(;B<C.length;B++){var G=C[B];if(typeof G==="string"){if(G==E){break;}}else{if(G instanceof RegExp){if(G.test(E)){break;}}else{if(typeof G==="function"){if(G(F,D,E)){break;}}}}}return B!=C.length;}function n(D,B,C){switch(z.arrayAccessForm){case"property":if(!(D[B] instanceof Array)){D[B+"_asArray"]=[D[B]];}else{D[B+"_asArray"]=D[B];}break;}if(!(D[B] instanceof Array)&&z.arrayAccessFormPaths.length>0){if(w(z.arrayAccessFormPaths,D,B,C)){D[B]=[D[B]];}}}function a(G){var E=G.split(/[-T:+Z]/g);var F=new Date(E[0],E[1]-1,E[2]);var D=E[5].split(".");F.setHours(E[3],E[4],D[0]);if(D.length>1){F.setMilliseconds(D[1]);}if(E[6]&&E[7]){var C=E[6]*60+Number(E[7]);var B=/\d\d-\d\d:\d\d$/.test(G)?"-":"+";C=0+(B=="-"?-1*C:C);F.setMinutes(F.getMinutes()-C-F.getTimezoneOffset());}else{if(G.indexOf("Z",G.length-1)!==-1){F=new Date(Date.UTC(F.getFullYear(),F.getMonth(),F.getDate(),F.getHours(),F.getMinutes(),F.getSeconds(),F.getMilliseconds()));}}return F;}function q(D,B,C){if(z.datetimeAccessFormPaths.length>0){var E=C.split(".#")[0];if(w(z.datetimeAccessFormPaths,D,B,E)){return a(D);}else{return D;}}else{return D;}}function b(E,C,B,D){if(C==h.ELEMENT_NODE&&z.xmlElementsFilter.length>0){return w(z.xmlElementsFilter,E,B,D);}else{return true;}}function A(D,J){if(D.nodeType==h.DOCUMENT_NODE){var K=new Object;var B=D.childNodes;for(var L=0;L<B.length;L++){var C=B.item(L);if(C.nodeType==h.ELEMENT_NODE){var I=x(C);K[I]=A(C,I);}}return K;}else{if(D.nodeType==h.ELEMENT_NODE){var K=new Object;K.__cnt=0;var B=D.childNodes;for(var L=0;L<B.length;L++){var C=B.item(L);var I=x(C);if(C.nodeType!=h.COMMENT_NODE){var H=J+"."+I;if(b(K,C.nodeType,I,H)){K.__cnt++;if(K[I]==null){K[I]=A(C,H);n(K,I,H);}else{if(K[I]!=null){if(!(K[I] instanceof Array)){K[I]=[K[I]];n(K,I,H);}}(K[I])[K[I].length]=A(C,H);}}}}for(var E=0;E<D.attributes.length;E++){var F=D.attributes.item(E);K.__cnt++;K[z.attributePrefix+F.name]=F.value;}var G=r(D);if(G!=null&&G!=""){K.__cnt++;K.__prefix=G;}if(K["#text"]!=null){K.__text=K["#text"];if(K.__text instanceof Array){K.__text=K.__text.join("\n");}if(z.stripWhitespaces){K.__text=K.__text.trim();}delete K["#text"];if(z.arrayAccessForm=="property"){delete K["#text_asArray"];}K.__text=q(K.__text,I,J+"."+I);}if(K["#cdata-section"]!=null){K.__cdata=K["#cdata-section"];delete K["#cdata-section"];if(z.arrayAccessForm=="property"){delete K["#cdata-section_asArray"];}}if(K.__cnt==0&&z.emptyNodeForm=="text"){K="";}else{if(K.__cnt==1&&K.__text!=null){K=K.__text;}else{if(K.__cnt==1&&K.__cdata!=null&&!z.keepCData){K=K.__cdata;}else{if(K.__cnt>1&&K.__text!=null&&z.skipEmptyTextNodesForObj){if((z.stripWhitespaces&&K.__text=="")||(K.__text.trim()=="")){delete K.__text;}}}}}delete K.__cnt;if(z.enableToStringFunc&&(K.__text!=null||K.__cdata!=null)){K.toString=function(){return(this.__text!=null?this.__text:"")+(this.__cdata!=null?this.__cdata:"");};}return K;}else{if(D.nodeType==h.TEXT_NODE||D.nodeType==h.CDATA_SECTION_NODE){return D.nodeValue;}}}}function o(I,F,H,C){var E="<"+((I!=null&&I.__prefix!=null)?(I.__prefix+":"):"")+F;if(H!=null){for(var G=0;G<H.length;G++){var D=H[G];var B=I[D];if(z.escapeMode){B=s(B);}E+=" "+D.substr(z.attributePrefix.length)+"=";if(z.useDoubleQuotes){E+='"'+B+'"';}else{E+="'"+B+"'";}}}if(!C){E+=">";}else{E+="/>";}return E;}function j(C,B){return"</"+(C.__prefix!=null?(C.__prefix+":"):"")+B+">";}function v(C,B){return C.indexOf(B,C.length-B.length)!==-1;}function y(C,B){if((z.arrayAccessForm=="property"&&v(B.toString(),("_asArray")))||B.toString().indexOf(z.attributePrefix)==0||B.toString().indexOf("__")==0||(C[B] instanceof Function)){return true;}else{return false;}}function m(D){var C=0;if(D instanceof Object){for(var B in D){if(y(D,B)){continue;}C++;}}return C;}function l(D,B,C){return z.jsonPropertiesFilter.length==0||C==""||w(z.jsonPropertiesFilter,D,B,C);}function c(D){var C=[];if(D instanceof Object){for(var B in D){if(B.toString().indexOf("__")==-1&&B.toString().indexOf(z.attributePrefix)==0){C.push(B);}}}return C;}function g(C){var B="";if(C.__cdata!=null){B+="<![CDATA["+C.__cdata+"]]>";}if(C.__text!=null){if(z.escapeMode){B+=s(C.__text);}else{B+=C.__text;}}return B;}function d(C){var B="";if(C instanceof Object){B+=g(C);}else{if(C!=null){if(z.escapeMode){B+=s(C);}else{B+=C;}}}return B;}function p(C,B){if(C===""){return B;}else{return C+"."+B;}}function f(D,G,F,E){var B="";if(D.length==0){B+=o(D,G,F,true);}else{for(var C=0;C<D.length;C++){B+=o(D[C],G,c(D[C]),false);B+=e(D[C],p(E,G));B+=j(D[C],G);}}return B;}function e(I,H){var B="";var F=m(I);if(F>0){for(var E in I){if(y(I,E)||(H!=""&&!l(I,E,p(H,E)))){continue;}var D=I[E];var G=c(D);if(D==null||D==undefined){B+=o(D,E,G,true);}else{if(D instanceof Object){if(D instanceof Array){B+=f(D,E,G,H);}else{if(D instanceof Date){B+=o(D,E,G,false);B+=D.toISOString();B+=j(D,E);}else{var C=m(D);if(C>0||D.__text!=null||D.__cdata!=null){B+=o(D,E,G,false);B+=e(D,p(H,E));B+=j(D,E);}else{B+=o(D,E,G,true);}}}}else{B+=o(D,E,G,false);B+=d(D);B+=j(D,E);}}}}B+=d(I);return B;}this.parseXmlString=function(D){var F=window.ActiveXObject||"ActiveXObject" in window;if(D===undefined){return null;}var E;if(window.DOMParser){var G=new window.DOMParser();var B=null;if(!F){try{B=G.parseFromString("INVALID","text/xml").getElementsByTagName("parsererror")[0].namespaceURI;}catch(C){B=null;}}try{E=G.parseFromString(D,"text/xml");if(B!=null&&E.getElementsByTagNameNS(B,"parsererror").length>0){E=null;}}catch(C){E=null;}}else{if(D.indexOf("<?")==0){D=D.substr(D.indexOf("?>")+2);}E=new ActiveXObject("Microsoft.XMLDOM");E.async="false";E.loadXML(D);}return E;};this.asArray=function(B){if(B===undefined||B==null){return[];}else{if(B instanceof Array){return B;}else{return[B];}}};this.toXmlDateTime=function(B){if(B instanceof Date){return B.toISOString();}else{if(typeof(B)==="number"){return new Date(B).toISOString();}else{return null;}}};this.asDateTime=function(B){if(typeof(B)=="string"){return a(B);}else{return B;}};this.xml2json=function(B){return A(B);};this.xml_str2json=function(B){var C=this.parseXmlString(B);if(C!=null){return this.xml2json(C);}else{return null;}};this.json2xml_str=function(B){return e(B,"");};this.json2xml=function(C){var B=this.json2xml_str(C);return this.parseXmlString(B);};this.getVersion=function(){return t;};};})); | |
const container = document.querySelector('.books'), | |
userId = '19858779', | |
key = 'a1b2c3d4e5f6g7h8i9j0' | |
const buildHtml = (books) => books.forEach(book => { | |
container.insertAdjacentHTML('beforeend', ` | |
<li class="book${book.has_cover === false ? ' book--without-cover' : ''}"> | |
<a class="book__cover" href="${book.url}" title="${book.description}"> | |
<img src="${book.image}" /> | |
</a> | |
<a class="book__title" href="${book.url}">${book.title}</a> | |
<span class="book__published">${book.published}</span> | |
</li> | |
`) | |
}); | |
const xmlToJson = xml => { | |
return (new X2JS()).xml_str2json( xml ).GoodreadsResponse.reviews.review.map(data => ({ | |
format: data.book.format, | |
pages: data.book.num_pages, | |
publisher: data.book.publisher, | |
rating: data.book.average_rating, | |
rating_count: data.book.ratings_count, | |
series: data.book.title, | |
user_rating: data.rating, | |
user_read: `${new Date(data.read_at).getFullYear()}-${String(new Date(data.read_at).getMonth() + 1).padStart(2, '0')}-${String(new Date(data.read_at).getUTCDay()).padStart(2, '0')}`, | |
// | |
description: data.book.description.replace(/[\u00A0-\u9999<>\&]/gim, function(i) {return '&#'+i.charCodeAt(0)+';';}), | |
has_cover: data.book.image_url.lastIndexOf('nophoto') == -1, | |
image: data.book.image_url, | |
published: data.book.publication_year, // `${data.book.publication_year}-${data.book.publication_month.padStart(2, '0')}-${data.book.publication_day.padStart(2, '0')}`, | |
title: data.book.title_without_series, | |
url: data.book.link, | |
})) | |
} | |
const fetchJson = (userId, key) => fetch(new Request( | |
'https://cors-anywhere.herokuapp.com/' + | |
`https://www.goodreads.com/review/list?v=2&id=${userId}&key=${key}&shelf=read&sort=date_read&per_page=20` | |
)) | |
.then(response => response.ok ? response.text() : [{ | |
errors: `Could not fetch '${response.url}': ${response.statusText} (${response.status})`, | |
}]) | |
fetchJson(userId, key) | |
.then(data => xmlToJson(data)) | |
.then(data => buildHtml(data)) | |
.catch(error => (container.insertAdjacentHTML('beforeend', `<li>${error}</li>`))) | |
</script> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment