您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Tracks a best-estimate hourly wage on active HITs being worked.
当前为
// ==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/preview?* // @include https://www.mturk.com/mturk/dashboard* // @include https://www.mturk.com/mturk/submit* // @version 0.5b // @grant GM_setValue // @grant GM_getValue // @require http://code.jquery.com/jquery-2.1.1.js // @author DeliriumTremens 2014 // ==/UserScript== // // 2014-07-10 0.1b Beginning development. Creating timer and tab tracker, as well as initial IndexedDB for storage of data. // // // 2014-07-15 0.4.2b Continued development. Not ready for live usage yet. Expanded math for wage calculation, added safeguards // to prevent adding expired or missing HITs. Added formatting for dashboard. Set up groundwork for additional // math to be included in the next update. Added a buttload of comments to the code. // // // 2014-07-16 0.4.3b More work! Added function to remove a HIT that expires from the database under the assumption that expired hits // were forgotten about rather than actively worked on until expiration. Updated wage calculation because I had a major // brainfart about DIVIDING DOLLARS BY HOURS -- don't ask... Yet more safeguards and checks put in place... // // ------------------------------------------------------------------------------------------------------------------------------------------------ // 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.webkitIndexedDB || window.mozIndexedDB; var WageStorage = {}; window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.mozIDBTransaction; window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.mozIDBKeyRange; 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("unworked", "unworked", {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"],'readwrite'); var store = trans.objectStore("Wage"); // ***** PLACEHOLDER FOR UPDATING A HIT INSTEAD OF ADDING NEW ***** var request; request = store.put({ hitId: hitId, date: date, reqName: reqName, reqId: reqId[1], reward: reward, start: wageStart, stop: wageEnd, unworked: wageExtra }); // Insert fresh hit into database request.onsuccess = function (e) { console.log(e.target.result); console.log("Inserted 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 () { console.log("Retrieving wage for dashboard..."); 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; tmp_results[hit.hitId][3] = hit.unworked; } cursor.continue(); } else { for (var key in tmp_results) { results.push(tmp_results[key]); } console.log("Calculating wage..."); var wage = calculateWage(results); // Calls function to calculate wage console.log("Wage calculated, showing results..."); addTableElement(wage); // Calls function to add wage to dashboard after all data has been pulled } } } } // Function to check if HIT has already been started, if so then store the start and end time so it can be updated and have unworked time added to database WageStorage.indexedDB.getHit = function () { console.log("Checking if HIT exists..."); 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 = false; console.log("New HIT..."); } else { inProgress = true; wageExtra = (event.target.result.unworked + (wageStart - event.target.result.stop)); wageStart = event.target.result.start; console.log("HIT exists, continuing..."); } } } } // Function to remove bad or unused HIT from database WageStorage.indexedDB.delHit = function(remHit) { console.log("Expired HIT... Removing from DB..."); var request = indexedDB.open("WageDB", v); var remHit = remHit; request.onsuccess = function(e) { WageStorage.indexedDB.db = e.target.result; var db = WageStorage.indexedDB.db var transaction = db.transaction('Wage','readwrite'); var store = transaction.objectStore('Wage'); var hitDel = store.delete(remHit); hitDel.onsuccess = function (event) { console.log("HIT successfully deleted from database..."); } } } // 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 wageNix = false; // Placeholder for determining if HIT was expired or unavailable or captcha'd var expiredHit = false; // Placeholder for expired 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 = []; calculateWage = function (wage) { // Function that does the meat of the wage calculation. Meaty. var currStart = 0; var currEnd = 0; var currWages = []; var currHit = 0; var batch = []; var batchWage = 0; var currWage = 0; for (var i = 0; i < wage.length; i++) { if ( currStart < wage[i][1] && wage[i][1] < currEnd ) { // If current HIT started before previous HIT was submitted, start batch processing //console.log(" Batch starting..."); currEnd = wage[i][2]; currHit = (wage[i][0])/((((currEnd - currStart) - wage[i][3] ) / 1000) / 3600); batch.push(currHit); } else if ( (currStart >= wage[i][1] && wage[i][1] >= currEnd) || (currStart < wage[i][1] && wage[i][1] > currEnd)) { // Before starting a hit outside of a confirmed batch, add contents of batch to wages //console.log(" single hit starting..."); if ( batch.length >= 1 ) { for ( var i = 0; i < batch.length; i++) { batchWage += batch[i]; } batchWage = wageRound((batchWage/batch.length),2); currWages.push(batchWage); currStart = wage[i][1]; currEnd = wage[i][2]; batch.push((wage[i][0])/((((currEnd - currStart) - wage[i][3]) / 1000) / 3600)); } else { currStart = wage[i][1]; currEnd = wage[i][2]; batch.push((wage[i][0])/((((currEnd - currStart) - wage[i][3]) / 1000) / 3600)); } } } if ( batch.length >= 1 ) { for (var i = 0; i < batch.length; i++ ) { batchWage += batch[i]; } batchWage = wageRound((batchWage/batch.length),2); currWages.push(batchWage); batchWage = 0; batch = []; } for (var i = 0; i < currWages.length; i++) { // Add up all of the calculated wages in the current wages array currWage += currWages[i]; } // Find the rounded average of all wages in the array currWage = wageRound((currWage/currWages.length),2); return currWage; } addTableElement = function (wage) { // Function to add the table cells to dashboard and displaying the wage var belowThisTD = (($.inArray('<a href=\"https://www.mturk.com/mturk/dashboard\">Today\'s Projected Earnings</a>',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('<a href=\"https://www.mturk.com/mturk/dashboard\">Today\'s Projected Earnings</a>',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 = "$" + wage + "/hr"; row.appendChild(cellRight); thisTd.parentNode.parentNode.insertBefore(row,thisTd.parentNode.nextSibling); } } } // Function for legitimate rounding because javascript sucks at floating point function wageRound(num,decimals) { return Math.round(num * Math.pow(10, decimals)) / Math.pow(10, decimals); } // Timestamp when the HIT is accepted $(window).ready( function() { if (document.URL === "https://www.mturk.com/mturk/dashboard" || /preview\?/i.test(document.URL)) { console.log("Nothing to do here..."); } // 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. console.log("Starting HIT"); } }); $(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); } if (/preview\?/i.test(document.URL)){ if (/expired/i.test($('#alertboxHeader').text())) { // Remove previous HIT from database -- this one expired. console.log(wageNix + " checker for expired hit"); WageStorage.indexedDB.delHit(GM_getValue("hitId")); } } else if (/accept\?/i.test(document.URL) || /previewandaccept\?/i.test(document.URL) || /continue\?/i.test(document.URL)) { wageNix = (/could not/i.test($('#alertboxHeader').text())); // Set wageNix true if hit is gone wageNix = (/expired/i.test($('#alertboxHeader').text())); // Set wageNix true if hit is expired wageNix = (/There are no more available HITs in this group/i.test($('#alertboxMessage').text())); console.log(wageNix + " checker for missing hit"); WageStorage.indexedDB.getHit(); // As soon as the apge is fully loaded, call function to check if HIT is already in DB } else if (/dashboard/i.test(document.URL)) { console.log("On the Dashboard, get wage..."); WageStorage.indexedDB.getWage(); } }); // 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(); console.log("Wage lost due to return..."); } else if (wageStart === null || wageNix === true || /preview\?/i.test(document.URL) || /There are no more available HITs in this group/i.test($('#alertboxMessage').text())) { // ***** THIS HIT IS GONE OR PREVIEWED ***** console.log("Either HIT expired or we were on a preview page"); } else { // ***** DO STUFF WHEN SUBMITTED ***** console.log("This should fire no matter what, unless returned..."); wageEnd = new Date().getTime(); GM_setValue("hitId", hitId); WageStorage.indexedDB.addhit(); } });