Skip to content

Instantly share code, notes, and snippets.

@fuddl
Last active March 27, 2020 21:41
Show Gist options
  • Save fuddl/3fab12d74b52bb9b150d89ead0ac114c to your computer and use it in GitHub Desktop.
Save fuddl/3fab12d74b52bb9b150d89ead0ac114c to your computer and use it in GitHub Desktop.
Userscript Wikia → Wikidata
// ==UserScript==
// @name WikiaData
// @namespace https://gist.github.com/fuddl
// @version 1.0.7
// @description Enrich Wikia with wikidata ids. Enrich wikidata with wikia ids.
// @updateURL https://gist.githubusercontent.com/fuddl/3fab12d74b52bb9b150d89ead0ac114c/raw/wikia-data.user.js
// @downloadURL https://gist.githubusercontent.com/fuddl/3fab12d74b52bb9b150d89ead0ac114c/raw/wikia-data.user.js
// @author fuddl
// @match https://*.fandom.com/*
// @grant GM_xmlhttpRequest
// ==/UserScript==
(function() {
let mode = 'browsing';
let style = document.createElement('style');
let pageQid = '';
let selectedProperty = '';
let flipped = false;
style.appendChild(document.createTextNode('.wikia-module [type="search"] { border: 1px solid #a2a9b1; border-radius: 2px; padding: 0.4em 1.84615385em 0.4em 0.4em; width: 100% }'));
style.appendChild(document.createTextNode('.wikidata-entity-list { margin: 1em 0; padding-left: 1em;} .wikidata-entity-list > li { list-style-type: initial; }'));
style.appendChild(document.createTextNode('.wikidata-link::before { content: " (" }'));
style.appendChild(document.createTextNode('.wikidata-link::after { content: ")" }'));
style.appendChild(document.createTextNode('.mode-qid-select .wikidata-link { background: #339966; color:white; border-radius: 4px; font-size: 0.45em; line-height: 16px; display: inline-block; padding: 0 5px; margin: 0 5px; vertical-align: middle; border: 1px solid white; transition: transform .25s, background .25s }'));
style.appendChild(document.createTextNode('.mode-qid-select .wikidata-link::before, .mode-qid-select .wikidata-link::after { display: none }'));
style.appendChild(document.createTextNode('.mode-qid-select .wikidata-link--active, .mode-qid-select .wikidata-link:hover { background: #006699; text-decoration: none }'));
style.appendChild(document.createTextNode('.mode-qid-select .wikidata-link--already-present { background: #006699 }'));
style.appendChild(document.createTextNode('.wikia-module__mode-qid-select { position: sticky; top: 100px } .wikia-module__mode-qid-select ~ * {display: none !important}'));
document.querySelector('head').appendChild(style);
let itemList = {};
function addToItemList(id, title, section, hash) {
if (!(id in itemList)) {
itemList[id] = {
title: title,
hash: hash,
section: section
}
} else {
delete itemList[id];
}
console.log(itemList);
let allLinks = document.querySelectorAll('[data-wikidata]');
for (let link of allLinks) {
link.classList.remove('wikidata-link--active');
}
for (let key of Object.keys(itemList)) {
let links = document.querySelectorAll('[data-wikidata="' + key + '"]');
for (let link of links) {
link.classList.add('wikidata-link--active');
}
}
updateItemList();
updateSubmitButton();
}
function updateItemList() {
let list = document.querySelector('.wikidata-entity-list');
list.innerHTML = '';
if (list != null && Object.entries(itemList).length > 0) {
for (let key of Object.keys(itemList)) {
let listItem = document.createElement('li');
listItem.innerText = itemList[key].title;
list.appendChild(listItem);
}
}
}
function updateSubmitButton() {
let button = document.querySelector('.wikidata-add-quickstatements');
if (Object.entries(itemList).length > 0) {
button.removeAttribute('hidden');
let statements = [];
for (let key of Object.keys(itemList)) {
let item = itemList[key];
let srcUrl = location.href + '?oldid=' + mw.config.values.wgRevisionId;
if (item.hash) {
srcUrl = srcUrl + '#' + item.hash;
}
let statement = [
!flipped ? pageQid : key, selectedProperty, !flipped ? key : pageQid,
'S854', '"' + srcUrl + '"',
's813', getCurrentDate(),
's1810', '"' + item.title + '"',
];
if (item.section != null) {
statement.push('s958', '"' + item.section + '"');
}
statements.push(statement.join('|'));
}
localStorage.setItem('lastProp', selectedProperty);
button.setAttribute('href', 'https://tools.wmflabs.org/quickstatements/#/v1=' + encodeURIComponent(statements.join('||')));
} else {
button.setAttribute('hidden', true);
}
}
function getPageTitle() {
return document.querySelector('.page-header__title').innerText;
}
function getCurrentDate() {
let now = new Date();
return '+' + now.toISOString().substring(0,10) + 'T00:00:00Z/11'
}
function getClosestHeadline(element) {
let subject = element;
while (element.parentNode && !element.parentNode.classList.contains('mw-content-text')) {
element = element.parentNode;
}
while (element.previousElementSibling && !element.matches('h1, h2, h3, h4, h5, h6')) {
element = element.previousElementSibling;
}
if (element.children.length > 0 && element.children[0].matches('.mw-headline')) {
let span = element.children[0];
return {
section: span.innerText,
hash: span.getAttribute('id'),
};
}
return {section: null, hash: null};
}
function setUpConnecterBox(target) {
let moduleTitle = document.createElement('h2');
moduleTitle.innerText = 'Connect to Wikidata';
let moduleTitleIcon = document.createElement('img');
moduleTitleIcon.setAttribute('src', 'https://upload.wikimedia.org/wikipedia/commons/f/ff/Wikidata-logo.svg');
moduleTitleIcon.classList.add('wds-icon');
moduleTitleIcon.classList.add('wds-icon-small');
moduleTitle.classList.add('has-icon');
moduleTitle.prepend(moduleTitleIcon);
style.appendChild(document.createTextNode('.wikia-module .wds-icon { margin-right: 7px }'));
style.appendChild(document.createTextNode('.WikiaRail .wikia-module h2 { color: inherit; border-bottom: 1px solid #a2a9b1; }'));
let module = document.createElement('div');
module.classList.add('rail-module');
module.classList.add('wikia-module');
module.appendChild(moduleTitle);
style.appendChild(document.createTextNode('.wikia-module { border: 1px solid #a7d7f9; background: white; padding: 1em; color: #222222; font-family: sans-serif; }'));
let rail = document.getElementById('WikiaRail');
rail.prepend(module);
if (typeof target != 'string') {
let query = document.createElement('input');
query.setAttribute('type', 'search');
let title = getPageTitle();
query.value = title;
module.appendChild(query);
let suggestions = document.createElement('ol');
suggestions.classList.add('wikia-suggestions');
style.appendChild(document.createTextNode('.wikia-suggestions > li { margin-top: .5em }'));
style.appendChild(document.createTextNode('.wikia-suggestions__link { color: inherit; display: block; padding: 1em; border: 1px solid #C9C9C9 } .wikia-suggestions__link:hover { background-color: #4C59A6; color: white; text-decoration: none }'));
style.appendChild(document.createTextNode('.wikia-suggestions__label { font-weight: bold }'));
module.appendChild(suggestions);
let updateSuggestions = function() {
let request = new XMLHttpRequest();
GM_xmlhttpRequest({
method: "GET",
url: labelQuery(query.value),
onload: function(response) {
suggestions.innerText = '';
let answer = JSON.parse(response.responseText);
answer.search.push({
label: getPageTitle(),
description: "Create a new entity",
id: null,
});
for (let suggestion of answer.search) {
let item = document.createElement('li');
let link = document.createElement('a');
link.classList.add('wikia-suggestions__link');
let title = document.createElement('div');
title.classList.add('wikia-suggestions__label');
title.innerText = suggestion.label;
let desc = document.createElement('div');
desc.innerText = suggestion.description;
link.href = createConnecterQS(getWikiaId(location), suggestion.id);
link.appendChild(title);
link.appendChild(desc);
item.appendChild(link);
suggestions.appendChild(item);
}
}
});
}
query.addEventListener('focus', updateSuggestions);
query.addEventListener('keyup', updateSuggestions);
query.addEventListener('change', updateSuggestions);
updateSuggestions();
} else {
let propsUrl = 'https://tools.wmflabs.org/hay/propbrowse/props.json';
GM_xmlhttpRequest({
method: "GET",
url: propsUrl,
onload: function(response) {
let propSelector = document.createElement('input');
propSelector.setAttribute('type', "search")
propSelector.setAttribute('list', "wikidata-property-list");
propSelector.setAttribute('placeholder', "add statement to " + getPageTitle());
module.appendChild(propSelector);
let datalist = document.createElement('datalist');
datalist.setAttribute('id', "wikidata-property-list");
module.appendChild(datalist);
let flipCheckbox = document.createElement('input');
flipCheckbox.setAttribute('type', 'checkbox');
flipCheckbox.setAttribute('id', 'wikidata-page-is-object');
module.appendChild(flipCheckbox);
let flipCheckboxLabel = document.createElement('label');
flipCheckboxLabel.setAttribute('for', 'wikidata-page-is-object');
flipCheckboxLabel.innerText = 'treat as object of statement.'
module.appendChild(flipCheckboxLabel);
flipCheckbox.addEventListener('change', function() {
flipped = flipCheckbox.checked ? true : false;
});
let itemList = document.createElement('ul');
itemList.classList.add('wikidata-entity-list');
module.appendChild(itemList);
let submit = document.createElement('a');
submit.classList.add('wikidata-add-quickstatements');
submit.setAttribute('hidden', true);
submit.innerText = 'Send to Quickstatements';
module.appendChild(submit);
let props = JSON.parse(response.responseText);
for (let prop of props) {
if (prop.datatype === 'wikibase-item') {
let option = document.createElement('option');
option.setAttribute('value', prop.label + ' (' + prop.id + ')');
datalist.appendChild(option);
}
}
let updatePropSelector = function() {
if (propSelector.value.match(/(P\d+)(\)|$)/)) {
mode = 'qid-select';
selectedProperty = propSelector.value.match(/(P\d+)(\)|$)/)[1];
document.querySelector('.WikiaArticle').classList.add('mode-qid-select');
document.querySelector('.wikia-module').classList.add('wikia-module__mode-qid-select');
checkIfStatementIsAlreadyPresent();
} else {
mode = 'browsing';
document.querySelector('.WikiaArticle').classList.remove('mode-qid-select');
}
}
propSelector.addEventListener('change', updatePropSelector);
propSelector.addEventListener('keyup', updatePropSelector);
propSelector.addEventListener('blur', updatePropSelector);
propSelector.addEventListener('click', function() {
if (localStorage.getItem('lastProp') && propSelector.value == '') {
propSelector.value = localStorage.getItem('lastProp');
propSelector.setSelectionRange(0, propSelector.value.length);
updatePropSelector();
}
});
}
});
}
}
function createConnecterQS(id, qid) {
let prefix = '';
if (qid === null) {
qid = "LAST";
prefix = 'CREATE||' + [qid, "L" + document.documentElement.lang, '"' + getPageTitle() + '"'].join('|') + '||';
}
let statement = [
qid, 'P6262', '"' + id + '"',
'S854', '"' + location.href + '?oldid=' + mw.config.values.wgRevisionId + '"',
's813', getCurrentDate(),
's1810', '"' + getPageTitle() + '"',
];
return 'https://tools.wmflabs.org/quickstatements/#/v1=' + encodeURIComponent(prefix + statement.join('|'));
}
function labelQuery(input) {
return "https://www.wikidata.org/w/api.php?action=wbsearchentities&limit=20&language=" + document.documentElement.lang + "&format=json&search=" + input;
}
function getWikiaId(location) {
let subdomain = location.host.split('.')[0];
let languagePrefixExtract = location.pathname.match(/^\/([a-z]{2})\//);
let languagePrefix = '';
if (languagePrefixExtract && languagePrefixExtract.length == 2) {
languagePrefix = languagePrefixExtract[1] + '.';
}
let articleTitle = decodeURI(location.pathname.match(/\/wiki\/(.*)$/)[1]).replace(' ', '_');
return languagePrefix + subdomain + ':' + articleTitle;
}
function getQidFromWikiaId(wikiaId, callback, failCallback) {
let query = "SELECT ?item ?itemLabel";
query += ' WHERE { ?item wdt:P6262 "' + wikiaId + '". SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en". } } #1';
let queryUrl = 'https://query.wikidata.org/sparql?format=json&query=' + encodeURIComponent(query);
let request = new XMLHttpRequest();
request.open('GET', queryUrl);
request.send();
request.onreadystatechange = function(request) {
let response = request.target;
if (response.readyState == 4 && response.status === 200) {
let answer = JSON.parse(response.responseText);
if (answer.results.bindings[0]) {
let wdurl = answer.results.bindings[0].item.value;
let wdId = wdurl.replace("http://www.wikidata.org/entity/", '');
callback({wdurl: wdurl, wdId: wdId});
} else if (typeof failCallback === "function") {
failCallback();
}
}
if (response.readyState == 4 && response.status > 400) {
setTimeout(function() {
request.send();
}, 1000);
}
};
}
function addLinkToTitle() {
let currentLocation = window.location;
let wikiaId = getWikiaId(currentLocation);
getQidFromWikiaId(wikiaId, function(answer) {
let link = document.createElement('a');
link.setAttribute('href', answer.wdurl);
link.innerText = " (" + answer.wdId + ")";
let title = document.querySelector("h1");
title.appendChild(link);
setUpConnecterBox(answer.wdId);
pageQid = answer.wdId;
}, function() {
setUpConnecterBox();
});
}
function addLinkToLink(link) {
let wikiaId = getWikiaId(link);
getQidFromWikiaId(wikiaId, function(answer) {
let appendlink = document.createElement('a');
appendlink.setAttribute('href', answer.wdurl);
appendlink.innerText = answer.wdId;
appendlink.setAttribute('data-wikidata', answer.wdId);
appendlink.setAttribute('data-title', link.innerText);
appendlink.classList.add('wikidata-link');
link.parentElement.insertBefore(appendlink, link.nextSibling);
appendlink.addEventListener('click', function(e){
if (mode == 'qid-select') {
e.preventDefault();
let title = getClosestHeadline(link);
addToItemList(answer.wdId, link.innerText, title.section, title.hash, link);
}
});
});
}
function checkIfStatementIsAlreadyPresent() {
let allLinks = document.querySelectorAll('[data-wikidata]');
for (let link of allLinks) {
link.classList.remove('wikidata-link--already-present');
}
for (let link of allLinks) {
let subject = pageQid;
let object = link.getAttribute('data-wikidata');
let url = location.href.replace(/(\?|\#).*/,'');
let prop = selectedProperty;
let query = "ASK { wd:" + subject + " p:" + prop + " ?stmt . ?stmt ps:" + prop + " wd:" + object + " . ?stmt prov:wasDerivedFrom ?ref . ?ref pr:P854 ?url . FILTER(STRSTARTS(str(?url), '" + url + "')) }";
let queryUrl = 'https://query.wikidata.org/sparql?format=json&query=' + encodeURIComponent(query);
let request = new XMLHttpRequest();
request.open('GET', queryUrl);
request.send();
request.onreadystatechange = function(request) {
let response = request.target;
if (response.readyState == 4 && response.status === 200) {
let answer = JSON.parse(response.responseText);
console.log(query);
console.log(answer.boolean);
if (answer.boolean === true) {
link.classList.add('wikidata-link--already-present');
}
}
if (response.readyState == 4 && response.status > 400) {
setTimeout(function() {
request.send();
}, 1000);
}
};
}
}
function addLinkToLinks() {
let links = document.querySelectorAll('.mw-content-text a[href^="/"]:not([href*="?action"])');
let i = 0;
for (let link of links) {
setTimeout(function(){
if(link.classList.contains('mw-redirect')) {
resolveRedirect(link, function() {
addLinkToLink(link);
});
}
addLinkToLink(link);
}, i * 10);
i++;
}
}
function extractCanonocal(markup) {
return markup.match(/<link rel="canonical" href="([^"]+)" \/>/)[1];
}
function resolveRedirect(link, callback) {
let request = new XMLHttpRequest();
request.open('GET', link.href, true);
request.send();
request.onreadystatechange = function(request) {
let response = request.target;
if (response.readyState == 4 && response.status === 200) {
link.href = extractCanonocal(response.responseText);
callback();
}
};
}
'use strict';
addLinkToTitle();
addLinkToLinks();
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment