// ==UserScript==
// @name [MTurk Worker] Dashboard Enhancer
// @namespace http://kadauchi.com/
// @version 2.0.2
// @description Brings many enhancements to the MTurk Worker Dashboard.
// @author Kadauchi
// @icon http://i.imgur.com/oGRQwPN.png
// @include https://worker.mturk.com/dashboard*
// ==/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 additional = document.getElementById(`HitsOverviewAdditionalInfo`);
additional.parentNode.insertBefore(row, additional);
})();
(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 additional = document.getElementById(`HitsOverviewAdditionalInfo`);
additional.parentNode.insertBefore(row, additional);
})();
(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(`HitsOverviewAdditionalInfo`);
additional.insertBefore(row, additional.lastChild);
})();
(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(`HitsOverviewAdditionalInfo`);
additional.insertBefore(row, additional.lastChild);
})();
(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 })}`;
const 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) {
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`;
}
})();