mTurk Title Bar Timer / Wage - Fork

Title bar timer/counter for MTurk

  1. // ==UserScript==
  2. // @name mTurk Title Bar Timer / Wage - Fork
  3. // @author parseHex
  4. // @namespace https://greasyfork.org/users/8394
  5. // @version 42.0.2
  6. // @description Title bar timer/counter for MTurk
  7. // @include https://worker.mturk.com/projects/*
  8. // @icon https://i.imgur.com/Y68Qxdd.png
  9. // @grant GM_setValue
  10. // @grant GM_getValue
  11. // ==/UserScript==
  12.  
  13. // Changing this to true will base wages on the total time passed since the tab has opened.
  14. // False means wages are based on the total time that the tab has been in focus.
  15. const ALWAYS_TICK_WAGE_TIMER = true;
  16.  
  17. // Whether to show the "Big Emoji", and configurable parameters related to its display (i.e., size and transparency).
  18. const SHOW_BIG_EMOJI = true;
  19. const BIG_EMOJI_SIZE = "250px";
  20. const BIG_EMOJI_OPACITY_ZERO_TO_ONE = "0.03";
  21.  
  22. // No need to change this. One second is alwyas 1000ms. Just here to add meaning to some code.
  23. const ONE_SECOND_IN_MS = 1000;
  24.  
  25. // The wage amounts. Change these to your liking.
  26. const LOVE_WAGE_LOWER_LIMIT = 12.00;
  27. const HAPPY_WAGE_LOWER_LIMIT = 8.00;
  28. const OK_WAGE_LOWER_LIMIT = 6.00;
  29. const SAD_WAGE_LOWER_LIMIT = 4.00;
  30. const DISGUSTED_WAGE_LOWER_LIMIT = 0.00;
  31.  
  32. // The emojis. Change these to whatever you want to display for each wage range.
  33. const LOVE_WAGE_EMOJI = "?";
  34. const HAPPY_WAGE_EMOJI = "?";
  35. const OK_WAGE_EMOJI = "?";
  36. const SAD_WAGE_EMOJI = "?";
  37. const DISGUSTED_WAGE_EMOJI = "?";
  38.  
  39. // Changing this to a bigger number will save you CPU cycles.
  40. // Changing it to a smaller number will give you more timely data.
  41. // Chrome automatically slows down the interval timers that this is based on in background tabs, though, so this is influenced strongly by that.
  42. // TODO: Fix this so we can change to a different update rate without breaking "seconds visible / invisible" (I know how to do it, but haven't bothered writing it yet since it doesn't earn me money to do so.)
  43. // The eventual fix will be to keep two separate stores of "seconds visible/invisible", managed by taking snapshots of the last time visibility status was changed.
  44. const UPDATE_RATE_IN_MILLISECONDS = (ALWAYS_TICK_WAGE_TIMER ? (ONE_SECOND_IN_MS / 3) : ONE_SECOND_IN_MS);
  45.  
  46. var Utility = (function createUtility() {
  47.  
  48. const _scriptStartTime = new Date().getTime();
  49. const _fallbackHITStartTime = (unsafeWindow.pageTimes === undefined ? _scriptStartTime : unsafeWindow.pageTimes.beginPageLoad);
  50. const _HITStartTime = (performance.timing === undefined ? _fallbackHitStartTime : performance.timing.navigationStart);
  51. const _HITLoadTime = _scriptStartTime - _HITStartTime;
  52.  
  53. let _HITLoadTimeAccuracy;
  54. if (performance.timing === undefined) {
  55. if (unsafeWindow.pageTimes === undefined) {
  56. _HITLoadTimeAccuracy = "low";
  57. }
  58. else {
  59. _HITLoadTimeAccuracy = "medium";
  60. }
  61. }
  62. else {
  63. _HITLoadTimeAccuracy = "high";
  64. }
  65.  
  66. let _allContentLoadedTime;
  67. let _allContentLoadedCallback = () => { };
  68. document.addEventListener('readystatechange', function (event) {
  69. if (document.readyState === "complete") {
  70. _allContentLoadedTime = performance.now();
  71. _allContentLoadedCallback();
  72. }
  73. });
  74.  
  75. var _secondsVisible = 0;
  76. var _pauseOnInvisible = true;
  77.  
  78. function getHITStartTime() {
  79. return _HITStartTime;
  80. }
  81.  
  82. function getHITLoadTime() {
  83. return _HITLoadTime;
  84. }
  85.  
  86. function getAllContentLoadedTime() {
  87. return _allContentLoadedTime;
  88. }
  89.  
  90. function setAllContentLoadedCallback(newFn) {
  91. _allContentLoadedCallback = newFn;
  92. }
  93.  
  94. function getHITLoadTimeAccuracy() {
  95. return _HITLoadTimeAccuracy;
  96. }
  97.  
  98. function getSecondsSinceStartTime() {
  99. return (new Date().getTime() - _HITStartTime) / 1000;
  100. }
  101.  
  102. function isWindowTopFrame() {
  103. return (window === window.top);
  104. }
  105.  
  106. function updateSecondsVisible() {
  107. if (!document.hidden && document.hasFocus()) { incrementSecondsVisible(); }
  108. }
  109.  
  110. function incrementSecondsVisible() {
  111. _secondsVisible += (UPDATE_RATE_IN_MILLISECONDS / ONE_SECOND_IN_MS);
  112. }
  113.  
  114. function getSecondsVisible() {
  115. return _secondsVisible;
  116. }
  117.  
  118. function hourlyWageDollars(secondsPassed, rewardCents) {
  119. return (rewardCents / secondsPassed) * 36;
  120. }
  121.  
  122. function hmsToSeconds(hours, minutes, seconds) {
  123. return hoursToSeconds(hours) + minutesToSeconds(minutes) + seconds;
  124. }
  125.  
  126. function minutesToSeconds(minutes) {
  127. return (minutes * 60);
  128. }
  129.  
  130. function toMMSSString(secondsRemaining) {
  131. let minutesRemaining = Math.floor(secondsRemaining / 60);
  132. let remainderSeconds = secondsRemaining - (minutesRemaining * 60);
  133. const localeOptions = { useGrouping: false, minimumIntegerDigits: 2, maximumFractionDigits: 0 };
  134. return minutesRemaining.toLocaleString(undefined, localeOptions) + ":" + remainderSeconds.toLocaleString(undefined, localeOptions);
  135. }
  136.  
  137. function daysToSeconds(days) {
  138. return (days * 86400);
  139. }
  140.  
  141. function hoursToSeconds(hours) {
  142. return (hours * 3600);
  143. }
  144.  
  145. function secondsRemainingInHHMMSSCountdown(hhmmssString) {
  146. let values = hhmmssString.split(":");
  147. return hmsToSeconds(Number(values[0]), Number(values[1]), Number(values[2]));
  148. }
  149.  
  150. function secondsRemainingInMMSSCountdown(mmssString) {
  151. let values = mmssString.split(":");
  152. let minutes = Number(values[0]);
  153. let seconds = Number(values[1]);
  154. return minutesToSeconds(minutes) + seconds;
  155. }
  156.  
  157. function secondsRemainingInMMSSCountup(mmssString, totalSeconds) {
  158. let countupValues = mmssString.split(":");
  159. let countupMinutes = Number(countupValues[0]);
  160. let countupSeconds = Number(countupValues[1]);
  161. let totalSecondsCounted = (countupMinutes * 60) + countupSeconds;
  162. return totalSeconds - totalSecondsCounted;
  163. }
  164.  
  165. function secondsRemainingInHHMMSSCountup(hhmmssString, totalSeconds) {
  166. let values = hhmmssString.split(":");
  167. return totalSeconds - hmsToSeconds(Number(values[0]), Number(values[1]), Number(values[2]));
  168. }
  169.  
  170. function secondsGoalForTargetWage(desiredCentsPerHour, jobCents) {
  171. let desiredCentsPerSecond = desiredCentsPerHour / 3600;
  172. return jobCents / desiredCentsPerSecond;
  173. }
  174.  
  175. function secondsPassed(totalSeconds, secondsRemaining) {
  176. return totalSeconds - secondsRemaining;
  177. }
  178.  
  179. function dollarsToCents(dollars) {
  180. return dollars * 100;
  181. }
  182.  
  183. function askWorkerFrameWhetherSurvey() {
  184. document.querySelector("iframe").contentWindow.postMessage({ msg: "Are you a survey HIT?" }, "*");
  185. }
  186.  
  187. function disablePauseOnInvisible() {
  188. _pauseOnInvisible = false;
  189. }
  190.  
  191. function isPausingOnInvisible() {
  192. return _pauseOnInvisible;
  193. }
  194.  
  195. function highlightWageRange(cssSelectorString) {
  196. document.querySelector(cssSelectorString).style = "font-weight: bold; text-decoration: underline;";
  197. }
  198.  
  199. function unhighlightWageRange(cssSelectorString) {
  200. document.querySelector(cssSelectorString).style = "font-weight: normal; text-decoration: none;";
  201. }
  202.  
  203. return {
  204. getSecondsSinceStartTime,
  205. getHITStartTime,
  206. isWindowTopFrame,
  207. updateSecondsVisible,
  208. getSecondsVisible,
  209. hourlyWageDollars,
  210. minutesToSeconds,
  211. toMMSSString,
  212. secondsRemainingInMMSSCountdown,
  213. secondsRemainingInMMSSCountup,
  214. secondsPassed,
  215. dollarsToCents,
  216. askWorkerFrameWhetherSurvey,
  217. disablePauseOnInvisible,
  218. isPausingOnInvisible,
  219. hmsToSeconds,
  220. secondsRemainingInHHMMSSCountdown,
  221. secondsRemainingInHHMMSSCountup,
  222. daysToSeconds,
  223. secondsGoalForTargetWage,
  224. highlightWageRange,
  225. unhighlightWageRange,
  226. getHITLoadTime,
  227. getHITLoadTimeAccuracy,
  228. getAllContentLoadedTime,
  229. setAllContentLoadedCallback
  230. };
  231.  
  232. })();
  233.  
  234. function updateActiveHITTabTimer(requesterName, secondsRemaining, rewardInCents) {
  235. if (secondsRemaining <= 0) {
  236. document.title = `EXPIRED - ${requesterName}`; return;
  237. }
  238.  
  239. Utility.updateSecondsVisible();
  240.  
  241. let newTitle = `${Utility.toMMSSString(secondsRemaining)} `;
  242.  
  243. let secondsPassed;
  244. if (Utility.isPausingOnInvisible() && (!ALWAYS_TICK_WAGE_TIMER)) {
  245. secondsPassed = Utility.getSecondsVisible();
  246. }
  247. else {
  248. secondsPassed = Utility.getSecondsSinceStartTime();
  249. }
  250.  
  251. document.querySelector("#timeWorkedSpan").innerText = Utility.toMMSSString(secondsPassed);
  252.  
  253. let hourlyWageDollars = Utility.hourlyWageDollars(secondsPassed, rewardInCents + (document.querySelector("#bonusWageCents").value !== "" ? Utility.dollarsToCents(document.querySelector("#bonusWageCents").value) : 0));
  254.  
  255. if (hourlyWageDollars > LOVE_WAGE_LOWER_LIMIT) {
  256. newTitle += LOVE_WAGE_EMOJI;
  257. if (SHOW_BIG_EMOJI) { document.querySelector("#pageEmoji").innerText = LOVE_WAGE_EMOJI; }
  258. Utility.highlightWageRange("#loveWageSpan");
  259. Utility.unhighlightWageRange("#happyWageSpan");
  260. Utility.unhighlightWageRange("#okWageSpan");
  261. Utility.unhighlightWageRange("#sadWageSpan");
  262. Utility.unhighlightWageRange("#disgustedWageSpan");
  263. }
  264. else if (hourlyWageDollars > HAPPY_WAGE_LOWER_LIMIT) {
  265. newTitle += HAPPY_WAGE_EMOJI;
  266. if (SHOW_BIG_EMOJI) { document.querySelector("#pageEmoji").innerText = HAPPY_WAGE_EMOJI; }
  267. Utility.unhighlightWageRange("#loveWageSpan");
  268. Utility.highlightWageRange("#happyWageSpan");
  269. Utility.unhighlightWageRange("#okWageSpan");
  270. Utility.unhighlightWageRange("#sadWageSpan");
  271. Utility.unhighlightWageRange("#disgustedWageSpan");
  272. }
  273. else if (hourlyWageDollars > OK_WAGE_LOWER_LIMIT) {
  274. newTitle += OK_WAGE_EMOJI;
  275. if (SHOW_BIG_EMOJI) { document.querySelector("#pageEmoji").innerText = OK_WAGE_EMOJI; }
  276. Utility.unhighlightWageRange("#loveWageSpan");
  277. Utility.unhighlightWageRange("#happyWageSpan");
  278. Utility.highlightWageRange("#okWageSpan");
  279. Utility.unhighlightWageRange("#sadWageSpan");
  280. Utility.unhighlightWageRange("#disgustedWageSpan");
  281. }
  282. else if (hourlyWageDollars > SAD_WAGE_LOWER_LIMIT) {
  283. newTitle += SAD_WAGE_EMOJI;
  284. if (SHOW_BIG_EMOJI) { document.querySelector("#pageEmoji").innerText = SAD_WAGE_EMOJI; }
  285. Utility.unhighlightWageRange("#loveWageSpan");
  286. Utility.unhighlightWageRange("#happyWageSpan");
  287. Utility.unhighlightWageRange("#okWageSpan");
  288. Utility.highlightWageRange("#sadWageSpan");
  289. Utility.unhighlightWageRange("#disgustedWageSpan");
  290. }
  291. else if (hourlyWageDollars > DISGUSTED_WAGE_LOWER_LIMIT) {
  292. newTitle += DISGUSTED_WAGE_EMOJI;
  293. if (SHOW_BIG_EMOJI) { document.querySelector("#pageEmoji").innerText = DISGUSTED_WAGE_EMOJI; }
  294. Utility.unhighlightWageRange("#loveWageSpan");
  295. Utility.unhighlightWageRange("#happyWageSpan");
  296. Utility.unhighlightWageRange("#okWageSpan");
  297. Utility.unhighlightWageRange("#sadWageSpan");
  298. Utility.highlightWageRange("#disgustedWageSpan");
  299. }
  300.  
  301. let pageLoadSpan = document.querySelector("#timeLostToPageLoad");
  302. if (pageLoadSpan.innerText.trim() === "") {
  303. pageLoadSpan.innerText = Utility.getHITLoadTime().toFixed(0);
  304. }
  305.  
  306. let fullPageLoadSpan = document.querySelector("#timeLostToFullPageLoad");
  307. if (fullPageLoadSpan.innerText.trim() === "") {
  308. if (Utility.getAllContentLoadedTime()) {
  309. fullPageLoadSpan.innerText = Utility.getAllContentLoadedTime().toFixed(0);
  310. }
  311. }
  312.  
  313. newTitle = newTitle + ` \$${hourlyWageDollars.toFixed(2)}/hr.`;
  314.  
  315. newTitle += ` ${requesterName}`;
  316.  
  317. if (!ALWAYS_TICK_WAGE_TIMER && (!(document.visible && document.hasFocus()))) {
  318. newTitle += `(\$ timer paused)`;
  319. }
  320.  
  321. document.title = newTitle;
  322. }
  323.  
  324. function swapToExpanderBar() {
  325. document.querySelector("#titleBarTimerBar").style.left = "-100%";
  326. document.querySelector("#expanderBar").style.left = "50%";
  327. GM_setValue("timerBarVisible", false);
  328. }
  329.  
  330. function swapToTimerBar() {
  331. document.querySelector("#titleBarTimerBar").style.left = "50%";
  332. document.querySelector("#expanderBar").style.left = "-100%";
  333. GM_setValue("timerBarVisible", true);
  334. }
  335.  
  336. function restoreTimerBarCollapseState() {
  337. let isTimerBarVisible = GM_getValue("timerBarVisible");
  338.  
  339. if (isTimerBarVisible !== undefined) {
  340. if (!isTimerBarVisible) {
  341. swapToExpanderBar();
  342. }
  343. }
  344. else {
  345. }
  346. }
  347.  
  348. function workerSiteMain() {
  349. if (Utility.isWindowTopFrame()) {
  350.  
  351. const divID = document.querySelector(`span[data-react-class*="require('reactComponents/common/ShowModal')"]`);
  352. const reactProps = divID.getAttribute("data-react-props");
  353. const json = JSON.parse(reactProps);
  354. const requesterName = json.modalOptions.requesterName;
  355.  
  356. const notAcceptedSpan = document.querySelector(`span[data-react-props*='"text":"accept"']`);
  357.  
  358. if (notAcceptedSpan) {
  359. document.title = `NOT ACCEPTED - ${requesterName}`;
  360. }
  361. else {
  362. const countdownTimer = document.querySelector("span[data-react-class*='reactComponents/common/CompletionTimer'] span:last-of-type");
  363. const rewardInDollars = json.modalOptions.monetaryReward.amountInDollars;
  364. const rewardInCents = Utility.dollarsToCents(rewardInDollars);
  365.  
  366. const timeGivenMinutesSplit = (countdownTimer.innerText.trim().match(/(\d+) Min/) || [0, 0])[1];
  367. const timeGivenSecondsSplit = 0; // TODO: Change this when I figure out what a HIT with <1 min reads out as.
  368. const maximumTimeGivenInSeconds = Utility.hmsToSeconds(0, timeGivenMinutesSplit, timeGivenSecondsSplit);
  369.  
  370. const secondsForLoveWage = Utility.secondsGoalForTargetWage(Utility.dollarsToCents(LOVE_WAGE_LOWER_LIMIT), rewardInCents);
  371. const secondsForHappyWage = Utility.secondsGoalForTargetWage(Utility.dollarsToCents(HAPPY_WAGE_LOWER_LIMIT), rewardInCents);
  372. const secondsForOKWage = Utility.secondsGoalForTargetWage(Utility.dollarsToCents(OK_WAGE_LOWER_LIMIT), rewardInCents);
  373. const secondsForSadWage = Utility.secondsGoalForTargetWage(Utility.dollarsToCents(SAD_WAGE_LOWER_LIMIT), rewardInCents);
  374. const secondsForDisgustedWage = Utility.secondsGoalForTargetWage(Utility.dollarsToCents(DISGUSTED_WAGE_LOWER_LIMIT), rewardInCents);
  375.  
  376. document.body.insertAdjacentHTML('afterend',
  377. `<div id='titleBarTimerBar' style="position: fixed !important; pointer-events: none !important; background: white !important; opacity: 0.95 !important; width: 100% !important; left: 50% !important; margin-left: -50% !important; top: 0px !important; z-index: ${Number.MAX_SAFE_INTEGER} !important; text-align: center !important;">` +
  378. `<span id="loveWageSpan">${LOVE_WAGE_EMOJI}: (<= ${Utility.toMMSSString(secondsForLoveWage)})</span> ` +
  379. `<span id="happyWageSpan">${HAPPY_WAGE_EMOJI}: (${Utility.toMMSSString(secondsForLoveWage)} - ${Utility.toMMSSString(secondsForHappyWage)})</span> ` +
  380. `<span id="okWageSpan">${OK_WAGE_EMOJI}: (${Utility.toMMSSString(secondsForHappyWage)} - ${Utility.toMMSSString(secondsForOKWage)})</span> ` +
  381. `<span id="sadWageSpan">${SAD_WAGE_EMOJI}: (${Utility.toMMSSString(secondsForOKWage)} - ${Utility.toMMSSString(secondsForSadWage)})</span> ` +
  382. `<span id="disgustedWageSpan">${DISGUSTED_WAGE_EMOJI}: (>= ${Utility.toMMSSString(secondsForSadWage)})</span>` +
  383. `<div style="text-align: center !important; margin-left: auto !important; margin-right: auto !important; font-style: italic !important;">Time Worked: <span id="timeWorkedSpan">00:00</span> <input type="text" id="bonusWageCents" style="pointer-events: auto !important;" placeholder="Bonus $" size="5"> <div>Pageload: <span id="timeLostToPageLoad"></span>ms Complete: <span id="timeLostToFullPageLoad"></span>ms</div></div>` +
  384. `<button id='hideBarButton' style="pointer-events: auto !important;">Hide</button>` +
  385. '</div>'
  386. );
  387.  
  388. document.body.insertAdjacentHTML('afterend',
  389. `<div id='expanderBar' style="position: fixed; pointer-events: none; background: white; opacity: 0.95; width: 100%; left: -100%; margin-left: -50%; top: 0px; z-index: ${Number.MAX_SAFE_INTEGER}; text-align: center;">` +
  390. `<button id='showBarButton' style="pointer-events: auto;">Show $/hr. Details</button>` +
  391. '</div>'
  392. );
  393.  
  394. document.querySelector('#hideBarButton').addEventListener('click', function hideBar() {
  395. swapToExpanderBar();
  396. });
  397.  
  398. document.querySelector('#showBarButton').addEventListener('click', function hideBar() {
  399. swapToTimerBar();
  400. });
  401.  
  402. restoreTimerBarCollapseState();
  403.  
  404. if (SHOW_BIG_EMOJI) {
  405. document.body.insertAdjacentHTML('afterend',
  406. `<div id="pageEmoji" draggable="false" style="position: fixed; opacity: ${BIG_EMOJI_OPACITY_ZERO_TO_ONE}; font-size: ${BIG_EMOJI_SIZE}; pointer-events: none; user-drag: none; user-select: none; width: 100%; left: 0px; height: 50%; top: 50%; z-index: ${Number.MAX_SAFE_INTEGER}; text-align: center;">` +
  407. `` +
  408. `</div>`);
  409. }
  410.  
  411.  
  412. setInterval(function workerSiteUpdate() {
  413. let countdownTimerText = countdownTimer.innerText.match(/\d+:\d+\b/)[0];
  414. // let secondsRemaining = Utility.secondsRemainingInHHMMSSCountdown(countdownTimerText);
  415. let secondsRemaining = Utility.secondsRemainingInMMSSCountup(countdownTimerText, maximumTimeGivenInSeconds);
  416. updateActiveHITTabTimer(requesterName, secondsRemaining, rewardInCents);
  417. }, UPDATE_RATE_IN_MILLISECONDS);
  418. } // End of HIT accepted check.
  419.  
  420. } // End of isWindowTopFrame().
  421. }
  422.  
  423. function originalSiteMain() {
  424. if (Utility.isWindowTopFrame()) {
  425. const countdownTimer = document.querySelector("#theTime");
  426.  
  427. const acceptHITInput = document.querySelector("input[name='/accept']");
  428.  
  429. const capsuleTexts = document.querySelectorAll(".capsule_field_text");
  430. const requesterName = capsuleTexts[0].innerText.trim();
  431. const rewardInDollars = capsuleTexts[1].innerText.trim().match(/\$([0-9.]+)/)[1];
  432. const rewardInCents = Utility.dollarsToCents(rewardInDollars);
  433. const timeGivenDaysSplit = (capsuleTexts[3].innerText.trim().match(/(\d+) days/) || [0, 0])[1];
  434. const timeGivenHoursSplit = (capsuleTexts[3].innerText.trim().match(/(\d+) hours/) || [0, 0])[1];
  435. const timeGivenMinutesSplit = (capsuleTexts[3].innerText.trim().match(/(\d+) minutes/) || [0, 0])[1];
  436. const timeGivenSecondsSplit = (capsuleTexts[3].innerText.trim().match(/(\d+) seconds/) || [0, 0])[1];
  437. const maximumTimeGivenInSeconds = Utility.daysToSeconds(timeGivenDaysSplit) + Utility.hmsToSeconds(timeGivenHoursSplit, timeGivenMinutesSplit, timeGivenSecondsSplit);
  438.  
  439. const secondsForLoveWage = Utility.secondsGoalForTargetWage(Utility.dollarsToCents(LOVE_WAGE_LOWER_LIMIT), rewardInCents);
  440. const secondsForHappyWage = Utility.secondsGoalForTargetWage(Utility.dollarsToCents(HAPPY_WAGE_LOWER_LIMIT), rewardInCents);
  441. const secondsForOKWage = Utility.secondsGoalForTargetWage(Utility.dollarsToCents(OK_WAGE_LOWER_LIMIT), rewardInCents);
  442. const secondsForSadWage = Utility.secondsGoalForTargetWage(Utility.dollarsToCents(SAD_WAGE_LOWER_LIMIT), rewardInCents);
  443. const secondsForDisgustedWage = Utility.secondsGoalForTargetWage(Utility.dollarsToCents(DISGUSTED_WAGE_LOWER_LIMIT), rewardInCents);
  444.  
  445. console.log("You lost", Utility.getHITLoadTime() / 1000, "seconds to HIT loading, with accuracy:", Utility.getHITLoadTimeAccuracy());
  446. console.log("For a theoretical 1-second submission, this means that loading time cut your wage from", `\$${Utility.hourlyWageDollars(1, rewardInCents).toFixed(2)}/hr.`, "to", `\$${Utility.hourlyWageDollars(1 + (Utility.getHITLoadTime() / 1000), rewardInCents).toFixed(2)}/hr.`);
  447. console.log("For a theoretical 5-second submission, this means that loading time cut your wage from", `\$${Utility.hourlyWageDollars(5, rewardInCents).toFixed(2)}/hr.`, "to", `\$${Utility.hourlyWageDollars(5 + (Utility.getHITLoadTime() / 1000), rewardInCents).toFixed(2)}/hr.`);
  448.  
  449. Utility.setAllContentLoadedCallback(() => {
  450. console.log("You lost", Utility.getAllContentLoadedTime() / 1000, "seconds to HIT the full page load, with accuracy:", Utility.getHITLoadTimeAccuracy());
  451. console.log("If you waited for the whole page to load, this means that the most you could earn with an instant submission would be", `\$${Utility.hourlyWageDollars(Utility.getAllContentLoadedTime() / 1000, rewardInCents).toFixed(2)}/hr.`);
  452. });
  453.  
  454. document.body.insertAdjacentHTML('afterend',
  455. `<div id="titleBarTimerBar" style="position: fixed; pointer-events: none; background: white; opacity: 0.95; width: 100%; left: 50%; margin-left: -50%; top: 0px; z-index: ${Number.MAX_SAFE_INTEGER}; text-align: center;">` +
  456. `<span id="loveWageSpan">${LOVE_WAGE_EMOJI}: (<= ${Utility.toMMSSString(secondsForLoveWage)})</span> ` +
  457. `<span id="happyWageSpan">${HAPPY_WAGE_EMOJI}: (${Utility.toMMSSString(secondsForLoveWage)} - ${Utility.toMMSSString(secondsForHappyWage)})</span> ` +
  458. `<span id="okWageSpan">${OK_WAGE_EMOJI}: (${Utility.toMMSSString(secondsForHappyWage)} - ${Utility.toMMSSString(secondsForOKWage)})</span> ` +
  459. `<span id="sadWageSpan">${SAD_WAGE_EMOJI}: (${Utility.toMMSSString(secondsForOKWage)} - ${Utility.toMMSSString(secondsForSadWage)})</span> ` +
  460. `<span id="disgustedWageSpan">${DISGUSTED_WAGE_EMOJI}: (>= ${Utility.toMMSSString(secondsForSadWage)})</span>` +
  461. `<div style="text-align: center; margin-left: auto; margin-right: auto; font-style: italic;">Time Worked: <span id="timeWorkedSpan">00:00</span> <input type="text" style="pointer-events: auto;" id="bonusWageCents" placeholder="Bonus $" size="5"><div>Pageload: <span id="timeLostToPageLoad"></span>ms Complete: <span id="timeLostToFullPageLoad"></span>ms</div></div>` +
  462. `<button id='hideBarButton' style="pointer-events: auto;">Hide</button>` +
  463. '</div>'
  464. );
  465.  
  466. document.body.insertAdjacentHTML('afterend',
  467. `<div id='expanderBar' style="position: fixed; pointer-events: none; background: white; opacity: 0.95; width: 100%; left: -100%; margin-left: -50%; top: 0px; z-index: ${Number.MAX_SAFE_INTEGER}; text-align: center;">` +
  468. `<button id='showBarButton' style="pointer-events: auto;">Show $/hr. Details</button>` +
  469. '</div>'
  470. );
  471.  
  472. document.querySelector('#hideBarButton').addEventListener('click', function hideBar() {
  473. swapToExpanderBar();
  474. });
  475.  
  476. document.querySelector('#showBarButton').addEventListener('click', function hideBar() {
  477. swapToTimerBar();
  478. });
  479.  
  480. restoreTimerBarCollapseState();
  481.  
  482. if (SHOW_BIG_EMOJI) {
  483. document.body.insertAdjacentHTML('afterend',
  484. `<div id="pageEmoji" draggable="false" style="position: fixed !important; opacity: ${BIG_EMOJI_OPACITY_ZERO_TO_ONE} !important; font-size: ${BIG_EMOJI_SIZE} !important; pointer-events: none !important; user-drag: none !important; user-select: none !important; width: 100% !important; left: 0px !important; height: 50% !important; top: 50% !important; z-index: ${Number.MAX_SAFE_INTEGER} !important; text-align: center !important;">` +
  485. `?` +
  486. `</div>`);
  487. }
  488.  
  489. if (acceptHITInput) {
  490. document.title = `NOT ACCEPTED - ${requesterName}`;
  491. }
  492. else {
  493. setInterval(function mainSiteUpdate() {
  494. let countupTimerText = countdownTimer.innerText.trim();
  495. let secondsRemaining = Utility.secondsRemainingInHHMMSSCountup(countupTimerText, maximumTimeGivenInSeconds);
  496. updateActiveHITTabTimer(requesterName, secondsRemaining, rewardInCents);
  497. }, UPDATE_RATE_IN_MILLISECONDS);
  498. } // End of HIT accepted check.
  499. } // End of isWindowTopFrame().
  500. }
  501.  
  502. function isWorkerSite() {
  503. return window.location.href.toLowerCase().includes("worker.mturk.com");
  504. }
  505.  
  506. (function main() {
  507. window.addEventListener("message", function handleMessage(event) {
  508. if (event.data.msg === "I'm ready for you to ask me questions.") {
  509. console.log("PARENT FRAME: Kid said it's ready for questions.");
  510. Utility.askWorkerFrameWhetherSurvey();
  511. console.log("PARENT FRAME: I asked whether the kid was a survey.");
  512. }
  513. else if (event.data.msg === "I am a survey HIT.") {
  514. console.log("PARENT FRAME: Kid said he was a survey.");
  515. Utility.disablePauseOnInvisible();
  516. console.log("PARENT FRAME: I disabled pausing on invisible, since the kid said he was a survey.");
  517. }
  518.  
  519. if (event.data.msg === "Submit relay.") {
  520. console.log("PARENT FRAME: I detected a form submit.");
  521. alert("Detected a form submit.");
  522. }
  523. });
  524.  
  525. if (isWorkerSite()) {
  526. workerSiteMain();
  527. }
  528. else {
  529. originalSiteMain();
  530. }
  531. })();