Skip to content

Instantly share code, notes, and snippets.

@JrRandy
Forked from cuylerstuwe/mturk-slow-or-just-me.user.js
Last active September 16, 2019 07:08
Show Gist options
  • Save JrRandy/05d13bad86d40c43317ef8464817705f to your computer and use it in GitHub Desktop.
Save JrRandy/05d13bad86d40c43317ef8464817705f to your computer and use it in GitHub Desktop.
// ==UserScript==
// @name mTurk Slow or Just Me?
// @namespace salembeats
// @version 1.34a
// @description UPDATE: Same as Salems matching revision, with earnings breakdown
// @author Cuyler Stuwe (salembeats)
// @include https://worker.mturk.com/dashboard*
// ==/UserScript==
// Minified, synchronous SHA-256 transform function copied from: http://geraintluff.github.io/sha256/
var sha256=function a(b){function c(a,b){return a>>>b|a<<32-b}for(var d,e,f=Math.pow,g=f(2,32),h="length",i="",j=[],k=8*b[h],l=a.h=a.h||[],m=a.k=a.k||[],n=m[h],o={},p=2;64>n;p++)if(!o[p]){for(d=0;313>d;d+=p)o[d]=p;l[n]=f(p,.5)*g|0,m[n++]=f(p,1/3)*g|0}for(b+="\x80";b[h]%64-56;)b+="\x00";for(d=0;d<b[h];d++){if(e=b.charCodeAt(d),e>>8)return;j[d>>2]|=e<<(3-d)%4*8}for(j[j[h]]=k/g|0,j[j[h]]=k,e=0;e<j[h];){var q=j.slice(e,e+=16),r=l;for(l=l.slice(0,8),d=0;64>d;d++){var s=q[d-15],t=q[d-2],u=l[0],v=l[4],w=l[7]+(c(v,6)^c(v,11)^c(v,25))+(v&l[5]^~v&l[6])+m[d]+(q[d]=16>d?q[d]:q[d-16]+(c(s,7)^c(s,18)^s>>>3)+q[d-7]+(c(t,17)^c(t,19)^t>>>10)|0),x=(c(u,2)^c(u,13)^c(u,22))+(u&l[1]^u&l[2]^l[1]&l[2]);l=[w+x|0].concat(l),l[4]=l[4]+w|0}for(d=0;8>d;d++)l[d]=l[d]+r[d]|0}for(d=0;8>d;d++)for(e=3;e+1;e--){var y=l[d]>>8*e&255;i+=(16>y?0:"")+y.toString(16)}return i};
function exists(thing) {
return thing !== undefined && thing !== null;
}
function medianOfSortedValues(sortedValuesArray) {
const middlePosition = Math.floor(sortedValuesArray.length/2);
if(sortedValuesArray.length % 2) {
return sortedValuesArray[middlePosition];
}
else {
return (sortedValuesArray[middlePosition-1] + sortedValuesArray[middlePosition]) / 2.0;
}
}
function sidesOfMedian(sortedValuesArray) {
const hasEvenNumberOfItems = ( sortedValuesArray.length % 2 === 0 );
if(hasEvenNumberOfItems) {
return [
sortedValuesArray.slice(0, (sortedValuesArray.length / 2)),
sortedValuesArray.slice((sortedValuesArray.length / 2), sortedValuesArray.length)
];
}
else {
const indexToSplitOn = ((sortedValuesArray.length + 1) / 2) - 1;
return [
sortedValuesArray.slice(0, indexToSplitOn),
sortedValuesArray.slice(indexToSplitOn + 1, sortedValuesArray.length)
];
}
}
function hitLogTotalUsd() {
const hitLog = JSON.parse(localStorage.getItem("WMTD_hitLog"));
return Object.keys(hitLog).reduce((acc, currentHitLogKey) => {
return (acc + hitLog[currentHitLogKey].reward.amount_in_dollars);
}, 0);
}
function todaysDateYYYYMMDD() {
const now = new Date();
const [year, month, day] = [now.getFullYear(), now.getMonth() + 1, now.getDate()].map(num => num.toLocaleString("en-US", {minimumIntegerDigits: 2, useGrouping: false}));
const todaysDateStr = `${year}-${month}-${day}`;
return todaysDateStr;
}
function todaysTotal() {
const lastRecordedDate = localStorage.getItem("WMTD_date");
if(lastRecordedDate === todaysDateYYYYMMDD()) {
return hitLogTotalUsd();
}
else {
return null;
}
}
function todaysTotalMTS() {
const mtsProjectedEarnings = document.getElementById('mts-ht-earnings').textContent;
return Number(mtsProjectedEarnings.replace(/[^0-9.]/g, ''));
}
function workerId() {
const workerIdCopyElement = document.querySelector(`[data-react-class="require('reactComponents/common/CopyText')['default']"]`);
return JSON.parse(workerIdCopyElement.dataset.reactProps).textToCopy;
}
function workerIdHash() {
return sha256(workerId());
}
function userEarningsPayload() {
const payload = JSON.stringify({
idHash: workerIdHash(),
date: todaysDateYYYYMMDD(),
total: todaysTotal() || todaysTotalMTS()
});
return payload;
}
async function submitUserEarnings() {
const apiEndpoint = `https://ne26dv9hq6.execute-api.us-west-2.amazonaws.com/dev/store`;
const responseStream = await fetch(apiEndpoint, {
method: "POST",
body: userEarningsPayload(),
headers: {
"Content-Type": "application/json"
}
});
const response = await responseStream.json();
return response;
}
function injectAveragePERow() {
const el = document.getElementById("TodaysActivityAdditionalInfo") || document.getElementById('dashboard-available-earnings').querySelector('.border-gray-lightest').children[0];
el.insertAdjacentHTML("beforebegin", `
<div class="row m-b-sm">
<div class="col-xs-7 col-sm-6 col-lg-7">
<strong>Today's Community Average</strong>
</div>
<div class="col-xs-5 col-sm-6 col-lg-5 text-xs-right">
<a id="retrieveCommunityAverage" href="javascript:void(0);">Retrieve Community Average</a>
</div>
</div>
<div class="row m-b-sm">
<div class="col-xs-7 col-sm-6 col-lg-7">
<strong>TC Low, Med, High</strong>
</div>
<div class="col-xs-5 col-sm-6 col-lg-5 text-xs-right">
<div id="tcLowMedHigh">&nbsp;</div>
</div>
</div>
<div class="row m-b-sm">
<div class="col-xs-7 col-sm-6 col-lg-7">
<strong>Box+Whisker Plot</strong>
</div>
<div style="text-align: center;">
<img id="boxplotImage" style="max-width: 270px;" src="">
</div>
</div>
<div class="row m-b-sm">
<div class="col-xs-7 col-sm-6 col-lg-7">
<strong>TC Rank</strong>
</div>
<div class="col-xs-5 col-sm-6 col-lg-5 text-xs-right">
<div id="rank"></div>
</div>
</div>
<div class="row m-b-sm">
<div class="col-xs-7 col-sm-6 col-lg-7">
<strong>Nearest Competitor</strong>
</div>
<div class="col-xs-5 col-sm-6 col-lg-5 text-xs-right">
<div id="nearestCompetitor"></div>
</div>
</div>
<div class="row m-b-sm">
<div class="col-xs-7 col-sm-6 col-lg-7">
<strong>Earnings Breakdown</strong>
</div>
<div class="col-xs-5 col-sm-6 col-lg-5 text-xs-right">
<div id="EarnBreakdown">&nbsp;</div>
</div>
</div>
`);
}
function boxplotRenderUrl(params = {
low: undefined,
high: undefined,
median: undefined,
firstQuartile: undefined,
thirdQuartile: undefined
}) {
return (`http://www.imathas.com/stattools/boxplot.php?n=1&showlabels=0&title0=&ds0q0=${params.low.replace("$", "")}&ds0q1=${params.firstQuartile.replace("$", "")}&ds0q2=${params.median.replace("$", "")}&ds0q3=${params.thirdQuartile.replace("$", "")}&ds0q4=${params.high.replace("$", "")}&title1=&ds1q0=&ds1q1=&ds1q2=&ds1q3=&ds1q4=&title2=&ds2q0=&ds2q1=&ds2q2=&ds2q3=&ds2q4=&xmin=0&xmax=${Math.floor((+params.high.replace("$", "")) + 5)}&ticks=5&axistitle=&imgwidth=300&imgheight=120`);
}
function embedBoxAndWhiskerData(params = {
low: undefined,
high: undefined,
median: undefined,
firstQuartile: undefined,
thirdQuartile: undefined}) {
document.getElementById("tcLowMedHigh").dataset.boxAndWhiskerPoints = JSON.stringify(params);
document.getElementById("boxplotImage").setAttribute("src", boxplotRenderUrl(params));
}
function rank(userEarnings, allUsersAscendingSortedEarnings) {
return ( [...allUsersAscendingSortedEarnings].reverse().indexOf(userEarnings) + 1 );
}
function nearestCompetitorTotalAndGapTuple(userEarnings, allUsersAscendingSortedEarnings) {
const hasCompetitors = ( allUsersAscendingSortedEarnings.length > 1 );
if(!hasCompetitors) { return [userEarnings, 0]; }
const userEarningsIndex = allUsersAscendingSortedEarnings.indexOf(userEarnings);
const isLeader = ( userEarningsIndex === allUsersAscendingSortedEarnings.length - 1 );
const tiedEarningsErrorFactor = 0.01;
let nearestCompetitorEarnings;
if(isLeader) {
nearestCompetitorEarnings = [...allUsersAscendingSortedEarnings].reverse().find(earnings => {
return earnings < ( userEarnings - tiedEarningsErrorFactor );
});
}
else {
nearestCompetitorEarnings = allUsersAscendingSortedEarnings.find(earnings => {
return earnings > ( userEarnings + tiedEarningsErrorFactor );
});
}
const nearestCompetitorGap = nearestCompetitorEarnings - userEarnings;
return [nearestCompetitorEarnings, nearestCompetitorGap];
}
function displayRank(rank, numberOfPositions) {
const relativeThresholdAtWhichToDisplayTopPercentile = 0.5;
const topPercentileZeroToOne = ( 1 - ( (numberOfPositions - rank) / numberOfPositions ) );
const formattedTopPercentile = topPercentileZeroToOne.toLocaleString("en-US", {style: "percent"});
document.getElementById("rank").innerHTML = `# <strong>${rank}</strong> / ${numberOfPositions} ${topPercentileZeroToOne <= relativeThresholdAtWhichToDisplayTopPercentile ? `( Top <strong>${formattedTopPercentile}</strong> )` : ""}`;
}
function displayNearestCompetitorInfo(nearestCompetitorTotalAndGapTuple) {
const [nearestCompetitorTotal, nearestCompetitorGap] = nearestCompetitorTotalAndGapTuple;
const sign = ( nearestCompetitorGap >= 0 ? "+" : "-" );
const color = ( sign === "+" ? "green" : "red" );
const absoluteGap = Math.abs(nearestCompetitorGap);
const [nearestCompetitorTotalFormatted, absoluteGapFormatted] = [nearestCompetitorTotal, absoluteGap].map(val => val.toLocaleString("en-US", {style: "currency", currency: "USD"}));
document.getElementById("nearestCompetitor").innerHTML = `${nearestCompetitorTotalFormatted} ( <span style="color: ${color};">${sign} ${absoluteGapFormatted}</span> )`;
}
async function main() {
injectAveragePERow();
document.getElementById("retrieveCommunityAverage").addEventListener("click", async e => {
const earningsApiResponse = await submitUserEarnings();
if(exists(earningsApiResponse.averageEarningsForToday)) {
const sortedIndividualEarnings = [...earningsApiResponse.individualEarningsForToday].sort((a,b) => a-b);
const lowestIndividualEarnings = sortedIndividualEarnings[0].toLocaleString("en-US", {style: "currency", currency: "USD"});
const medianIndividualEarnings = medianOfSortedValues(sortedIndividualEarnings).toLocaleString("en-US", {style: "currency", currency: "USD"});
const highestIndividualEarnings = sortedIndividualEarnings[sortedIndividualEarnings.length-1].toLocaleString("en-US", {style: "currency", currency: "USD"});
const [firstQuartile, thirdQuartile] = sidesOfMedian(sortedIndividualEarnings).map(side => medianOfSortedValues(side).toLocaleString("en-US", {style: "currency", currency: "USD"}));
embedBoxAndWhiskerData({low: lowestIndividualEarnings,
high: highestIndividualEarnings,
median: medianIndividualEarnings,
firstQuartile,
thirdQuartile});
document.getElementById("tcLowMedHigh").innerHTML = `
<span style='color: red;'>${lowestIndividualEarnings}</span>,
<span style='color: orange;'>${medianIndividualEarnings}</span>,
<span style='color: green;'>${highestIndividualEarnings}</span>`;
e.target.innerText = earningsApiResponse.averageEarningsForToday.toLocaleString("en-US", {style: "currency", currency: "USD"});
const total = ( todaysTotal() || todaysTotalMTS() );
displayRank( rank(total, sortedIndividualEarnings), sortedIndividualEarnings.length );
displayNearestCompetitorInfo( nearestCompetitorTotalAndGapTuple(total, sortedIndividualEarnings) );
const revsortedIndividualEarnings = sortedIndividualEarnings.reverse();
var sampleArray = revsortedIndividualEarnings
sampleArray = sampleArray.map(function(each_element){
return Number(each_element.toFixed(2));
});
const earningsBreakdown = "$" + sampleArray.join("<br />$");
document.getElementById("EarnBreakdown").innerHTML = `
<span style='color: black;'>${earningsBreakdown}</span>`;
}
else if(earningsApiResponse.errorMessage === "Your request is missing fields: total") {
e.target.innerText = "Work before comparing!";
}
});
}
main();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment