-
-
Notifications
You must be signed in to change notification settings - Fork 83
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 3c635f9
Showing
16 changed files
with
1,405 additions
and
0 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
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
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. |
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
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 | ||
}) | ||
` | ||
} | ||
} |
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
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://www.w3.org/2000/svg" xmlns:xlink="http://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() | ||
} |
Oops, something went wrong.