您需要先安装一款用户样式管理器扩展(如 Stylus)后才能安装此样式。
您需要先安装一款用户样式管理器扩展(如 Stylus)后才能安装此样式。
您需要先安装一款用户样式管理器扩展(如 Stylus)后才能安装此样式。
您需要先安装一款用户样式管理器扩展后才能安装此样式。
您需要先安装一款用户样式管理器扩展后才能安装此样式。
您需要先安装一款用户样式管理器扩展后才能安装此样式。
(我已经安装了用户样式管理器,让我安装!)
// ==UserScript==
// @name [MTurk Worker] Dashboard Enhancer
// @namespace http://kadauchi.com/
// @version 2.2.0
// @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==
try { if (mturksuite) return; } catch (e) {}
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 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 = {};
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 ? 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();