// ==UserScript== // @name [MTurk Worker] Dashboard Enhancer // @namespace http://kadauchi.com/ // @version 2.0.4 // @description Brings many enhancements to the MTurk Worker Dashboard. // @author Kadauchi // @icon http://i.imgur.com/oGRQwPN.png // @include https://worker.mturk.com/dashboard* // @downloadURL none // ==/UserScript== const dashboard = new Object(); (function () { Object.assign(String.prototype, { toNumberDE () { return Number(this.replace(/[^0-9.]/g, ``)); } }); Object.assign(Number.prototype, { toNumberDE () { return this; } }); const rows = document.getElementById(`dashboard-hits-overview`).getElementsByClassName(`row`); dashboard.hits_overview = { approved: rows[0].getElementsByClassName(`text-xs-right`)[0].textContent.toNumberDE(), pending: rows[2].getElementsByClassName(`text-xs-right`)[0].textContent.toNumberDE(), rejected: rows[3].getElementsByClassName(`text-xs-right`)[0].textContent.toNumberDE(), }; dashboard.daily_hit_statistics_overview = { total: { submitted: 0, approved: 0, rejected: 0, pending: 0, earnings: 0, } }; dashboard.earnings_to_date = { bonuses: document.getElementById(`dashboard-earnings-to-date`).getElementsByClassName(`text-xs-right`)[2].textContent.toNumberDE() }; for (const row of document.getElementsByClassName(`daily_hit_statuses`)) { const col = row.children; const date = col[0].children[0].href.match(/[0-9]{4}-[0-9]{2}-[0-9]{2}/)[0]; dashboard.daily_hit_statistics_overview[date] = new Object(); [`submitted`, `approved`, `rejected`, `pending`, `earnings`].forEach((currentValue, index) => { const value = col[index + 1].textContent.toNumberDE(); dashboard.daily_hit_statistics_overview[date][currentValue] = value; dashboard.daily_hit_statistics_overview.total[currentValue] += value; }); } })(); (function allApprovedRate() { const overview = dashboard.hits_overview; 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 = `${((overview.approved + overview.pending) / (overview.approved + overview.pending + overview.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 overview = dashboard.hits_overview; 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 = `${(overview.approved / (overview.approved + overview.rejected + overview.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() { const overview = dashboard.hits_overview; for (const row of document.getElementById(`dashboard-hits-overview`).getElementsByClassName(`row`)) { if (row.textContent.indexOf(`Approval Rate`) !== -1) { row.getElementsByClassName(`text-xs-right`)[0].textContent = `${(overview.approved / (overview.approved + overview.rejected) * 100).toFixed(4)}%`; } if (row.textContent.indexOf(`Rejection Rate`) !== -1) { row.getElementsByClassName(`text-xs-right`)[0].textContent = `${(overview.rejected / (overview.approved + overview.rejected) * 100).toFixed(4)}%`; } } })(); (function rejectionsBelow99() { const overview = dashboard.hits_overview; 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((overview.rejected - (0.01 * (overview.approved + overview.rejected + overview.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 overview = dashboard.hits_overview; 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((overview.rejected - (0.05 * (overview.approved + overview.rejected + overview.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); })(); (function totalLast45Days() { const table = document.querySelector(`.mturk-table.hits-statuses`); const row = table.insertRow(); row.className = `daily_hit_statuses`; const date = row.insertCell(0); date.textContent = `Total`; date.className = `hidden-xs-down col-sm-2 col-md-2`; const submitted = row.insertCell(1); submitted.textContent = dashboard.daily_hit_statistics_overview.total.submitted; submitted.className = `text-xs-right col-xs-3 col-sm-2 col-md-2`; const approved = row.insertCell(2); approved.textContent = dashboard.daily_hit_statistics_overview.total.approved; approved.className = `text-xs-right col-xs-3 col-sm-2 col-md-2`; const rejected = row.insertCell(3); rejected.textContent = dashboard.daily_hit_statistics_overview.total.rejected; rejected.className = `text-xs-right col-xs-3 col-sm-2 col-md-2`; const pending = row.insertCell(4); pending.textContent = dashboard.daily_hit_statistics_overview.total.pending; pending.className = `text-xs-right col-xs-3 col-sm-2 col-md-2`; const earnings = row.insertCell(5); earnings.textContent = `$${dashboard.daily_hit_statistics_overview.total.earnings.toLocaleString(`en-US`, { minimumFractionDigits: 2 })}`; earnings.className = `text-xs-right col-xs-3 col-sm-2 col-md-2`; })(); (function hitStatusChanges() { const oldDashboard = localStorage.dashboard ? JSON.parse(localStorage.dashboard) : null; localStorage.dashboard = JSON.stringify(dashboard); for (const row of document.getElementsByClassName(`daily_hit_statuses`)) { const col = row.children; const date = col[0].children[0] ? col[0].children[0].href.match(/[0-9]{4}-[0-9]{2}-[0-9]{2}/)[0] : `total`; [`submitted`, `approved`, `rejected`, `pending`, `earnings`].forEach((currentValue, index) => { const value = col[index + 1].textContent.toNumberDE(); const oldValue = oldDashboard ? oldDashboard.daily_hit_statistics_overview[date][currentValue] : 0; if (value !== oldValue) { const change = value - oldValue; const changeString = change.toFixed(2).toString().replace(`.00`, ``); if (Math.round(change * 100) !== 0) { const span = document.createElement(`span`); span.textContent = change > 0 ? `+${changeString}` : changeString; span.style.float = `left`; span.style.fontSize = `70%`; col[index + 1].appendChild(span); } } }); } })(); (function todaysActivity() { 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 = `Today's Activity`; 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 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); const today = document.querySelector(`a[href^="/status_details/"]`); if (today.textContent === `Today`) { const date = today.href.match(/[0-9]{4}-[0-9]{2}-[0-9]{2}/)[0]; if (date === localStorage.WMTD_date) { if (!localStorage.WMTD_bonusStart) { localStorage.WMTD_bonusStart = dashboard.earnings_to_date.bonuses; } } else { if (dashboard.daily_hit_statistics_overview[date].approved === 0) { localStorage.WMTD_bonusStart = dashboard.earnings_to_date.bonuses - dashboard.daily_hit_statistics_overview[date].earnings; } else { localStorage.WMTD_bonusStart = dashboard.earnings_to_date.bonuses; } } bonusesValue.textContent = `$${(dashboard.earnings_to_date.bonuses - localStorage.WMTD_bonusStart).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, url, 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 = page.toNumberDE() * 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 = new Object(); return get(1, true); } else { let projectedEarnings = 0; const reqLog = {}; 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; } } const sort = Object.keys(reqLog).sort((a, b) => reqLog[a].reward - reqLog[b].reward); const fragment = document.createDocumentFragment(); 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 reqRow = document.createElement(`div`); reqRow.className = `row m-b-sm`; fragment.appendChild(reqRow); const requester = document.createElement(`div`); requester.className = `col-xs-6`; reqRow.appendChild(requester); const requesterStrong = document.createElement(`strong`); requesterStrong.textContent = requester_name; requester.appendChild(requesterStrong); const submitValue = document.createElement(`div`); submitValue.className = `col-xs-3 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); } collapse.appendChild(fragment); earningsValue.textContent = `$${projectedEarnings.toLocaleString(`en-US`, { minimumFractionDigits: 2 })}`; } } catch (error) { earningsValue.textContent = error; } } get(date === localStorage.WMTD_date ? localStorage.WMTD_lastPage ? localStorage.WMTD_lastPage.toNumberDE() : 1 : 1, false); localStorage.WMTD_date = date; } else { earningsValue.textContent = `N/A`; bonusesValue.textContent = `N/A`; } })();