Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
stefanw committed Mar 22, 2020
0 parents commit 3c635f9
Show file tree
Hide file tree
Showing 16 changed files with 1,405 additions and 0 deletions.
674 changes: 674 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# VOEBBot

Browser extension that uses your [VÖBB](https://www.voebb.de/) public library account (just 15 € a year!) to remove the paywall on print articles of German online news sites.

## How it works

Your VÖBB account includes access to [genios](https://www.genios.de/) and [Munzinger](https://www.munzinger.de/) which gives you access to the print editions of most German newspapers.

When you browse the websites of German news sites the extension will detect the paywall and in the background log in to the library service, search for the article and inject the content of the article into the news site.

## Setup

This extension is in alpha development. You can clone the repository and load the directory as an unpacked extension to try it out.

Unless your browser autofills your credentials on the *various* VÖBB services, you can give the extension your 11-digit user id and password via its options page.

## Currently supported sites

- www.spiegel.de
- magazin.spiegel.de
- www.zeit.de

Caveat: the article may not be found in the library service (print articles take apparently longer to show up). Also online media has online exclusives that likely do not appear in the library services.

Many more sites are possible, please help to add more.
210 changes: 210 additions & 0 deletions background/background.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
const voebbLogin = [
{fill: {selector: 'input[name="L#AUSW"]', key: "username"}},
{fill: {selector: 'input[name="LPASSW"]', key: "password"}},
{click: 'input[name="LLOGIN"]'},
]
const providers = {
"www.munzinger.de": {
init: "https://www.munzinger.de/search/go/spiegel/aktuell.jsp?portalid=50158",
loggedIn: ".metanav-a[href='/search/logout']",
login: [
[
{click: "#redirect"}
],
voebbLogin,
[
{click: 'input[name="CLOGIN"]'}
]
],
search: [
[
{url: "https://www.munzinger.de/search/query?template=%2Fpublikationen%2Fspiegel%2Fresult.jsp&query.id=query-spiegel&query.key=gQynwrIS&query.commit=yes&query.scope=spiegel&query.index-order=personen&query.facets=yes&facet.path=%2Fspiegel&facet.activate=yes&hitlist.highlight=yes&hitlist.sort=-field%3Aisort&query.Titel={title}&query.Ausgabe={edition}&query.Ressort=&query.Signatur=&query.Person=&query.K%C3%B6rperschaft=&query.Ort=&query.Text={overline}"},
],
[
{extract: ".mitte-text"}
]
]
},
"bib-voebb.genios.de": {
init: "https://bib-voebb.genios.de/",
loggedIn: ".boxLogin a[href='/openIdConnectClient/logout']",
login: [
voebbLogin
],
search: [
[
{url: "https://bib-voebb.genios.de/dosearch?explicitSearch=true&q=&dbShortcut=%3A5%3A1%3A2%3AZEIT&searchMask=5754&TI%2CUT%2CDZ%2CBT%2COT%2CSL={title}&AU=&KO=&MM%2COW%2CUF%2CMF%2CAO%2CTP%2CVM%2CNN%2CNJ%2CKV%2CZ2=&CT%2CDE%2CZ4%2CKW=&Z3%2CCN%2CCE%2CKC%2CTC%2CVC=&DT_from=&DT_to=&timeFilterType=selected&timeFilter=NONE&x=59&y=11"}
],
[
{click: ".boxHeader"}
],
[
{extract: ".divDocument pre:nth-of-type(5)", convert: "preToParagraph"},
]
]
}
}




const converters = {
preToParagraph: function (htmlArr) {
return htmlArr.map(function(html) {
html = html.replace(/<pre[^>]+>/, '').replace(/<\/pre>/, '')
let parts = html.split('\n\n')
return `<p>${parts.join('</p><p>')}</p>`
})
}
}

const readers = []
const storageItems = {}

browser.storage.sync.get({username: '', password: ''}).then(function(items) {
for (var key in items) {
storageItems[key] = items[key]
}
})

function connected(p) {
const reader = {
port: p,
step: 0,
phase: 'login'
}
readers[p.sender.tab.id] = reader
p.onMessage.addListener(function(m) {
console.log('received message', m)
if (m.type === 'voebb-init') {
reader.provider = m.provider
reader.articleInfo = m.articleInfo
startProvider(reader)
}
})
}

browser.runtime.onConnect.addListener(connected)


function startProvider (reader) {
const provider = providers[reader.provider]
var creating = browser.tabs.create({
url: provider.init,
active: false,
});
creating.then(function tabCreated(tab) {
reader.tabId = tab.id
console.log('tab created', tab.id)
browser.tabs.onUpdated.addListener(function onTabUpdated (tabId, changeInfo, tabInfo) {
if (reader.done) {
return
}
if (tabId !== tab.id) {
return
}
if (changeInfo.status === 'complete') {
console.log('good tab complete', tabId)
initStep(reader)
}
});
});
}

function initStep (reader) {
const provider = providers[reader.provider]
loginTest(reader, provider).then(function(loggedIn) {
if (loggedIn) {
reader.step = 0
reader.phase = 'search'
}
runStep(reader, provider)
})
}

function loginTest (reader, provider) {
if (reader.phase === 'login' && reader.step === 0) {
return new Promise(function(resolve) {
browser.tabs.executeScript(reader.tabId, {
code: `document.querySelector("${provider.loggedIn}") !== null`
}).then(function(result) {
console.log('loggedin?', result);
resolve(result[0])
}, function(err) {
console.warn('Error after action', action, err)
})
})
}
return Promise.resolve(false)
}


function runStep (reader, provider) {
const actions = provider[reader.phase][reader.step]
const isFinal = reader.phase === 'search' && reader.step === provider[reader.phase].length - 1
actions.forEach(function(action) {
const actionCode = getActionCode(reader, action)
console.log(actionCode)
browser.tabs.executeScript(reader.tabId, {
code: actionCode
}).then(function(result) {
console.log('action', action, 'result', result)
result = result[0]
if (result.length > 0 && action.convert) {
result = converters[action.convert](result)
}
if (isFinal) {
if (result.length > 0) {
reader.port.postMessage({
type: "success",
content: result
})
browser.tabs.remove(reader.tabId)
} else {
console.warn('failed to find')
reader.port.postMessage({
type: "failed",
content: result
})
}
}
}, function(err) {
console.warn('Error after action', action, err)
})
})
if (isFinal) {
reader.done = true
}
reader.step += 1
if (reader.step > provider[reader.phase].length - 1) {
if (reader.phase === 'login') {
reader.phase = 'search'
}
reader.step = 0
}
}

function getActionCode (reader, action) {
if (action.fill) {
if (storageItems[action.fill.key]) {
return `document.querySelector('${action.fill.selector}').value = '${storageItems[action.fill.key]}'`
} else {
return `undefined`
}
} else if (action.click) {
return `document.querySelector('${action.click}').click()`
} else if (action.url) {
const vars = ['title', 'edition', 'overline']
let url = action.url
for (let v of vars) {
url = url.replace(new RegExp(`\{${v}\}`), encodeURIComponent(reader.articleInfo[v] || ''))
}
return `document.location.href = '${url}';`
} else if (action.extract) {
return `
Array.from(document.querySelectorAll('${action.extract}')).map(function(el) {
return el.outerHTML
})
`
}
}
134 changes: 134 additions & 0 deletions content/content.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
const readers = {
"magazin.spiegel.de": {
selectors: {
title: "#articles > article > header h1",
main: "#articles > article > main .paragraph",
edition: "body > footer > span.pvi",
paywall: "#preview"
},
provider: "www.munzinger.de"
},
"www.spiegel.de": {
selectors: {
title: ".leading-tight span:not(:first-child)",
overline: "article .text-primary-base",
main: "article section .clearfix",
mimic: "article section .clearfix .RichText",
paywall: "div[data-component='Paywall']"
},
provider: "www.munzinger.de"
},
"www.zeit.de": {
selectors: {
title: ".article-heading__title",
edition: ".zplus-badge__media-item@alt",
date: ".metadata__source.encoded-date",
paywall: ".gate.article__item",
main: ".article-page",
mimic: ".article-page .paragraph"
},
start: function () {
document.querySelector('.paragraph.article__item').classList.remove('paragraph--faded')
},
provider: "bib-voebb.genios.de"
}
}

const loader = `
<center id="voebbit-loader"><svg version="1.1" id="L4" xmlns="http:https://www.w3.org/2000/svg" xmlns:xlink="http:https://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 100 100" enable-background="new 0 0 0 0" xml:space="preserve"
style="margin: 0 auto;display:inline-block;width: 100px;height: 100px;">
<circle fill="#000" stroke="none" cx="6" cy="50" r="6">
<animate
attributeName="opacity"
dur="1s"
values="0;1;0"
repeatCount="indefinite"
begin="0.1"/>
</circle>
<circle fill="#000" stroke="none" cx="26" cy="50" r="6">
<animate
attributeName="opacity"
dur="1s"
values="0;1;0"
repeatCount="indefinite"
begin="0.2"/>
</circle>
<circle fill="#000" stroke="none" cx="46" cy="50" r="6">
<animate
attributeName="opacity"
dur="1s"
values="0;1;0"
repeatCount="indefinite"
begin="0.3"/>
</circle>
</svg></center>`


var articleInfo = {}
const port = browser.runtime.connect({name:"port-from-cs"});

function setupReader() {
for (const key in reader.selectors) {
if (reader.selectors[key]) {
const parts = reader.selectors[key].split('@')
const result = document.querySelector(parts[0])
if (result === null) {
articleInfo[key] = ''
continue
}
if (parts[1]) {
articleInfo[key] = result.attributes[parts[1]].value.trim()
} else {
articleInfo[key] = result.textContent.trim()
}
}
}

const paywall = document.querySelector(reader.selectors.paywall)
if (paywall === null) {
return
}

paywall.style.display = "none"

if (reader.start) {
reader.start()
}

port.postMessage({
"type": "voebb-init",
"provider": reader.provider,
"articleInfo": articleInfo
});

const main = document.querySelector(reader.selectors.main)
main.innerHTML = main.innerHTML + loader

port.onMessage.addListener(function(m) {
console.log(m);
if (m.type === 'failed') {
paywall.style.display = "block"
return
}
let content = m.content
if (reader.selectors.mimic) {
const mimic = document.querySelector(reader.selectors.mimic)
content = `<div class="${mimic.className}">${content}</div>`
}
main.innerHTML = content
if (reader.cleanup) {
reader.cleanup()
}
});


}

const host = document.location.host
const reader = readers[host]

if (reader !== undefined) {
console.log("setup reader!");
setupReader()
}
Loading

0 comments on commit 3c635f9

Please sign in to comment.