-
-
Save chrisaldrich/031c946446b1bc070b1937f330989c63 to your computer and use it in GitHub Desktop.
Hypothes.is - retrieve your annotations into Obsidian (for templater plugin)
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
<%* | |
/* | |
# Hypothes.idian a templater script for retrieving annotations from Hypothes.is | |
Dev: RoamHacker https://twitter.com/roamhacker | |
# Prerequisites: | |
+ Templater plugin by https://github.com/SilentVoid13/Templater | |
+ Free Hypothes.is developer token from: https://hypothes.is/account/developer | |
+ This script will prompt you for his token and save it to a file called "hypothesis config.md" | |
+ This file store your configuration and can be located any where in your vault. | |
+ Since it contains your unique user token, you should not share this file with others. | |
# Features: | |
+ Retrieve your annotations for a web article/web PDF' | |
+ Retrieve your annotations from a date | |
+ Open a URL in hypothes.is for annotation | |
+ Retrieve ALL user annotations for a web article/web PDF | |
# Output: | |
+ If an empty document, descriptive front matter is output to beginning of the document, followed by annotations | |
+ If document already contains text, the annotations are inserted at the current location | |
# Update Log: | |
+ 2021-05-04 fix that they config file can be located anywhere | |
+ 2021-04-26 First alpha version released to testers | |
*/ | |
const configFileName = 'hypothesis config.md'; | |
let userToken = ''; | |
let userid = ''; | |
const apiUrl = 'https://api.hypothes.is/api/'; | |
const apiHTTPGet = async (apiCall, data) => { | |
return await fetch(apiCall, { | |
"method": "GET", cache: 'no-cache', | |
"headers": { "Authorization": "Bearer " + userToken } | |
}).then(async(data)=> await data.json() ) | |
} | |
const getAllAnnotations = async (articleUrl)=> { | |
const searchUrl = `search?limit=200&order=asc&uri=${encodeURIComponent(articleUrl)}`; | |
const results = await apiHTTPGet(`${apiUrl}${searchUrl}`); | |
return await apiAnnotationSimplify(results); | |
} | |
const getMyAnnotations = async (articleUrl)=> { | |
const searchUrl = `search?limit=200&user=${userid}&order=asc&uri=${encodeURIComponent(articleUrl)}`; | |
const results = await apiHTTPGet(`${apiUrl}${searchUrl}`); | |
return await apiAnnotationSimplify(results); | |
} | |
const getAnnotationsSinceDate = async (fromDate)=> { | |
const searchUrl = `search?limit=200&user=${userid}&sort=updated&order=asc&search_after=${encodeURIComponent(fromDate)}`; | |
const results = await apiHTTPGet(`${apiUrl}${searchUrl}`); | |
return await apiAnnotationSimplify(results); | |
} | |
const apiAnnotationSimplify = async (results)=>{ | |
return results.rows.map(e=>{ | |
var r = { | |
title: e.document.title[0], uri:e.uri, context: e.links.incontext, | |
text: e.text, highlight: '', tags: e.tags, | |
user: e.user, group:e.group, created: e.created, updated: e.updated, | |
}; | |
try { | |
if(e.target[0].selector) { | |
var txt = e.target[0].selector.filter(e=>e.type=='TextQuoteSelector'); | |
if(txt) r.highlight = txt[0].exact; | |
} | |
} catch(e){}; | |
return r; | |
}); | |
} | |
const openArticleInHypothesis = async (articleUrl)=> { | |
window.open('https://via.hypothes.is/' + articleUrl, '_blank'); | |
} | |
const getUserProfile = async ()=> await apiHTTPGet(`${apiUrl}profile`); | |
const configFile = await app.vault.getFiles().find(f => f.name == 'hypothesis config.md'); | |
//Setup configuration file | |
if (configFile == undefined ) { | |
userToken = await tp.system.prompt("Hypothes.is user token from https://hypothes.is/account/developer"); | |
console.log(userToken) | |
if (userToken==null || userToken.length==0) return; | |
const userProfile = await getUserProfile(); | |
userid = userProfile.userid; | |
if( userToken.length>0 && userid != null ){ | |
const fileOutput = `---\nhypothesisUserToken: ${userToken} \n` + | |
`hypothesisUserID: ${userid} \n` + | |
`---\n\nThis file can be place anywhere in your vault.\n\n` + | |
`get your token here: https://hypothes.is/account/developer` | |
await app.vault.create(configFileName,fileOutput); | |
} | |
} else { | |
//load user token | |
if (app.metadataCache.metadataCache[ app.metadataCache.fileCache[configFile.path].hash ]?.frontmatter?.hypothesisUserToken) { | |
userToken = app.metadataCache.metadataCache[ app.metadataCache.fileCache[configFile.path].hash ].frontmatter.hypothesisUserToken; | |
userid = app.metadataCache.metadataCache[ app.metadataCache.fileCache[configFile.path].hash ].frontmatter.hypothesisUserID; | |
} | |
} | |
if(userToken.length == 0 || userid == null) { | |
new Notice(`No user token or is invalid. Try deleting ${configFileName} and restarting the script.`) | |
return ''; | |
} | |
const selectedText = tp.file.selection(); | |
let articleAnnotations = null; | |
let articleURL = null; | |
let insertUser = false; | |
//find out the type of action | |
const hypothesisAction = await tp.system.suggester( | |
[ 'Retrieve my annotations for a web article/web PDF', | |
'Retrieve my annotations from a date', | |
'Open URL in Hypothes.is for annotation (select a URL or type url)', | |
'Retrieve ALL annotations for a web article/web PDF', | |
], | |
['myarticle', 'mydate', 'openURL', 'allAnnotations']) | |
switch (hypothesisAction) { | |
case 'myarticle': | |
articleURL = await tp.system.prompt('URL:', selectedText); | |
if(articleURL==null || articleURL.length==0) return ''; | |
articleAnnotations = await getMyAnnotations(articleURL); | |
break; | |
case 'allAnnotations': | |
articleURL = await tp.system.prompt('URL:', selectedText); | |
if(articleURL==null || articleURL.length==0) return ''; | |
articleAnnotations = await getAllAnnotations(articleURL); | |
insertUser = true; | |
break; | |
case 'openURL': | |
const articleURLtoOpen = await tp.system.prompt('URL to open in Hypothes.is:', selectedText ); | |
await openArticleInHypothesis(articleURLtoOpen); | |
return; | |
break; | |
case 'mydate': | |
const articleDates = await tp.system.prompt('Retrieve article titles from date:', tp.date.now('YYYY-MM-DD', -7) ); | |
if(articleDates==null || articleDates.length==0) return ''; | |
articleAnnotations = await getAnnotationsSinceDate(articleDates); | |
if (articleAnnotations.length==0) { | |
new Notice('no results for this date range'); | |
return; | |
} | |
const articlesNames = [...new Set( articleAnnotations.map(e=>e.title + ' \n(' + e.uri + ')' ))]; | |
const articlesURIs = [...new Set( articleAnnotations.map(e=>e.uri))]; | |
articleURL = await tp.system.suggester(articlesNames, articlesURIs) | |
if(articleURL==null || articleURL.length==0) return ''; | |
articleAnnotations = await getMyAnnotations(articleURL); | |
break; | |
} | |
if (articleAnnotations==null || articleAnnotations.length == 0) return ''; | |
/* TEMPLATE STARTS HERE */ | |
if (tp.file.content.length==0) { | |
//likely a new document, insert front matter | |
tR += `---\n`; | |
tR += `fileType: HypothesisAnnotations\n`; | |
tR += `creationDate: ${tp.date.now('YYYY-MM-DD')} \n`; | |
tR += `annotationDate: ${articleAnnotations[0].created.substring(0,10)}\n`; | |
tR += `uri: ${articleAnnotations[0].uri}\n`; | |
tR += `---\n`; | |
} | |
tR += `# ${articleAnnotations[0].title}\n` | |
tR += `URL: ${articleAnnotations[0].uri}\n\n` | |
for( a of articleAnnotations) { | |
let tags = ''; | |
let user = ''; | |
if(a.tags.length>0) tags = ' ' + (a.tags.map(t=> '[['+ t + ']]')).join(' '); | |
if(insertUser) user = ' _(' + a.user.replace('acct:','').replace('@hypothes.is','') + ')_'; | |
if(a.text) tR += `${a.text}\n—[[${user}]]\n\n`; | |
tR += `## Source \n`; | |
tR += `> ${a.highlight}[^1]\n\n`; | |
tR += `[^1]: [${articleAnnotations[0].title}](${articleAnnotations[0].uri}) | [syndication link](tk) \n`; | |
tR += `\n---\ntags: \nlinks: ${tags} \n- broader terms (BT): \n- narrower terms (NT): \n- related terms (RT): \n- used for (UF) or aliases: \nconnected ideas: \nMOC: \n\n---\n`; | |
} | |
%> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Details for the use of this code can be found at https://forum.obsidian.md/t/retrieve-annotations-for-hypothes-is-via-templater-plugin-hypothes-idian/17225
I've only modified the section at the bottom that follows the line
/* TEMPLATE STARTS HERE */
The changes still keep all the relevant data fields, but reorder them and add a bit of formatting to fit the layout and the way I use my Obsidian notebook. I changed the formatting so that tags in Hypothes.is are changed into [[wikilinks]] rather than #hashtags as in the original.
Hopefully the small changes I've made and comparison with the original https://gist.github.com/roamhacker/c48bca69f1520deed0ecbc8840f6241a will provide those who aren't as code-savy to better understand the template.