User:Alexander Misel/test2.js
外观
注意:保存之后,你必须清除浏览器缓存才能看到做出的更改。Google Chrome、Firefox、Microsoft Edge及Safari:按住⇧ Shift键并单击工具栏的“刷新”按钮。参阅Help:绕过浏览器缓存以获取更多帮助。
// ==UserScript==
// @name RecomWP
// @namespace http://tampermonkey.net/
// @version 0.2
// @description Wikipedia Recommendation System
// @homepage https://github.com/AlexanderMisel/RecomWP
// @supportURL https://github.com/AlexanderMisel/RecomWP/issues
// @author Alexander Misel
// @license GPL-3.0
// @match https://wiki.zwnes.eu.org/*
// @grant none
// ==/UserScript==
(function () {
// Polyfill for Wikipedia with no jQuery
if (Array.isArray(window.RLQ) && window.RLQ.length > 1) {
var tempMW = null;
if (window.mw) {
tempMW = window.mw;
}
window.mw = {
config: {
set: function (data) {
for (var key in data) {
window[key] = data[key];
}
}
},
loader: {
load: function (data) {},
state: function () {},
implement: function () {}
}
};
window.RLQ[0]();
window.RLQ[1]();
window.mw = tempMW;
} else {
window.wgAction = mw.config.get('wgAction');
window.wgArticleId = mw.config.get('wgArticleId');
window.wgPageName = mw.config.get('wgPageName');
window.wgNamespaceNumber = mw.config.get('wgNamespaceNumber');
}
var navLink = document.createElement('li');
navLink.id = 'RecomWP';
navLink.innerHTML = '<a href="/wiki/Special:%E7%A9%BA%E7%99%BD%E9%A1%B5%E9%9D%A2/RecomWP" title="为你推荐的条目">推荐条目</a>';
document.querySelector('#p-navigation ul').appendChild(navLink);
if (window.wgNamespaceNumber === 0 || window.wgPageName === 'Special:空白页面/RecomWP') {
var wpdb;
window.indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction;
window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange;
console.log('RecomWP initialised.');
var DBOpenRequest = window.indexedDB.open('RecomWP');
DBOpenRequest.onerror = function (event) {
console.log('Error loading database.');
};
DBOpenRequest.onsuccess = function (event) {
console.log('Database initialised.');
// store the result of opening the database in the db variable. This is used a lot below
wpdb = DBOpenRequest.result;
if (window.wgArticleId) {
addData({
pageId: window.wgArticleId,
pageName: window.wgPageName
});
} else if (window.wgAction === 'view') {
var GM_addStyle = function (css) {
const style = document.createElement('style');
style.type = 'text/css';
style.id = 'GM_addStyleBy8626';
style.innerHTML = css;
document.head.appendChild(style);
};
document.title = '推荐条目';
document.getElementById('firstHeading').innerText = '推荐条目';
GM_addStyle('.recom-root:after{clear:both;display:table;content:" "}.ra-read-more{overflow:hidden;max-height:300px;transition:max-height 1s;}#left-recom{float:left;width:66.666667%}#right-recom{float:left;width:33.333333%}.ra-read-more h2{border-bottom:0;font-size:.8em;font-weight:400;color:#72777d;padding-bottom:.5em;text-transform:uppercase;letter-spacing:1px}.ra-read-more ul{margin:0}.ext-related-articles-card-list{border-top:0}.ext-related-articles-card-list{display:-webkit-flex;display:-moz-flex;display:-ms-flexbox;display:flex;flex-flow:row wrap;font-size:1em;list-style:none;overflow:hidden;position:relative}.ext-related-articles-card-list .ext-related-articles-card{border-radius:6px;overflow:hidden;box-shadow:0 2px 4px rgba(0,0,0,0.12),0 2px 4px rgba(0,0,0,0.24);transition:all 0.3s cubic-bezier(.25,.8,.25,1);}.ext-related-articles-card-list .ext-related-articles-card:hover{box-shadow:0 5px 10px rgba(0,0,0,0.25),0 5px 10px rgba(0,0,0,0.22);}.ext-related-articles-card-list .ext-related-articles-card:active{box-shadow:0 2px 4px rgba(0,0,0,0.12),0 2px 4px rgba(0,0,0,0.24);}.ext-related-articles-card-list .ext-related-articles-card{background-color:#fff;box-sizing:border-box;margin:0;height:80px;position:relative;border:1px solid rgba(0,0,0,.2);margin-right:1%;margin-bottom:10px;width:49%;}.ext-related-articles-card-list .ext-related-articles-card-thumb{background-color:#eaecf0;background-image:url(/w/extensions/RelatedArticles/resources/ext.relatedArticles.cards/noimage.png?9e3d8);background-image:linear-gradient(transparent,transparent),url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 56 56%22%3E %3Cpath fill=%22%23eaecf0%22 d=%22M0 0h56v56h-56%22/%3E %3Cpath fill=%22%2372777d%22 d=%22M36.4 13.5h-18.6v24.9c0 1.4.9 2.3 2.3 2.3h18.7v-25c.1-1.4-1-2.2-2.4-2.2zm-6.2 3.5h5.1v6.4h-5.1v-6.4zm-8.8 0h6v1.8h-6v-1.8zm0 4.6h6v1.8h-6v-1.8zm0 15.5v-1.8h13.8v1.8h-13.8zm13.8-4.5h-13.8v-1.8h13.8v1.8zm0-4.7h-13.8v-1.8h13.8v1.8z%22/%3E %3C/svg%3E");background-repeat:no-repeat;background-position:top center;-webkit-background-size:100% 100%;background-size:100% 100%;background-size:cover;float:left;height:100%;width:80px;margin-right:10px}.ext-related-articles-card-list .ext-related-articles-card>a{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1}.ext-related-articles-card-list .ext-related-articles-card-detail{position:relative;top:50%;-webkit-transform:translateY(-50%);-ms-transform:translateY(-50%);transform:translateY(-50%)}.ext-related-articles-card-list h3{font-family:inherit;font-size:1em;max-height:2.6em;line-height:1.3;margin:0;overflow:hidden;padding:0;position:relative;font-weight:500}.ext-related-articles-card-list h3 a{color:#000}.ext-related-articles-card-list h3::after{content:" ";position:absolute;right:0;bottom:0;width:25%;height:1.3em;background-color:transparent;background-image:-webkit-linear-gradient(right,rgba(255,255,255,0),#fff 50%);background-image:-moz-linear-gradient(right,rgba(255,255,255,0),#fff 50%);background-image:-o-linear-gradient(right,rgba(255,255,255,0),#fff 50%);background-image:linear-gradient(to right,rgba(255,255,255,0),#fff 50%)}.ext-related-articles-card-list .ext-related-articles-card-extract{color:#72777d;font-size:.8em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-top:2px}.recom-card{box-shadow:0 2px 4px rgba(0,0,0,0.12),0 2px 4px rgba(0,0,0,0.24);padding:10px;transition:all 0.3s cubic-bezier(.25,.8,.25,1);}.recom-card + .recom-card{margin-top:20px;}.recom-card:hover{box-shadow:0 5px 10px rgba(0,0,0,0.25),0 5px 10px rgba(0,0,0,0.22);}.recom-card img,.img-banner img{cursor:pointer;width:100%;}.trend-list{list-style-type:none;}.trend-list li{display:flex;line-height:36px;font-size:24px;cursor:pointer;}.trend-list li:hover{color:#4284f3;}.trend-item-left{display:inline-block;min-width:50px;user-select:none;}');
let landscapeDiv = document.createElement('div');
landscapeDiv.id = 'landscape-recom';
let leftDiv = document.createElement('div');
leftDiv.id = 'left-recom';
let rightDiv = document.createElement('div');
rightDiv.id = 'right-recom';
let rootDiv = document.getElementById('mw-content-text');
rootDiv.innerHTML = '';
rootDiv.classList.add('recom-root');
rootDiv.appendChild(landscapeDiv);
rootDiv.appendChild(leftDiv);
rootDiv.appendChild(rightDiv);
generateRecom();
getTrend();
}
};
DBOpenRequest.onupgradeneeded = function (event) {
wpdb = event.target.result;
wpdb.onerror = function (event) {
console.error('Error loading database.');
};
// Create an objectStore for this database
var objectStore = wpdb.createObjectStore('historyArticles', { keyPath: 'pageId' });
// define what data items the objectStore will contain
objectStore.createIndex('pageName', 'pageName', { unique: true });
objectStore.createIndex('visitCount', 'visitCount', { unique: false });
objectStore.createIndex('lastTime', 'lastTime', { unique: false });
console.log('Object store created.');
};
var addData = function (newItem) {
var transaction = wpdb.transaction(['historyArticles'], 'readwrite');
// report on the success of the transaction completing, when everything is done
transaction.oncomplete = function () {
console.log('Transaction completed: database modification finished.');
};
transaction.onerror = function () {
console.error('Transaction not opened due to error: ' + transaction.error + '');
};
// call an object store that's already been added to the database
var objectStore = transaction.objectStore('historyArticles');
objectStore.openCursor(newItem.pageId).onsuccess = function (event) {
var cursor = event.target.result;
if (cursor) {
newItem.visitCount = cursor.value.visitCount + 1;
newItem.lastTime = Date.now();
var request = cursor.update(newItem);
request.onsuccess = function () {
console.log('Update success');
};
} else {
newItem.visitCount = 1;
newItem.lastTime = Date.now();
var objectStoreRequest = objectStore.add(newItem);
objectStoreRequest.onsuccess = function (event) {
console.log('Insert success');
};
}
};
};
var removeData = function (pageId) {
wpdb.transaction(['historyArticles'], 'readwrite').objectStore('historyArticles').delete(pageId);
};
var httpGet = function(url, callback) {
let request = new XMLHttpRequest();
request.open('GET', url, true);
request.onload = function () {
if (request.status >= 200 && request.status < 400) {
var responseText = JSON.parse(request.responseText);
callback(responseText);
}
};
request.send();
}
var relativeSearch = function (pageName, callback) {
let queryUrl = 'https://wiki.zwnes.eu.org/w/api.php?action=query&format=json&formatversion=2&prop=pageimages|description&piprop=thumbnail&pithumbsize=160&generator=search&gsrsearch=morelike%3A'
+ encodeURI(pageName) + '&gsrnamespace=0&gsrlimit=4&gsrqiprofile=classic_noboostlinks&uselang=content&smaxage=86400&maxage=86400';
httpGet(queryUrl, callback);
};
var dailyFeatured = function (callback) {
let ymd = new Date(Date.now() - 2 * 60 * 60000).toISOString().split('T')[0].replace(/-/g, '/');
let queryUrl = 'https://wiki.zwnes.eu.org/api/rest_v1/feed/featured/' + ymd;
httpGet(queryUrl, callback);
};
// O(n) time & O(n) space
var mergeTwo = function(arr1, arr2) {
let merged = [];
let index1 = 0;
let index2 = 0;
let current = 0;
let duplicate = 0;
while (current + duplicate < (arr1.length + arr2.length)) {
let isArr1Depleted = index1 >= arr1.length;
let isArr2Depleted = index2 >= arr2.length;
if (!isArr1Depleted && (isArr2Depleted || (arr1[index1].views >= arr2[index2].views))) {
merged[current] = arr1[index1];
index1++;
} else {
if (merged[current-1] && merged[current-1].article === arr2[index2].article) {
duplicate++;
current--;
} else {
merged[current] = arr2[index2];
}
index2++;
}
current++;
}
return merged;
}
var dailyTrend = function (params, callback) {
var result = [];
Promise.all(params.split(',')
.map(x => fetch('https://yfdyh-lab.toolforge.org/topviews?' + x)
.then(response => response.json())
.then(data => {
result = mergeTwo(result, data);
})
)
).then(() => callback(result));
};
var getTrend = function () {
var generateImage = function (imgCard, imageObj) {
let featImg = new Image();
featImg.onclick = function () {
window.location.href = imageObj.file_page
}
// normal image
featImg.src = imageObj.thumbnail.source;
imgCard.className = 'recom-card';
imgCard.appendChild(featImg);
if (imageObj.description) {
imgCard.insertAdjacentHTML('beforeend', imageObj.description.html);
}
};
var trendParams = {
'ACG': 'topic=acg',
'影视娱乐': 'topic=drama|film|show,type=star',
'': 'topic=!drama|!film|!show|!acg&type=!star'
};
var generateTrendList = function (type) {
let trendTitle = document.createElement('h2');
trendTitle.innerHTML = type + '热门条目';
trendTitle.style = 'text-align:center';
let trendCard = document.createElement('div');
trendCard.className = 'recom-card';
trendCard.appendChild(trendTitle)
let trendList = document.createElement('ul');
trendList.className = 'trend-list'
trendCard.appendChild(trendList);
document.getElementById('right-recom').appendChild(trendCard);
dailyTrend(trendParams[type], articles => {
for (let i = 0; i < 10; i++) {
let trendItemLeft = document.createElement('span');
trendItemLeft.className = 'trend-item-left';
trendItemLeft.innerText = i + 1;
let trendItemRight = document.createElement('span');
trendItemRight.className = 'trend-item-right';
if (!articles[i]) break;
trendItemRight.innerText = articles[i].article.replace(/_/g, ' ');
let trendItem = document.createElement('li');
trendItem.onclick = function () {
window.location.href = 'https://wiki.zwnes.eu.org/wiki/' + articles[i].article
}
trendItem.appendChild(trendItemLeft);
trendItem.appendChild(trendItemRight);
trendList.appendChild(trendItem);
}
});
};
let imgCard = document.createElement('div');
document.getElementById('right-recom').appendChild(imgCard);
dailyFeatured(res => {
if (res.image) {
generateImage(imgCard, res.image);
}
});
generateTrendList('');
generateTrendList('影视娱乐');
generateTrendList('ACG');
}
var appendToPage = function (pageName, pages, pageId) {
let titleBar = document.createElement('h2');
titleBar.innerHTML = '<strong>' + pageName + '</strong>相关页面 ';
let alist = document.createElement('ul');
alist.className = 'ext-related-articles-card-list';
pages.forEach(function (item) {
let listItem = document.createElement('li');
listItem.className = 'ext-related-articles-card';
listItem.title = item.title;
let cardDiv = document.createElement('div');
cardDiv.className = 'ext-related-articles-card-thumb';
if (item.thumbnail) {
cardDiv.style['background-image'] = 'url("' + item.thumbnail.source + '")';
}
let cardLink = document.createElement('a');
cardLink.href = '/wiki/' + encodeURI(item.title);
let cardDetail = document.createElement('div');
cardDetail.className = 'ext-related-articles-card-detail';
cardDetail.innerHTML = '<h3>' + item.title + '</h3>';
if (item.description) {
cardDetail.innerHTML += '<p class="ext-related-articles-card-extract">' + item.description + '</p>';
}
listItem.appendChild(cardDiv);
listItem.appendChild(cardLink);
listItem.appendChild(cardDetail);
alist.appendChild(listItem);
});
let blockDiv = document.createElement('div');
blockDiv.className = 'ra-read-more';
let dontLike = document.createElement('a');
dontLike.innerText = '[不感兴趣]';
dontLike.onclick = function () {
removeData(pageId);
blockDiv.style['max-height'] = '0';
};
titleBar.appendChild(dontLike);
blockDiv.appendChild(titleBar);
blockDiv.appendChild(alist);
let leftDiv = document.getElementById('left-recom');
leftDiv.appendChild(blockDiv);
};
var generateRecom = function () {
/*
* Simple implementation of Binary heap
*/
function BinaryHeap (scoreFunction) {
this.content = [];
this.scoreFunction = scoreFunction;
}
BinaryHeap.prototype = {
push: function (element) {
// Add the new element to the end of the array.
this.content.push(element);
// Allow it to bubble up.
this.bubbleUp(this.content.length - 1);
},
pop: function () {
// Store the first element so we can return it later.
var result = this.content[0];
// Get the element at the end of the array.
var end = this.content.pop();
// If there are any elements left, put the end element at the
// start, and let it sink down.
if (this.content.length > 0) {
this.content[0] = end;
this.sinkDown(0);
}
return result;
},
remove: function (node) {
var length = this.content.length;
// To remove a value, we must search through the array to find
// it.
for (var i = 0; i < length; i++) {
if (this.content[i] !== node) continue;
// When it is found, the process seen in 'pop' is repeated
// to fill up the hole.
var end = this.content.pop();
// If the element we popped was the one we needed to remove,
// we're done.
if (i === length - 1) break;
// Otherwise, we replace the removed element with the popped
// one, and allow it to float up or sink down as appropriate.
this.content[i] = end;
this.bubbleUp(i);
this.sinkDown(i);
break;
}
},
size: function () {
return this.content.length;
},
bubbleUp: function (n) {
// Fetch the element that has to be moved.
var element = this.content[n],
score = this.scoreFunction(element);
// When at 0, an element can not go up any further.
while (n > 0) {
// Compute the parent element's index, and fetch it.
var parentN = Math.floor((n + 1) / 2) - 1,
parent = this.content[parentN];
// If the parent has a lesser score, things are in order and we
// are done.
if (score >= this.scoreFunction(parent)) {
break;
}
// Otherwise, swap the parent with the current element and
// continue.
this.content[parentN] = element;
this.content[n] = parent;
n = parentN;
}
},
sinkDown: function (n) {
// Look up the target element and its score.
var length = this.content.length,
element = this.content[n],
elemScore = this.scoreFunction(element);
while (true) {
// Compute the indices of the child elements.
var child2N = (n + 1) * 2, child1N = child2N - 1;
// This is used to store the new position of the element,
// if any.
var swap = null;
// If the first child exists (is inside the array)...
if (child1N < length) {
// Look it up and compute its score.
var child1 = this.content[child1N],
child1Score = this.scoreFunction(child1);
// If the score is less than our element's, we need to swap.
if (child1Score < elemScore) {
swap = child1N;
}
}
// Do the same checks for the other child.
if (child2N < length) {
var child2 = this.content[child2N],
child2Score = this.scoreFunction(child2);
if (child2Score < (swap == null ? elemScore : child1Score)) {
swap = child2N;
}
}
// No need to swap further, we are done.
if (swap == null) break;
// Otherwise, swap and continue.
this.content[n] = this.content[swap];
this.content[swap] = element;
n = swap;
}
}
};
var heap = new BinaryHeap(function (x) {
var res = ((Date.now() - x.lastTime) / 604800000) - x.visitCount;
return res;
});
// Database iterate
var objectStore = wpdb.transaction('historyArticles').objectStore('historyArticles');
objectStore.openCursor().onsuccess = function (event) {
document.getElementById('left-recom').innerHTML = '';
var cursor = event.target.result;
if (cursor) {
heap.push(cursor.value);
cursor.continue();
} else {
// first remove the backmost entries to keep size, default size 127
if (heap.size() >= 128) {
for (let i = 127; i < heap.size(); i++) {
removeData(heap.content[i].pageId);
}
}
var getPage = function () {
if (!heap.size()) {
return false;
}
let topEntry = heap.pop();
let pageName = topEntry.pageName;
relativeSearch(pageName, function (data) {
if (data.batchcomplete) {
if (data.query) {
appendToPage(pageName, data.query.pages, topEntry.pageId);
} else {
removeData(topEntry.pageId);
getPage();
}
}
});
return true;
};
for (let i = 1; i <= 6; i++) {
if (!getPage()) {
break;
}
}
}
};
};
}
})();