您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Record time spent working on HITs. Forked from [MTurk Worker] Dashboard Enhancer v2.1.3 by @Kadauchi.
// ==UserScript== // @name Mturk Hourly // @namespace https://greasyfork.org/users/11205 // @version 2.2.0 // @description Record time spent working on HITs. Forked from [MTurk Worker] Dashboard Enhancer v2.1.3 by @Kadauchi. // @author Kerek // @include https://worker.mturk.com/dashboard* // @include https://worker.mturk.com/projects/*/tasks/*assignment_id=* // @include https://worker.mturk.com/status_details/* //@grant GM_setClipboard // ==/UserScript== if (location.href.indexOf(`https://worker.mturk.com/dashboard`) !== -1) { Remove_Old_Data(); const toNum = (string) => Number(string.replace(/[^0-9.]/g, ``)); const toDate = (string) => string.split(`T`)[0]; const toMoney = (number) => `$${number.toFixed(2)}`; const needPlus = (number) => number > 0 ? `+` : ``; const statusDetailsTable = document.querySelector( `div[data-react-class="require('reactComponents/dailyWorkerStatisticsTable/DailyWorkerStatisticsTable')['default']"]`, ); const statusDetailsArr = JSON.parse(statusDetailsTable.dataset.reactProps).bodyData; const statusDetailsObj = statusDetailsArr.reduce((obj, details) => ({ ...obj, [toDate(details.date)]: details }), {}); const hitsOverviewTable = document.getElementById(`dashboard-hits-overview`); const hitsOverviewRows = hitsOverviewTable.querySelectorAll(`.col-xs-5.text-xs-right`); const hitsOverview = { approved: toNum(hitsOverviewRows[0].textContent), pending: toNum(hitsOverviewRows[1].textContent), rejected: toNum(hitsOverviewRows[2].textContent), }; function allApprovedRate() { const row = document.createElement(`div`); row.className = `row m-b-sm`; const col1 = document.createElement(`div`); col1.className = `col-xs-7`; row.appendChild(col1); const strong = document.createElement(`strong`); strong.textContent = `All Approved Rate`; col1.appendChild(strong); const col2 = document.createElement(`div`); col2.className = `col-xs-5 text-xs-right`; col2.textContent = `${( ((hitsOverview.approved + hitsOverview.pending) / (hitsOverview.approved + hitsOverview.pending + hitsOverview.rejected)) * 100 ).toFixed(4)}%`; row.appendChild(col2); const hr = document.getElementById(`dashboard-hits-overview`).getElementsByTagName(`hr`)[1]; hr.parentNode.insertBefore(row, hr); } function allRejectedRate() { const row = document.createElement(`div`); row.className = `row m-b-sm`; const col1 = document.createElement(`div`); col1.className = `col-xs-7`; row.appendChild(col1); const strong = document.createElement(`strong`); strong.textContent = `All Rejected Rate`; col1.appendChild(strong); const col2 = document.createElement(`div`); col2.textContent = `${( (hitsOverview.approved / (hitsOverview.approved + hitsOverview.rejected + hitsOverview.pending)) * 100 ).toFixed(4)}%`; col2.className = `col-xs-5 text-xs-right`; row.appendChild(col2); const hr = document.getElementById(`dashboard-hits-overview`).getElementsByTagName(`hr`)[1]; hr.parentNode.insertBefore(row, hr); } function fourDigitPercents() { for (const row of document.getElementById(`dashboard-hits-overview`).getElementsByClassName(`row`)) { if (row.textContent.includes(`Approval Rate`)) { row.getElementsByClassName(`text-xs-right`)[0].textContent = `${( (hitsOverview.approved / (hitsOverview.approved + hitsOverview.rejected)) * 100 ).toFixed(4)}%`; } if (row.textContent.includes(`Rejection Rate`)) { row.getElementsByClassName(`text-xs-right`)[0].textContent = `${( (hitsOverview.rejected / (hitsOverview.approved + hitsOverview.rejected)) * 100 ).toFixed(4)}%`; } } } function hitStatusChanges() { const old = localStorage.getItem(`statusDetailsObj`) ? JSON.parse(localStorage.getItem(`statusDetailsObj`)) : {}; localStorage.setItem(`statusDetailsObj`, JSON.stringify(statusDetailsObj)); function applyChanges(node) { node.querySelectorAll(`.desktop-row`).forEach((row) => { const date = row.querySelector(`a`).href.split(`/status_details/`)[1]; row.querySelectorAll(`.text-xs-right`).forEach((col) => { const key = col.classList[2].replace(`-column`, ``).replace(`-`, `_`); const change = statusDetailsObj[date][key] - (old[date] ? old[date][key] : 0); if (change !== 0) { const span = document.createElement(`span`); span.textContent = key.includes(`rewards`) || key.includes(`earnings`) ? `${needPlus(change)}${toMoney(change)}` : `${needPlus(change)}${change}`; span.style.float = `left`; span.style.fontSize = `70%`; col.appendChild(span); } }); }); } const observer = new MutationObserver((mutationsList, observer) => { mutationsList.forEach((mutation) => { const addedNode = mutation.addedNodes[0]; if (addedNode && addedNode.classList.contains(`expanded-row`)) { applyChanges(addedNode); } }); }); observer.observe(statusDetailsTable, { childList: true, subtree: true }); } function latestActivity() { const latest = statusDetailsArr[0]; const date = toDate(latest.date); const container = document.createElement(`div`); container.className = `row m-b-xl`; const col = document.createElement(`div`); col.className = `col-xs-12`; container.appendChild(col); const h2 = document.createElement(`h2`); h2.className = `m-b-md`; h2.textContent = `Activity for ${date}`; col.appendChild(h2); const row = document.createElement(`div`); row.className = `row`; col.appendChild(row); const col2 = document.createElement(`div`); col2.className = `col-xs-12`; row.appendChild(col2); const border = document.createElement(`div`); border.className = `border-gray-lightest p-a-sm`; col2.appendChild(border); const earningsRow = document.createElement(`div`); earningsRow.className = `row m-b-sm`; border.appendChild(earningsRow); const earningsText = document.createElement(`div`); earningsText.className = `col-xs-7 col-sm-6 col-lg-7`; earningsRow.appendChild(earningsText); const earningsStrong = document.createElement(`strong`); earningsStrong.textContent = `Projected Earnings`; earningsText.appendChild(earningsStrong); const earningsValue = document.createElement(`div`); earningsValue.className = `col-xs-5 col-sm-6 col-lg-5 text-xs-right`; earningsValue.textContent = localStorage.todaysearnings || `$0.00`; earningsRow.appendChild(earningsValue); const hourlyRow = document.createElement(`div`); hourlyRow.className = `row m-b-sm`; border.appendChild(hourlyRow); const hourlyText = document.createElement(`div`); hourlyText.className = `col-xs-6 col-sm-6 col-lg-6`; hourlyRow.appendChild(hourlyText); const hourlyStrong = document.createElement(`strong`); hourlyStrong.textContent = `Total Hourly`; hourlyText.appendChild(hourlyStrong); const hourlyTime = document.createElement(`div`); hourlyTime.className = `col-xs-3 col-sm-3 col-lg-3 text-xs-right`; hourlyTime.textContent = `00:00:00`; hourlyRow.appendChild(hourlyTime); const hourlyValue = document.createElement(`div`); hourlyValue.className = `col-xs-3 col-sm-3 col-lg-3 text-xs-right`; hourlyValue.textContent = `$0.00`; hourlyRow.appendChild(hourlyValue); const bonusesRow = document.createElement(`div`); bonusesRow.className = `row m-b-sm`; border.appendChild(bonusesRow); const bonusesText = document.createElement(`div`); bonusesText.className = `col-xs-7 col-sm-6 col-lg-7`; bonusesRow.appendChild(bonusesText); const bonusesStrong = document.createElement(`strong`); bonusesStrong.textContent = `Bonuses`; bonusesText.appendChild(bonusesStrong); const bonusesValue = document.createElement(`div`); bonusesValue.className = `col-xs-5 col-sm-6 col-lg-5 text-xs-right`; bonusesValue.textContent = localStorage.todaysbonuses || `$0.00`; bonusesRow.appendChild(bonusesValue); const collapse = document.createElement(`div`); collapse.id = `TodaysActivityAdditionalInfo`; collapse.className = `collapse`; border.appendChild(collapse); const hr = document.createElement(`hr`); hr.className = `m-b-sm m-t-0`; collapse.appendChild(hr); const hr2 = document.createElement(`hr`); hr2.className = `m-b-sm m-t-0`; border.appendChild(hr2); const control = document.createElement(`a`); control.className = `collapse-more-less`; control.href = `#TodaysActivityAdditionalInfo`; control.setAttribute(`aria-controls`, `TodaysActivityAdditionalInfo`); control.setAttribute(`aria-expanded`, `false`); control.setAttribute(`data-toggle`, `collapse`); border.appendChild(control); const more = document.createElement(`span`); more.className = `more`; control.appendChild(more); const plus = document.createElement(`i`); plus.className = `fa fa-plus-circle`; more.appendChild(plus); const moreText = document.createTextNode(`\nMore\n`); more.appendChild(moreText); const less = document.createElement(`span`); less.className = `less`; control.appendChild(less); const minus = document.createElement(`i`); minus.className = `fa fa-minus-circle`; less.appendChild(minus); const lessText = document.createTextNode(`\nLess\n`); less.appendChild(lessText); const side = document.querySelector(`.col-md-push-8`); side.insertBefore(container, side.firstChild); bonusesValue.textContent = `$${latest.bonus_rewards.toLocaleString(`en-US`, { minimumFractionDigits: 2 })}`; let hitLog = date === localStorage.WMTD_date ? (localStorage.WMTD_hitLog ? JSON.parse(localStorage.WMTD_hitLog) : {}) : {}; async function get(page, rescan) { try { page = Number.isInteger(page) ? page : 1; earningsValue.textContent = `Calculating Page ${page}`; const fetchURL = new URL(`https://worker.mturk.com/status_details/${date}`); fetchURL.searchParams.append(`page_number`, page); fetchURL.searchParams.append(`format`, `json`); const response = await fetch(fetchURL, { credentials: `include`, }); if (response.status === 429) { return setTimeout(get, 2000, page, rescan); } const json = await response.json(); for (const hit of json.results) { hitLog[hit.hit_id] = hit; } const logLength = Object.keys(hitLog).length; const expectedLength = Number(page) * 20 - 20 + json.num_results; if (!rescan && logLength !== expectedLength) { return get(1, true); } else { localStorage.WMTD_hitLog = JSON.stringify(hitLog); } localStorage.WMTD_lastPage = page; if (json.results.length === 20) { return get(++page, rescan); } else if (logLength !== json.total_num_results) { hitLog = {}; return get(1, true); } else { let projectedEarnings = 0; const reqLog = {}; const hourlyLog = {}; for (const key in hitLog) { const hit = hitLog[key]; if (hit.status !== `Rejected`) { projectedEarnings += hit.reward.amount_in_dollars; } if (!reqLog[hit.requester_id]) { reqLog[hit.requester_id] = { requester_id: hit.requester_id, requester_name: hit.requester_name, reward: hit.reward.amount_in_dollars, submitted: 1, }; } else { reqLog[hit.requester_id].submitted += 1; reqLog[hit.requester_id].reward += hit.reward.amount_in_dollars; } if (!hourlyLog[hitLog[key].requester_id]){ var time_data = localStorage.getItem('time_data.' + hitLog[key].hit_id.split('&')[0]); if (time_data !== null){ var starts = time_data.split("$#$")[3].split('?'); var last_start = starts[starts.length-1]; var stops = time_data.split("$#$")[4].split('?'); var last_stop = stops[stops.length-1]; if (last_start.length && last_stop.length){ hourlyLog[hitLog[key].requester_id] = { req : hitLog[key].requester_name, intervals :[[last_start,last_stop]], totalReward : parseFloat(time_data.split("$#$")[2]), }; } } } else{ var time_data = localStorage.getItem('time_data.' + hitLog[key].hit_id.split('&')[0]); if (time_data !== null){ var starts = time_data.split("$#$")[3].split('?'); var last_start = starts[starts.length-1]; var stops = time_data.split("$#$")[4].split('?'); var last_stop = stops[stops.length-1]; if (last_start.length && last_stop.length){ hourlyLog[hitLog[key].requester_id].intervals.push([last_start,last_stop]); } hourlyLog[hitLog[key].requester_id].totalReward += parseFloat(time_data.split("$#$")[2]); } } } const sort = Object.keys(reqLog).sort((a, b) => reqLog[a].reward - reqLog[b].reward); const fragment = document.createDocumentFragment(); var total_intervals = []; var total_recorded_earnings = 0; for (var j = sort.length-1; j > -1; j--){ var dkey = sort[j]; var d_req = reqLog[dkey].requester_name; var d_submitted = reqLog[dkey].submitted; var d_reward = Number(reqLog[dkey].reward).toFixed(2); var d_hourly = "N/A"; if (d_req !== "Bonuses" && hourlyLog[reqLog[dkey].requester_id]){ var intervals = hourlyLog[reqLog[dkey].requester_id].intervals; var d_intervals_sum = 0; for (i=0;i<intervals.length;i++){ d_intervals_sum += (intervals[i][1]-intervals[i][0]); } var d_intervals_avg = d_intervals_sum / intervals.length; var d_intervals = mergeIntervals(intervals); d_intervals = combineIntervals(intervals,Math.max(2*60*1000,Math.min(10*d_intervals_avg,10*60*1000))); total_intervals = total_intervals.concat(d_intervals); total_recorded_earnings += hourlyLog[reqLog[dkey].requester_id].totalReward; var d_time = 0; for (i=0;i<d_intervals.length; i++){ var s = new Date(parseInt(d_intervals[i][0])); var f = new Date(parseInt(d_intervals[i][1])); //console.log(d_req, i, s.toLocaleTimeString(),f.toLocaleTimeString()); d_time += (d_intervals[i][1] - d_intervals[i][0])/(1000*60*60); } d_hourly = "$" + (hourlyLog[reqLog[dkey].requester_id].totalReward / d_time).toFixed(2); } reqLog[dkey].hourly = d_hourly; } total_intervals = mergeIntervals(total_intervals); var total_time = 0; for (i=0;i<total_intervals.length; i++){ var s = new Date(parseInt(total_intervals[i][0])); var f = new Date(parseInt(total_intervals[i][1])); //console.log(i, s.toLocaleTimeString(),f.toLocaleTimeString()); total_time += (total_intervals[i][1] - total_intervals[i][0])/(1000*60*60); } var total_hourly = '$' + (total_recorded_earnings/total_time).toFixed(2); var hour = Math.floor(total_time); var min = Math.floor((total_time - hour)*60); var sec = Math.floor((total_time - hour - min/60)*3600); var time_display = (hour>0?pad(hour,2) + ":":"") + pad(min,2) + ":" + pad(sec,2); hourlyValue.textContent = total_hourly; hourlyTime.textContent = time_display; for (let i = sort.length - 1; i > -1; i--) { const key = sort[i]; const requester_name = reqLog[key].requester_name; const reward = `$${reqLog[key].reward.toLocaleString(`en-US`, { minimumFractionDigits: 2 })}`; const submitted = reqLog[key].submitted; const hourly = reqLog[key].hourly; const reqRow = document.createElement(`div`); reqRow.className = `row m-b-sm`; fragment.appendChild(reqRow); const requester = document.createElement(`div`); requester.className = `col-xs-4`; reqRow.appendChild(requester); const requesterStrong = document.createElement(`strong`); requesterStrong.textContent = requester_name; requester.appendChild(requesterStrong); const submitValue = document.createElement(`div`); submitValue.className = `col-xs-2 text-xs-right`; submitValue.textContent = submitted; reqRow.appendChild(submitValue); const rewardValue = document.createElement(`div`); rewardValue.className = `col-xs-3 text-xs-right`; rewardValue.textContent = reward; reqRow.appendChild(rewardValue); const hourlyValue = document.createElement(`div`); hourlyValue.className = `col-xs-3 text-xs-right`; hourlyValue.textContent = hourly; reqRow.appendChild(hourlyValue); } collapse.appendChild(fragment); earningsValue.textContent = `$${projectedEarnings.toLocaleString(`en-US`, { minimumFractionDigits: 2 })}`; } } catch (error) { earningsValue.textContent = error; } } get( date === localStorage.WMTD_date ? (localStorage.WMTD_lastPage ? Number(localStorage.WMTD_lastPage) : 1) : 1, false, ); localStorage.WMTD_date = date; } function openFirstWeek() { statusDetailsTable.querySelector(`.fa.expand-button.fa-plus-circle`).click(); } function rejectionsBelow99() { const row = document.createElement(`div`); row.className = `row m-b-sm`; const col1 = document.createElement(`div`); col1.className = `col-xs-7`; row.appendChild(col1); const strong = document.createElement(`strong`); strong.textContent = `Rejections ≤ 99%`; col1.appendChild(strong); const col2 = document.createElement(`div`); col2.textContent = Math.round( (hitsOverview.rejected - 0.01 * (hitsOverview.approved + hitsOverview.rejected + hitsOverview.pending)) / -0.99, ).toLocaleString(); col2.className = `col-xs-5 text-xs-right`; row.appendChild(col2); const additional = document .getElementById(`dashboard-hits-overview`) .getElementsByClassName(`border-gray-lightest`)[0]; additional.appendChild(row); } function rejectionsBelow95() { const row = document.createElement(`div`); row.className = `row m-b-sm`; const col1 = document.createElement(`div`); col1.className = `col-xs-7`; row.appendChild(col1); const strong = document.createElement(`strong`); strong.textContent = `Rejections ≤ 95%`; col1.appendChild(strong); const col2 = document.createElement(`div`); col2.textContent = Math.round( (hitsOverview.rejected - 0.05 * (hitsOverview.approved + hitsOverview.rejected + hitsOverview.pending)) / -0.95, ).toLocaleString(); col2.className = `col-xs-5 text-xs-right`; row.appendChild(col2); const additional = document .getElementById(`dashboard-hits-overview`) .getElementsByClassName(`border-gray-lightest`)[0]; additional.appendChild(row); } allApprovedRate(); allRejectedRate(); fourDigitPercents(); hitStatusChanges(); latestActivity(); openFirstWeek(); rejectionsBelow99(); rejectionsBelow95(); } else if (location.href.indexOf('worker.mturk.com/status_details') > -1){ $('span.reward-column:eq(0)').before('<span class="hourly-column text-xs-right column-header p-x-sm" data-reactid=".2.$header.$2">Hourly</span>'); $('li.table-row').each(function(){ var hitId = $(this).find('a[href*="contact_requester"]:eq(0)').attr('href').split('hit_id=')[1].split('&')[0]; var feedback = {opened:"N/A",submitted:"N/A", time:"N/A", hourly:"N/A"}; var time_data = localStorage.getItem('time_data.' + hitId); if (time_data){ feedback = create_feedback(time_data); } $(this).find('.reward-column').before('<span class="p-x-sm column hourly-column text-xs-right"><span class="hourlyCopy" title="Opened: ' + feedback.opened +'
Submitted: ' + feedback.submitted +'
Time: ' + feedback.time +'
Hourly: ' + feedback.hourly +'">' + feedback.hourly+ '</span></span>'); }); $('.hourlyCopy').click(function(evt){ evt.preventDefault(); //GM_setClipboard($(this).attr('title')); GM_setClipboard('Time:' + $(this).attr('title').split('Time:')[1]); }); } else if (location.href.indexOf('worker.mturk.com') > -1){ var hit_returned = false; if(typeof(Storage)!=="undefined") { $('button:contains("Return")').click(function(){ hit_returned = true; }); store_data('openWorker'); window.addEventListener('beforeunload', function(){ store_data('closeWorker'); }); } } function create_feedback(time_data) { var last_start = time_data.split("$#$")[3].split('?'); last_start = new Date(parseInt(last_start[last_start.length - 1])); var last_finish = time_data.split("$#$")[4].split('?'); last_finish = new Date(parseInt(last_finish[last_finish.length - 1])); //console.log(time_data, last_start, last_finish, time_data.split("$#$")[3].split('?'), time_data.split("$#$")[4].split('?')); var reward = time_data.split("$#$")[2]; var time_spent = last_finish - last_start; var h = Math.floor(time_spent/(1000*60*60)); var m = Math.floor((time_spent - h*1000*60*60)/(1000*60)); var s = Math.floor((time_spent - h*1000*60*60 - m*1000*60)/(1000)); var f = {opened:last_start.toLocaleTimeString(), submitted:last_finish.toLocaleTimeString(), time: ((h > 0 ? (pad(h,2) + ":"):"") + pad(m,2) + ":" + pad(s,2)), hourly: '$' + (reward/((last_finish-last_start)/(60*60*1000))).toFixed(2)}; console.log(f); return f; // return "Opened: " + last_start.toLocaleTimeString() + "<br>Submitted: " + last_finish.toLocaleTimeString()+ "<br>Time: " + (h > 0 ? (pad(h,2) + ":"):"") + pad(m,2) + ":" + pad(s,2) + "<br>Hourly: $" + (reward/((last_finish-last_start)/(60*60*1000))).toFixed(2) ; } function store_data(action_type) { if (!hit_returned){ var now_in_milliseconds = new Date().getTime(); var hitReview_hitId = window.location.href.split('tasks/')[1].split('?')[0]; console.log(action_type); var $data_holder = $('a:contains("HIT Details"):eq(0)').parent().attr('data-react-props'); var requester_name = JSON.parse($data_holder).modalOptions.requesterName; var hit_title = JSON.parse($data_holder).modalOptions.projectTitle; var hit_reward = JSON.parse($data_holder).modalOptions.monetaryReward.amountInDollars; var open_list = ""; var close_list = ""; var stored_copy = localStorage.getItem('time_data.' + hitReview_hitId); if (stored_copy !== null){ open_list = stored_copy.split('$#$')[3]; close_list = stored_copy.split('$#$')[4]; } if (action_type == "openWorker"){ open_list += "?" + now_in_milliseconds; } else if (action_type == "closeWorker"){ close_list += "?" + now_in_milliseconds; } var autoapprove_data = requester_name + "$#$" + hit_title + "$#$"+ hit_reward +"$#$" + open_list + "$#$" + close_list; console.log(autoapprove_data); localStorage.setItem('time_data.' + hitReview_hitId, autoapprove_data); } } function mergeIntervals(intervals) { // test if there are at least 2 intervals if(intervals.length <= 1) return intervals; var stack = []; var top = null; // sort the intervals based on their start values intervals = intervals.sort(function (startValue, endValue) { if (startValue[0] > endValue[0]) { return 1; } if (startValue[0] < endValue[0]) { return -1; } return 0; }); // push the 1st interval into the stack stack.push(intervals[0]); // start from the next interval and merge if needed for (var i = 1; i < intervals.length; i++) { // get the top element top = stack[stack.length - 1]; // if the current interval doesn't overlap with the // stack top element, push it to the stack if (top[1] < intervals[i][0]) { stack.push(intervals[i]); } // otherwise update the end value of the top element // if end of current interval is higher else if (top[1] < intervals[i][1]) { top[1] = intervals[i][1]; stack.pop(); stack.push(top); } } return stack; } function combineIntervals(intervals, padding) { // test if there are at least 2 intervals if(intervals.length <= 1) return intervals; var stack = []; var top = null; // push the 1st interval into the stack stack.push(intervals[0]); // start from the next interval and merge if needed for (var i = 1; i < intervals.length; i++) { // get the top element top = stack[stack.length - 1]; // if the current interval doesn't overlap with the // stack top element, push it to the stack if ((intervals[i][0]-top[1]) > padding) { // console.log("hi",(intervals[i][0]-top[1]) + padding); stack.push(intervals[i]); } // otherwise update the end value of the top element // if end of current interval is higher else if (top[1] < intervals[i][1]) { top[1] = intervals[i][1]; stack.pop(); stack.push(top); } } //console.log("stack",stack); return stack; } function pad(n, width, z) { z = z || '0'; n = n + ''; return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n; } function Remove_Old_Data(){ var today = new Date(); var date = today.getFullYear()+'-'+(today.getMonth()+1)+'-'+today.getDate(); if (localStorage.getItem('mturkHourlyRemovalDate') != date){ console.log('mturk hourly data older than 14 days has been cleared'); //thanks to @ChrisTurk for the old key removal code: Object.keys(localStorage) .forEach(function(key){ if (/^time_data\./.test(key)) { console.log(localStorage[key]); var stored = localStorage[key].split(/\?/).pop(); // grabs last timestamp from the stack // days * hours * min * sec * 1000 [millisec 'cause of the way its stored] if (($.now()-stored) > 14*24*60*60*1000) { // console.log('older than 14 days'); localStorage.removeItem(key); //uncomment this line to delete keys } else { // console.log('fresh memes'); //less than 1 day old, don't delete it. } } }); localStorage.setItem('mturkHourlyRemovalDate',date); } }