// ==UserScript==
// @name History of the Seen
// @namespace https://github.com/theoky/HistoryOfTheSeen
// @description Script to implement a history of the seen approach for some news sites. Details at https://github.com/theoky/HistoryOfTheSeen
// @author Theoky
// @version 0.4192
// @lastchanges workaround for bug in GreaseMonkey 3.2
// @license GNU GPL version 3
// @released 2014-02-20
// @updated 2014-06-10
// @homepageURL https://github.com/theoky/HistoryOfTheSeen
//
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_deleteValue
// @grant GM_registerMenuCommand
// @grant GM_listValues
// @grant GM_addStyle
//
// for testing purposes (set FireFox greasemonkey.fileIsGreaseable)
// @include file://*testhistory.html
//
// @include http*://*.derstandard.at/*
// @include http*://*.faz.net/*
// @include http*://*.golem.de/*
// @include http*://*.handelsblatt.com/*
// @include http*://*.heise.de/newsticker/*
// @include http*://*.kleinezeitung.at/*
// @include http*://*.nachrichten.at/*
// @include http*://*.oe24.at/*
// @include http*://*.orf.at/*
// @include http*://orf.at/*
// @include http*://*.reddit.com/*
// @include http*://*.spiegel.de/*
// @include http*://*.sueddeutsche.de/*
// @include http*://*.welt.de/*
// @include http*://*.wirtschaftsblatt.at/*
// @include http*://*.zeit.de/*
// @include http*://dastandard.at/*
// @include http*://derstandard.at/*
// @include http*://diepresse.com/*
// @include http*://diestandard.at/*
// @include http*://kurier.at/*
// @include http*://slashdot.org/*
// @include http*://taz.de/*
// @include http*://notalwaysright.com/*
// @include http*://www.nytimes.com/*
// @require http://code.jquery.com/jquery-2.1.1.min.js
// @require http://code.jquery.com/ui/1.11.2/jquery-ui.js
// @require https://greasyfork.org/scripts/130-portable-md5-function/code/Portable%20MD5%20Function.js?version=10066
// was require md5.js
// was require http://crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/md5.js
// ==/UserScript==
// Copyright (C) 2015 T. Kopetzky - theoky
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// Tested with FireFox 34 and GreaseMonkey 2.3
//-------------------------------------------------
//Functions
(function(){
var defaultSettings = {
ageOfUrl: 5, // age in days after a url is deleted from the store
// < 0 erases all dates (disables history)
targetOpacity: 0.3,
targetOpacity4Dim: 0.85,
steps: 10,
dimInterval: 30000,
expireAllDomains: true, // On fast machines this can be true and expires
// all domains in the database with each call. If false,
// only the urls of the current domain are expired which
// is slightly faster.
cleanOnlyDaily: true,
considerViewPort: true,
dbOpsPerRun: 5
};
var UNDEF = 'undefined';
var DEFAULT_TAG = 'a';
var defaultGetContentFct = function(elem) {
if ((typeof elem != UNDEF) && (typeof elem.href != UNDEF)) {
return elem.href;
}
return UNDEF;
};
var AFTER_SCROLL_DELAY = 750;
var DO_DEBUG = false;
var DEBUG_LVL_ERROR = 1;
var DEBUG_LVL_WARN = 2;
var DEBUG_LVL_INFO = 4;
var perUrlSettings = [
{
url : ['.*\.?slashdot\.org' ],
tag : 'article',
upTrigger: "../article",
getContent: function(elem) {
if ((typeof elem != UNDEF) && (typeof elem.id != UNDEF)) {
return elem.id;
}
return UNDEF;
},
parentHints : [ ]
},
{
url : ['.*\.?derstandard\.at', '.*\.?diestandard\.at', '.*\.?dastandard\.at' ],
upTrigger: "../a",
parentHints : [
"ancestor::div[contains(concat(' ', @class, ' '), ' text ')]",
"ancestor::ul[@class='stories']" ]
},
{
url : ['notalwaysright\.com'],
upTrigger: "../a[@rel='bookmark']",
parentHints : [ "ancestor::div[contains(concat(' ', @class, ' '), ' post ')]" ]
},
{
url : ['.*\.?golem.de'],
upTrigger: "../a",
parentHints : [ "ancestor::li",
"ancestor::section[@id='index-promo']",
"ancestor::section[contains(concat(' ', @class, ' '), ' promo ')]" ]
},
{
url : ['.*\.?reddit.com'],
// class="title may-blank srTagged imgScanned"
upTrigger: "../a[contains(@class, 'title') and contains(@class, 'may-blank')]",
parentHints : [ "ancestor::div[contains(concat(' ', @class, ' '), ' thing ')]" ]
},
{
url : ['nytimes\.com'],
upTrigger: "../a",
parentHints : [
// "ancestor::li[contains(concat(' ', @class, ' '), ' portal-post ')]",
"ancestor::div[contains(concat(' ', @class, ' '), ' collection ')]"
]
}
];
var dimMap = {};
var countDownTimer = defaultSettings.steps;
var theHRefs = null;
var curSettings = null;
var KEY_LAST_EXPIRE_OP = "lastExpire";
var timeOutAfterLastScroll = UNDEF;
var tag2Process = null;
var getContentFct = null;
var theDomain = null;
var progressbar;
var progressLabel;
// Styling
var progressBarStyle =
".ui-widget {" +
" font-family: Verdana,Arial,sans-serif !important;" +
" font-size: 1.1em !important;" +
"}" +
".ui-widget-content {" +
" border: 1px solid #aaaaaa !important;" +
" color: #222222 !important;" +
"}" +
".ui-widget-header {" +
" border: 1px solid #aaaaaa !important;" +
" background: #cccccc !important;" +
" color: #222222 !important;" +
" font-weight: bold !important;" +
"}" +
".ui-progressbar {" +
" height: 2em !important;" +
" text-align: left !important;" +
" overflow: hidden !important;" +
"}" +
".ui-progressbar .ui-progressbar-value {" +
" margin: -1px !important;" +
" height: 100% !important;" +
"}" +
".ui-progressbar .ui-progressbar-overlay {" +
" background: url('data:image/gif;base64,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') !important;" +
" height: 100% !important;" +
" filter: alpha(opacity=25) !important; /* support: IE8 */" +
" opacity: 0.25 !important;" +
"}" +
// ".ui-progressbar-indeterminate .ui-progressbar-value {" +
// " background-image: none !important;" +
// "}" +
".ui-progressbar {" +
" height: 2em !important;" +
" text-align: left !important;" +
" overflow: hidden !important;" +
" position: absolute !important;" +
" left: 20% !important;" +
" top: 4px !important;" +
" width: 60% !important;" +
" z-index: 255 !important;" +
"}" +
".progress-label {" +
" position: absolute !important;" +
" left: 5% !important;" +
" top: 4px !important;" +
" font-weight: bold !important;" +
" text-shadow: 1px 1px 0 #fff !important;" +
" z-index: 256 !important;" +
"}";
// Debugging
function debuglog(msg) {
if (DO_DEBUG) {
console.log(msg);
}
}
function debugLogLvl(lvl, msg) {
if (lvl & DEBUG_LVL_ERROR) {
console.log("error: " + msg);
}
if (DO_DEBUG) {
if (lvl & DEBUG_LVL_WARN) {
console.log("warn:" + msg);
}
if (lvl & DEBUG_LVL_INFO) {
console.log(msg);
}
}
}
var g_index;
var g_keys;
var g_lengthOfKeysArray;
var g_workInProgress = false;
var g_par1 = UNDEF;
var g_par2 = UNDEF;
var g_workerFctDefault = function(key, par1, par2) {
GM_deleteValue(key);
};
var g_workerFct = g_workerFctDefault;
var g_finishFct_Default = function() {
document.location.reload(true);
};
var g_finishFct = g_finishFct_Default;
var g_label;
function appendProgressBar() {
$("body").append ( '\
<div id="progressbar" class="ui-progressbar ui-progressbar-indeterminate"><div class="progress-label">History of the Seen: Resetting DB for current domain...</div></div>');
}
function removeProgressBar(reload) {
$("#progressbar").remove();
if (reload) {
document.location.reload(true);
}
}
/*
* Init function for "threading"
*/
function initThreadingLoop()
{
if (g_workInProgress) {
debugLogLvl(DEBUG_LVL_ERROR, "initThreading with already threading in progress.");
return;
}
g_workInProgress = true;
g_index = 0;
g_keys = GM_listValues();
if (!g_keys) {
debugLogLvl(DEBUG_LVL_WARN, "g_keys empty?");
return;
}
g_lengthOfKeysArray = g_keys.length;
appendProgressBar();
progressbar = $("#progressbar");
progressLabel = $(".progress-label");
progressbar.progressbar({
value : false,
change : function() {
progressLabel.text(g_label + progressbar.progressbar("value").toFixed(2) + "% ");
},
complete : function() {
progressLabel.text(" History of the Seen: Operation Complete! ");
}
});
progressbar.progressbar("value", 0);
setTimeout(doThreadWork, 1);
}
/*
* Worker method
*/
function doThreadWork()
{
if (!g_workInProgress) {
return;
}
var i = 0;
var currentKey = null;
currentKey = g_keys[g_index];
while (i < defaultSettings.dbOpsPerRun && currentKey) {
g_workerFct(currentKey, g_par1, g_par2);
g_index ++;
i++;
currentKey = g_keys[g_index];
}
progressbar.progressbar("value", g_index * 100 / g_lengthOfKeysArray);
if (currentKey) {
setTimeout(doThreadWork, 10);
} else
{
removeProgressBar(false);
if (g_finishFct !== UNDEF) {
g_finishFct();
}
g_workInProgress = false;
}
}
// Resetting section
function resetAllUrls() {
if (!g_workInProgress && confirm('Are you sure you want to erase the complete seen history?')) {
g_label = " History of the Seen: Cleaning DB, done ";
g_par1 = UNDEF;
g_par2 = UNDEF;
g_workerFct = g_workerFctDefault;
g_finishFct = g_finishFct_Default;
initThreadingLoop();
}
}
function resetUrlsForCurrentHelper(dKey, domainOrUri) {
if (confirm('Are you sure you want to erase the seen history for ' +
domainOrUri + '?')) {
g_label = " History of the Seen: Cleaning DB, done ";
g_par1 = dKey;
g_par2 = domainOrUri;
g_workerFct = function(key, dKey, domainOrUri) {
if (key == KEY_LAST_EXPIRE_OP){
return;
}
try {
var val = GM_getValue(key, "{}");
var dict = JSON.parse(val);
if(dict) {
if (dict[dKey] == domainOrUri) {
GM_deleteValue(key);
}
}
} catch (e) {
console.log(e);
}
};
g_finishFct = g_finishFct_Default;
initThreadingLoop();
}
}
function resetUrlsForCurrentDomain() {
resetUrlsForCurrentHelper("domain", document.domain);
}
function resetUrlsForCurrentSite() {
resetUrlsForCurrentHelper("base", document.baseURI);
}
function expireUrls() {
debugLogLvl(DEBUG_LVL_INFO, "expireUrls");
if (defaultSettings.cleanOnlyDaily) {
var lastExpireDate = new Date(GM_getValue(KEY_LAST_EXPIRE_OP, nDaysOlderFromNow(2)));
var diff = Math.abs((new Date()) - lastExpireDate);
if (diff / 1000 / 3600 / 24 < 1) {
// less than one day -> no DB cleaning
return;
}
}
/*
var val = GM_getValue(KEY_EXPIRE_OP_INPROGRESS);
if (typeof val !== UNDEF) {
// expire in progress
return;
}
GM_setValue(KEY_EXPIRE_OP_INPROGRESS, True);
*/
// cutOffDate
g_label = " History of the Seen: Expiring old URLs for this site, done ";
g_par1 = nDaysOlderFromNow(defaultSettings.ageOfUrl);
debuglog("cutOffDate" + g_par1);
g_par2 = UNDEF;
g_workerFct = function(key, cutOffDate, par2) {
if (key == KEY_LAST_EXPIRE_OP){
return;
}
var dict = JSON.parse(GM_getValue(key, "{}"));
if(dict) {
try {
debuglog(dict["domain"], cutOffDate.getTime(), dict["date"]);
if (cutOffDate.getTime() > dict["date"]) {
if (defaultSettings.expireAllDomains ||
(dict["domain"] == document.domain))
{
GM_deleteValue(key);
}
}
} catch (e) {
console.log(e);
}
}
else {
console.log('Error! JSON.parse failed - dict is likely to be corrupted. Probably best to completely clean DB.');
}
};
g_finishFct = function() {
GM_setValue(KEY_LAST_EXPIRE_OP, new Date());
}
initThreadingLoop();
}
function nDaysOlderFromNow(age, aDate, zeroHour) {
var aDate = typeof aDate !== UNDEF ? aDate : new Date();
var zeroHour = typeof zeroHour !== UNDEF ? zeroHour : true;
var dateStore = new Date(aDate.getTime());
var workDate = aDate;
if (age >= 0) {
workDate.setDate(dateStore.getDate() - age);
if (zeroHour) {
workDate.setHours(0,0,0,0);
}
}
return workDate;
}
/*
* Find the settings for a given URL
*/
function findPerUrlSettings(theSettings, aDomain) {
debugLogLvl(DEBUG_LVL_INFO, "findPerUrlSettings");
for (var i=0; i < theSettings.length; ++i) {
for (var j = 0; j < theSettings[i].url.length; ++j) {
var myRegExp = new RegExp(theSettings[i].url[j], 'i');
if (aDomain.match(myRegExp)) {
return theSettings[i];
}
}
}
}
/*
* Find the parent element as specified in the settings.
*/
function locateParentElem(curSettings, aDomain, aRoot) {
if (!curSettings) {
return null;
}
// console.log("locateParentElem 1", curSettings.url);
var res = null;
for (var xpath = 0; xpath < curSettings.parentHints.length; ++xpath) {
// console.log("locateParentElem 2", curSettings.parentHints[xpath], aRoot);
res = document.evaluate(curSettings.parentHints[xpath], aRoot, null, 9, null).singleNodeValue;
if (res) {
// console.log("locateParentElem found something");
return res;
}
}
return res;
}
/*
* Check if the current node qualifies for looking up the hierarchy.
*/
function goUp(curSettings, aRoot) {
if (!curSettings) {
return false;
}
var res = null;
if (curSettings.upTrigger !== "") {
res = document.evaluate(curSettings.upTrigger, aRoot, null, 9, null).singleNodeValue;
}
return res !== null;
}
/*
* Set the opacity for specified links
*/
function dimLinks() {
var interval = (1 - defaultSettings.targetOpacity4Dim)/defaultSettings.steps;
var countDownTimer = countDownTimer - 1;
var curOpacity = defaultSettings.targetOpacity4Dim + interval*countDownTimer;
// TODO: Better iterate over dimmap
for(var i = 0; i < theHRefs.length; i++)
{
var hash = 'm' + hex_md5(theHRefs[i].href);
if (hash in dimMap) {
theHRefs[i].style.opacity = curOpacity;
}
}
if (countDownTimer > 0) {
var to = setTimeout(dimLinks, defaultSettings.dimInterval);
}
}
/*
* Check if an element is fully drawn on the viewport
* from http://stackoverflow.com/questions/487073/check-if-element-is-visible-after-scrolling?lq=1
*/
function isFullyInView(elem)
{
debugLogLvl(DEBUG_LVL_INFO, "isFullyInView");
var docViewTop = $(window).scrollTop();
var docViewBottom = docViewTop + $(window).height();
var elemTop = $(elem).offset().top;
var elemBottom = elemTop + $(elem).height();
return ((elemBottom <= docViewBottom) && (elemTop >= docViewTop));
// is really fully in view
// return ((elemBottom >= docViewTop) && (elemTop <= docViewBottom)
// && (elemBottom <= docViewBottom) && (elemTop >= docViewTop) );
}
/*
* Called after scrolling finished for defined time
*/
function evaluateElems() {
debugLogLvl(DEBUG_LVL_INFO, "evaluate all");
processElements(false);
timeOutAfterLastScroll = UNDEF;
}
/*
* Wait for scrolling to end
*/
function onScroll()
{
if (timeOutAfterLastScroll !== UNDEF) {
window.clearTimeout(timeOutAfterLastScroll)
}
timeOutAfterLastScroll = setTimeout(evaluateElems, AFTER_SCROLL_DELAY);
}
/*
* Process all elements
*/
function processElements(firstCall) {
debugLogLvl(DEBUG_LVL_INFO, "processElements");
var allTagElems = document.getElementsByTagName(tag2Process);
var elemMap = {};
var theBase = document.baseURI;
// Change the DOM
// First loop: gather all new links and make already seen opaque.
for(var i = 0; i < allTagElems.length; i++)
{
var hash = 'm' + hex_md5(getContentFct(allTagElems[i]));
// setValue needs letter in the beginning, thus use of 'm'
debugLogLvl(DEBUG_LVL_INFO, "hash: " + hash);
var key = GM_getValue(hash);
if (typeof key !== UNDEF && key !== null) {
// workaround for issue https://github.com/greasemonkey/greasemonkey/issues/2156
// key found -> loaded this reference already
debugLogLvl(DEBUG_LVL_INFO, "key found");
if (firstCall) {
var done = false;
if(goUp(curSettings, allTagElems[i])) {
var pe = locateParentElem(curSettings, theDomain, allTagElems[i])
// console.log("locate parent done", pe);
if (pe) {
pe.style.opacity = defaultSettings.targetOpacity;
done = true;
}
}
if (!done) {
// change display
allTagElems[i].style.opacity = defaultSettings.targetOpacity;
debugLogLvl(DEBUG_LVL_INFO, "changing opacity");
}
}
} else {
//check if element is fully visible
debugLogLvl(DEBUG_LVL_INFO, "key not found");
if (isFullyInView(allTagElems[i])) {
debuglog(allTagElems[i] + " is in view");
// key not found, store it with current date
elemMap[hash] = {"domain":theDomain, "date":(new Date()).getTime(), "base":theBase};
dimMap[hash] = allTagElems[i];
}
}
}
// remember all new urls to hide the next time
for (var e2 in elemMap) {
GM_setValue(e2, JSON.stringify(elemMap[e2]));
}
theHRefs = allTagElems;
if (firstCall) {
var to = setTimeout(dimLinks, defaultSettings.dimInterval);
}
}
// Menus
GM_registerMenuCommand("Remove the seen history for this site.", resetUrlsForCurrentSite);
GM_registerMenuCommand("Remove the seen history for this domain.", resetUrlsForCurrentDomain);
GM_registerMenuCommand("Remove all seen history (for all sites)!", resetAllUrls);
GM_addStyle(progressBarStyle);
// Main part
function run_script() {
debugLogLvl(DEBUG_LVL_INFO, "run");
dimMap = {};
theDomain = document.domain;
curSettings = findPerUrlSettings(perUrlSettings, theDomain);
tag2Process = DEFAULT_TAG;
getContentFct = defaultGetContentFct;
if (typeof curSettings != UNDEF) {
if (typeof curSettings.tag != UNDEF) {
tag2Process = curSettings.tag;
}
if (typeof curSettings.getContent != UNDEF) {
getContentFct = curSettings.getContent;
}
}
expireUrls();
processElements(true);
window.addEventListener("scroll", onScroll, false);
}
run_script();
})();
Donate for the site OpenUserJS
Are you sure you want to go to an external site to donate a monetary value?
WARNING: Some countries laws may supersede the payment processors policy such as the GDPR and PayPal. While it is highly appreciated to donate, please check with your countries privacy and identity laws regarding privacy of information first. Use at your utmost discretion.