// ==UserScript== // @name MTurk Wage Reporter // @namespace localhost // @description Tracks a best-estimate hourly wage on active HITs being worked. // @include https://www.mturk.com/mturk/accept?* // @include https://www.mturk.com/mturk/continue?* // @include https://www.mturk.com/mturk/previewandaccept?* // @include https://www.mturk.com/mturk/dashboard* // @include https://www.mturk.com/mturk/submit* // @version 0.4.1b // @grant GM_setValue // @grant GM_getValue // @require http://code.jquery.com/jquery-2.1.1.js // @downloadURL // @updateURL // @author DeliriumTremens 2014 // @downloadURL none // ==/UserScript== // // 2014-07-10 0.1b Beginning development. Creating timer and tab tracker, as well as initial IndexedDB for storage of data. // // ------------------------------------------------------------------------------------------------------------------------------------------------ // First, create indexedDB variables. // This sets up the various indexedDB functions and processes for interacting with WageDB. // Parts borrowed from HITdb var indexedDB = window.indexedDB || window.webkidIndexedDB || window.mozIndexedDB; var WageStorage = {}; window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.mozIDBTransaction; window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.mozIDBKeyRange; WageStorage.IDBTransactionModes = { "READ_ONLY": "readonly", "READ_WRITE": "readwrite", "VERSION_CHANGE": "versionchange" }; var idbKeyRange = window.IDBKeyRange; WageStorage.indexedDB = {}; WageStorage.indexedDB.db = null; // Global catch for indexedDB errors WageStorage.indexedDB.onerror = function(e) { console.log(e); } // Check if database exists. If no database, create one. // Parts borrowed from HITdb. var inProgress = false; // Boolean for when a HIT is continued from queue var dbExists = true; // Boolean for when the database is already created var v = 5; // Version of the database WageStorage.indexedDB.create = function () { // Function that creates the database if it hasn't been created already. var request = indexedDB.open("WageDB", v); request.onupgradeneeded = function (e) { WageStorage.indexedDB.db = e.target.result; var db = WageStorage.indexedDB.db; var newDB = false; if(!db.objectStoreNames.contains("Wage")) { var store = db.createObjectStore("Wage", { keyPath: "hitId" }); // Primary key of the database (not an index) store.createIndex("date", "date", {unique: false}); // These are the other fields (or Indexes) of the database. store.createIndex("reqName", "reqName", {unique: false}); store.createIndex("reqId", "reqId", {unique: false}); store.createIndex("reward", "reward", {unique: false}); store.createIndex("start", "start", {unique: false}); store.createIndex("stop", "stop", {unique: false}); store.createIndex("queue", "queue", {unique: false}); newDB = true; } db.close(); } request.onsuccess = function(e) { WageStorage.indexedDB.db = e.target.result; var db = WageStorage.indexedDB.db; db.close(); } request.onerror = WageStorage.indexedDB.onerror; } // Function for adding HIT data into database. // Includes logic for updating HITs which were worked on in several sittings (or from queue) WageStorage.indexedDB.addhit = function () { var request = indexedDB.open("WageDB", v); request.onsuccess = function (e) { WageStorage.indexedDB.db = e.target.result; var db = WageStorage.indexedDB.db; var newDB = false; if(!db.objectStoreNames.contains("Wage")) { // Make sure database is there... This should never fire. db.close(); } else { var trans = db.transaction(["Wage"],WageStorage.IDBTransactionModes.READ_WRITE); var store = trans.objectStore("Wage"); var request; request = store.put({ hitId: hitId, date: date, reqName: reqName, reqId: reqId[1], reward: reward, start: wageStart, stop: wageEnd, queue: wageExtra }); // Insert fresh hit into database } db.close(); } request.onerror = WageStorage.indexedDB.onerror; } // Function for getting todays data. // Gets reward amount, start time, stop time, and queue time (unworked time) WageStorage.indexedDB.getWage = function () { var request = indexedDB.open("WageDB", v); request.onsuccess = function (e) { WageStorage.indexedDB.db = e.target.result; var db = WageStorage.indexedDB.db var transaction = db.transaction('Wage','readonly'); var store = transaction.objectStore('Wage'); var index = store.index('date'); var range = IDBKeyRange.only(date); var results = []; var tmp_results = {}; index.openCursor(range).onsuccess = function(event) { var cursor = event.target.result; if (cursor) { var hit = cursor.value; if (tmp_results[hit.hitId] === undefined) { tmp_results[hit.hitId] = []; tmp_results[hit.hitId][0] = hit.reward; tmp_results[hit.hitId][1] = hit.start; tmp_results[hit.hitId][2] = hit.stop; } cursor.continue(); } else { for (var key in tmp_results) { results.push(tmp_results[key]); } addTableElement(results); // Calls function to add wage to dashboard after all data has been pulled } } } } // Function to check if HIT has already been started WageStorage.indexedDB.getHit = function () { var request = indexedDB.open("WageDB", v); request.onsuccess = function (e) { WageStorage.indexedDB.db = e.target.result; var hitId2 = $('input[name="hitId"]').val(); var db = WageStorage.indexedDB.db var transaction = db.transaction('Wage','readonly'); var store = transaction.objectStore('Wage'); var hitCheck = store.get(hitId2); hitCheck.onsuccess = function (event) { if (event.target.result === undefined) { inProgress = true; } else { inProgress = false; } } } } // Script Variables var wageLost = false; // Boolean for a returned hit, and a wage that is abandoned. Will record the time with zero payment. var wageExtra = 0; // Placeholder for extra time that was not worked. var wageStart = null; // Placeholder for start time of HIT var wageEnd = null; // Placeholder for end time of HIT var reqId = document.URL.match(/requesterId=(.*?)&/i); // Parse requester ID out of URL if (reqId) { GM_setValue("reqId", reqId); // If no requester ID is available, it's because you accepted next hit in batch. Retrieves ID. } else { reqId = GM_getValue("reqId"); // Stores requester ID in a global script var to be pulled in case of batch work. } // HIT Data var hitId = $('input[name="hitId"]').val(); // Parses hit id from html var reward = parseFloat($('span[class="reward"]:eq(1)').text().replace('$','')); // Parses reward from html var reqName = $('input[name="prevRequester"]').val(); // Parses requester name from html var date = new Date(); // Get todays date date = date.toLocaleDateString().replace(/\//g,'-'); // Convert date to usable string // Create table element for showing hourly wage. Parts borrowed from Today's Projected Earnings script var allTds, thisTd; var allTdhtml = []; addTableElement = function (wage) { // Function to add the table cells to dashboard and displaying the wage var currStart = 0; var currEnd = 0; var currWages = []; var currWage = 0; for (var i = 0; i < wage.length; i++) { currStart = ((wage[i][1] > currStart) ? wage[i][1] : currStart); currEnd = ((wage[i][2] > currEnd) ? wage[i][2] : currEnd); currWages.push(((((currEnd - currStart) / 1000) / 3600) * 10000) * wage[i][0]); } for (var i = 0; i < currWages.length; i++) { currWage += currWages[i]; } currWage = parseFloat(currWage/currWages.length).toFixed(2); var belowThisTD = (($.inArray('Today\'s Projected Earnings',allTdhtml) > -1) ? /Today's Projected Earnings/ : /Total Earnings/ ); // If Projected Earnings script is installed, this will be sure to place is in the correct spot var rowColor = (($.inArray('Today\'s Projected Earnings',allTdhtml) > -1) ? "#f1f3eb" : "FFFFFF" ); // If Projected Earnings script is installed, this will ensure the correct color is used for the new row for (var i = 0; i < allTds.length; i++) { thisTd = allTds[i]; if ( thisTd.innerHTML.match(belowThisTD) && thisTd.className.match(/metrics\-table\-first\-value/) ) { var row = document.createElement('tr'); row.className = "even"; row.setAttribute("style",("background-color:" + rowColor)); var hourlyWageTitle = document.createElement('p'); hourlyWageTitle.innerHTML = "Today's Hourly Wage"; hourlyWageTitle.setAttribute("style",("background-color:" + rowColor)); var cellLeft = document.createElement('td'); cellLeft.className = "metrics-table-first-value"; cellLeft.appendChild(hourlyWageTitle); row.appendChild(cellLeft); var cellRight = document.createElement('td'); cellRight.innerHTML = "$" + currWage + "/hr"; row.appendChild(cellRight); thisTd.parentNode.parentNode.insertBefore(row,thisTd.parentNode.nextSibling); } } } // Timestamp when the HIT is accepted $(window).ready( function() { if (document.URL === "https://www.mturk.com/mturk/dashboard" ) {} // Don't record anything on dashboard, instead add appropriate element to tables else { wageStart = new Date().getTime(); // As soon as the window can run something, set the start time. } }); $(window).on('load', function() { allTds = document.getElementsByTagName('td'); // As soon as the page is fully loaded, grab all of the td elements to an array for (var i = 0; i < allTds.length; i++) { allTdhtml.push(allTds[i].innerHTML); } WageStorage.indexedDB.getWage(); // As soon as the page is fully loaded, call function to get todays data from DB WageStorage.indexedDB.getHit(); // As soon as the apge is fully loaded, call function to check if HIT is already in DB }); // Detect if 'Return HIT' button has been clicked to record timer as zero-earnings. // Timestamp when button was clicked to end time spent. $('a[href*="/mturk/return?"]').on('click', function() { wageLost = true; wageEnd = new Date().getTime(); }); $('img[name="/submit"]').on('click', function() { wageLost = false; }); // Create database if not created yet WageStorage.indexedDB.create(); // Detect the page unloading and react accordingly. // If return button was clicked, record zero wage earnings, otherwise record earnings. // Timestamp if submitted to end time spent. $(window).on('beforeunload', function() { if (wageLost) { // ***** DO STUFF WHEN RETURN CLICKED ***** reward = 0; WageStorage.indexedDB.addhit(); } else { // ***** DO STUFF WHEN SUBMITTED ***** console.log("This should fire no matter what, unless returned..."); wageEnd = new Date().getTime(); WageStorage.indexedDB.addhit(); } });