Mturk Hourly

Record time spent working on HITs.

目前為 2017-07-10 提交的版本,檢視 最新版本

// ==UserScript==
// @name        Mturk Hourly
// @author      @Kerek
// @description Record time spent working on HITs.
// @match       https://www.mturk.com/mturk/accept*
// @match       https://www.mturk.com/mturk/preview*
// @match       https://www.mturk.com/mturk/continue*
// @match       https://www.mturk.com/mturk/submit
// @match       https://www.mturk.com/mturk/return*
// @match       https://www.mturk.com/mturk/statusdetail*
// @match https://www.mturk.com/mturk/dashboard
// @require     http://code.jquery.com/jquery-latest.min.js
// @version     1.0
// @grant       none
// @namespace https://greasyfork.org/users/11205
// ==/UserScript==

if (window.location.href == "https://www.mturk.com/mturk/dashboard" && $("#total_earnings_amount").length){
    Todays_Projected_Earnings();
}
else{
    var hit_returned = false;
    if(typeof(Storage)!=="undefined")
{
    $('img[src="/images/return_hit.gif"]').parent().click(function(){
        hit_returned = true;
    });
    store_data('open');
    window.addEventListener('beforeunload', function(){store_data('close');});

    var $requesters = $('td[class="statusdetailRequesterColumnValue"]');
    if ($requesters.length > 0)
    {
        $requesters.each(function(){
            var hitId = $(this).find('a[href^="/mturk/contact?"]').attr('href').match(/[A-Z0-9]{30}/);
            var $feedback_value = $(this).parent().find('td[class="statusdetailStatusColumnValue"]').next();
            var feedback_str = create_feedback_str(hitId);
            var status_value = $feedback_value.html() + feedback_str;
            $feedback_value.html(status_value);
        });
    }
}
}



function Todays_Projected_Earnings(){
    var TPEhitLOG = {}; var TPEdetailsLOG = {}; var TPEhourlyLOG = {}; var pe = 0;

    var today = $("a[href^='/mturk/statusdetail?encodedDate']:contains(Today)").eq(0).prop("href");

    var $peTR = $('<div id="TPE_div" class="even" style="display:table-row">');
    var $peTD1 = $('<td class="metrics-table-first-value">');
    var $peTD2 = $('<td>');
    var $peA = $('<a href="javascript:void(0)">Today\'s Projected Earnings</a>');
    var $TPE_details = $('<span style="color: blue; font-size: 10px; cursor: pointer; float: right;">Details<img style="margin-left: 5px;" src="/media/more.gif" border="0/"></span>');
    var $peSPAN = $('<span class="reward">$0.00</span>');
    $("td.metrics-table-first-value:contains(Total Earnings)").parent().after($peTR);
    $peTR.append($peTD1.append($peA,$TPE_details),$peTD2.append($peSPAN));

    var $TPED_table = $('<table style="display: none;" width="760" align="center" cellspacing="0" cellpadding="0">');
    var $TPED_tboday = $('<tbody>');
    var $TPED_tr_1 = $('<tr id="TPE_tr"  height="25px"><td width="10" bgcolor="#7fb4cf" style="padding-left: 10px;"></td><td width="100%" bgcolor="#7fb4cf" class="white_text_14_bold">Today\'s Projected Earnings Details&nbsp;&nbsp;<a id="fourmEXPORT" href="javascript:void(0)" class="whatis" >(Forum Export)</a></td><td width="10" align="right" bgcolor="#7fb4cf"></td></tr>');
    var $TPED_tr_2 = $('<tr><td class="container-content" colspan="3"><table class="metrics-table" width="100%"><tbody><tr><td width="100%"><table class="metrics-table" width="100%"><tbody id="tbody2"></tbody></table></td></tr></tbody></table></td></tr>');
    var $TPED_tr_h = $('<tr class="metrics-table-header-row"><th class="metrics-table-first-header">Requester</th><th>Submitted</th><th>Projected</th><th>Hourly</th></tr>');

    $("#subtabs_and_searchbar").next().next().after($TPED_table);
    $TPED_table.append($TPED_tboday);
    $TPED_tboday.append($TPED_tr_1,$TPED_tr_2);
    $("#tbody2").append($TPED_tr_h);

    $("#fourmEXPORT").click(function(){
        var exportcode = "";
        var bonus = $("#bonus").text();
        if (bonus !== "$0.00"){
            var total = (Number(pe)+Number(bonus.replace(/[^0-9.]/g, ""))).toFixed(2);
            exportcode += "[b]Today's Projected Earnings: $"+Number(pe).toFixed(2)+" + Bonuses: "+bonus+" = $"+total+"[/b]\n";

        }
        else {
            exportcode += "[b]Today's Projected Earnings: $"+Number(pe).toFixed(2)+"[/b]\n";
        }
        exportcode += "[spoiler=Today's Projected Earnings Full Details][table][tr][th][b]Requester[/b][/th][th][b]Submitted[/b][/th][th][b]Projected[/b][/th][/tr]";

        var x_sorted = Object.keys(TPEdetailsLOG).sort(function(a,b){return TPEdetailsLOG[a].reward - TPEdetailsLOG[b].reward;});
        for (var j = x_sorted.length-1; j > -1; j--){
            var xkey = x_sorted[j];
            var x_req = TPEdetailsLOG[xkey].req;
            var x_reqid = TPEdetailsLOG[xkey].reqid;
            var x_submitted = TPEdetailsLOG[xkey].submit;
            var x_reward = Number(TPEdetailsLOG[xkey].reward).toFixed(2);
            if (x_req === "Bonuses"){
                if (x_reward !== "0.00"){
                    exportcode += "[tr][td]"+x_req+"[/td][td]"+x_submitted+"[/td][td]$"+x_reward+"[/td][/tr]\n";
                }
            }
            else {
                exportcode += "[tr][td][url=https://www.mturk.com/mturk/searchbar?selectedSearchType=hitgroups&requesterId="+x_reqid+"]"+x_req+"[/url][/td][td]"+x_submitted+"[/td][td]$"+x_reward+"[/td][/tr]\n";
            }
        }
        exportcode += "[/table][/spoiler]";

        GM_setClipboard(exportcode);
        alert("Forum Export copied to your clipboard.");
    });

    $peA.click(function(){
        if ($peA.text() === "Today's Projected Earnings"){
            var confirmation = confirm("Are you sure you want to recalculate Today's Projected Earnings?");
            if (confirmation === true){
                TPEhitLOG = {}; TPEdetailsLOG = {}; TPEhourlyLOG = {}; pe = 0;
                $("#tbody2").find("tr.odd, tr.even").remove();
                getDATA(today);
                $peSPAN.text("$0.00");
            }
        }
    });

    $TPE_details.click(function(){
        $TPE_details.find("img").attr("src", ($TPE_details.find("img").attr("src") === "/media/more.gif") ? "/media/less.gif" : "/media/more.gif");
        $TPED_table.toggle();
    });

    if (today){
        var date = today.split("encodedDate=")[1];
        if (date === localStorage.TPE_date){
            if (localStorage.TPEhitLOG){
                TPEhitLOG = JSON.parse(localStorage.TPEhitLOG);
            }
            pe = Number(localStorage.TPE_pe) || 0;
            $peSPAN.text("$"+Number(pe).toFixed(2));
            getDATA(localStorage.TPE_lastpage);
        }
        else {
            localStorage.TPE_date = date;
            localStorage.Goal_progress = 0;
            TPEhitLOG = {}; pe = 0;
            $peSPAN.text("$0.00");
            getDATA(today);
        }
    }

    function getDATA(URL){
        var page = URL.match(/Number=([0-9]*)/g);
        if (page){
            $peA.text("Calculating Page "+page.toString().replace(/[^0-9.]/g, ""));
        }
        else {
            localStorage.removeItem("TPEhitLOG");
            localStorage.Goal_progress = 0;
            $peA.text("Calculating Page 1");
        }

        $.get(URL, function(data){
            var $data = $(data);
            var $hits = $data.find("#dailyActivityTable").find("tr[valign='top']");
            var pagereqerr = $data.find("td.error_title:contains(You have exceeded the maximum allowed page request rate for this website.)").length;
            var noactivity = $data.find("#dailyActivityTable").find("td:contains(You have no HIT activity on this day matching the selected status.)").length;
            if ($hits.length){
                console.log("hit length");
                var url = $data.find("a:contains(Next)").eq(0).prop("href");
                for (var i = 0; i < $hits.length; i++){
                    var req = $hits.eq(i).find("td.statusdetailRequesterColumnValue").text().trim();
                    var title = $hits.eq(i).find("td.statusdetailTitleColumnValue").text().trim();
                    var reward = $hits.eq(i).find("td.statusdetailAmountColumnValue").text().trim();
                    var status = $hits.eq(i).find("td.statusdetailStatusColumnValue").text().trim();
                    var reqid = $hits.eq(i).find("a").prop("href").split("requesterId=")[1].split("&")[0];
                    var hitid = $hits.eq(i).find("a").prop("href").split("HIT+")[1];

                    if (!TPEhitLOG[hitid]){
                        TPEhitLOG[hitid] = {
                            req    : req,
                            title  : title,
                            reward : reward,
                            status : status,
                            reqid  : reqid,
                            hitid  : hitid
                        };
                    }
                }
                if (url){
                    getDATA(url);
                }
                else {
                    pe = 0;
                    for(var key in TPEhitLOG){
                        if (TPEhitLOG[key].status !== "Rejected"){
                            pe += parseFloat(TPEhitLOG[key].reward.replace(/[^0-9.]/g, ""));
                        }
                        if (!TPEdetailsLOG[TPEhitLOG[key].reqid]){
                            TPEdetailsLOG[TPEhitLOG[key].reqid] = {
                                req    : TPEhitLOG[key].req,
                                submit : 1,
                                reward : parseFloat(TPEhitLOG[key].reward.replace(/[^0-9.]/g, "")),
                                reqid  : TPEhitLOG[key].reqid
                            };
                        }
                        else {
                            TPEdetailsLOG[TPEhitLOG[key].reqid].submit = TPEdetailsLOG[TPEhitLOG[key].reqid].submit + 1;
                            TPEdetailsLOG[TPEhitLOG[key].reqid].reward = TPEdetailsLOG[TPEhitLOG[key].reqid].reward + parseFloat(TPEhitLOG[key].reward.replace(/[^0-9.]/g, ""));
                        }
                        if (!TPEhourlyLOG[TPEhitLOG[key].reqid]){
                            var time_data = localStorage.getItem('time_data.' + TPEhitLOG[key].hitid.split('&')[0]);
                            if (time_data != null){
                                var starts = time_data.split("$#$")[3].split('?'); 
                                var last_start = starts[starts.length-1];

                                var stops = time_data.split("$#$")[4].split('?');
                                var last_stop = stops[stops.length-1];

                                if (last_start.length && last_stop.length){
                                    TPEhourlyLOG[TPEhitLOG[key].reqid] = {
                                        req : TPEhitLOG[key].req,
                                        intervals :[[last_start,last_stop]],
                                        totalReward : parseFloat(time_data.split("$#$")[2]),
                                    };
                                }

                            }
                        }
                        else{
                            var time_data = localStorage.getItem('time_data.' + TPEhitLOG[key].hitid.split('&')[0]);
                            if (time_data != null){

                                var starts = time_data.split("$#$")[3].split('?');
                                var last_start = starts[starts.length-1];
                                var stops = time_data.split("$#$")[4].split('?');
                                var last_stop = stops[stops.length-1];
                                if (last_start.length && last_stop.length){

                                    //console.log(TPEhourlyLOG[TPEhitLOG[key].reqid].intervals);
                                    TPEhourlyLOG[TPEhitLOG[key].reqid].intervals.push([last_start,last_stop]);
                                }
                                // console.log(TPEhourlyLOG[TPEhitLOG[key].reqid].intervals);
                                TPEhourlyLOG[TPEhitLOG[key].reqid].totalReward += parseFloat(time_data.split("$#$")[2]);
                            }
                        }
                    }

                    if (!TPEdetailsLOG.bonuses && $("#bonus").length){
                        TPEdetailsLOG.bonuses  = {
                            req    : "Bonuses",
                            submit : "N/A",
                            reward : parseFloat($("#bonus").text().replace(/[^0-9.]/g, "")),
                            reqid  : "N/A"
                        };
                    }
                    else if ($("#bonus").length){
                        TPEdetailsLOG.bonuses.reward = parseFloat($("#bonus").text().replace(/[^0-9.]/g, ""));
                    }

                    var d_sorted = Object.keys(TPEdetailsLOG).sort(function(a,b){return TPEdetailsLOG[a].reward - TPEdetailsLOG[b].reward;});
                    var oddeven = true;
                    for (var j = d_sorted.length-1; j > -1; j--){
                        var dkey = d_sorted[j];
                        var d_req = TPEdetailsLOG[dkey].req;
                        var d_submitted = TPEdetailsLOG[dkey].submit;
                        var d_reward = Number(TPEdetailsLOG[dkey].reward).toFixed(2);
                        var d_hourly = "N/A";
                        if (d_req !== "Bonuses" && TPEhourlyLOG[TPEdetailsLOG[dkey].reqid]){
                            var intervals = TPEhourlyLOG[TPEdetailsLOG[dkey].reqid].intervals;
                            var d_intervals_sum = 0;
                            for (i=0;i<intervals.length;i++){
                                d_intervals_sum += (intervals[i][1]-intervals[i][0]);
                            }
                            var d_intervals_avg = d_intervals_sum / intervals.length;
                            var d_intervals = mergeIntervals(intervals);
                            d_intervals = combineIntervals(intervals,Math.max(2*60*1000,Math.min(10*d_intervals_avg,15*60*1000)));
                            console.log(d_req, "padding", Math.min(10*d_intervals_avg,15*60*1000));
                            var d_time = 0;
                            for (i=0;i<d_intervals.length; i++){
                                var s = new Date(parseInt(d_intervals[i][0]));
                                var f = new Date(parseInt(d_intervals[i][1]));
                                console.log(d_req, i, s.toLocaleTimeString(),f.toLocaleTimeString());
                                d_time += (d_intervals[i][1] - d_intervals[i][0])/(1000*60*60);
                            }
                            d_hourly = "$" + (TPEhourlyLOG[TPEdetailsLOG[dkey].reqid].totalReward / d_time).toFixed(2);
                        }
                        if (oddeven){
                            oddeven = false;
                            $("#tbody2").append('<tr class="odd"><td class="metrics-table-first-value">'+d_req+'</td><td>'+d_submitted+'</td><td><span class="reward">$'+d_reward+'</span></td><td><span class="reward">'+d_hourly+'</span></tr>');
                        }
                        else {
                            oddeven = true;
                            $("#tbody2").append('<tr class="even"><td class="metrics-table-first-value">'+d_req+'</td><td>'+d_submitted+'</td><td><span class="reward">$'+d_reward+'</span><td><span class="reward">'+d_hourly+'</span></td></tr>');
                        }
                    }
                    localStorage.TPEhitLOG = JSON.stringify(TPEhitLOG);
                    localStorage.TPE_lastpage = URL;
                    localStorage.TPE_pe = pe;
                    $peA.text("Today's Projected Earnings");
                    $peSPAN.text("$"+Number(pe).toFixed(2));
                    $('#today_total').text("$"+(Number($('#bonus').text().replace('$','')) +Number($('#TPE_div span.reward').text().replace('$',''))).toFixed(2));
                    document.title =  $('#today_total').text();
                    localStorage.Goal_percent = ((Number(localStorage.TPE_pe)/Number(localStorage.Goal_goal))*100);
                    localStorage.Goal_progress = Number(pe)-Number(localStorage.Goal_goal);
                    if ($("#goalDIV").length){
                        $("#progress").width(Number(localStorage.Goal_percent)+"%");
                        $("#progressper").text(Number(localStorage.Goal_progress).toFixed(2));
                    }
                    Unsynced();
                }
            }
            else if (noactivity){
                console.log("no activity");
                localStorage.TPE_lastpage = URL;
                localStorage.TPE_pe = 0;
                localStorage.Goal_progress = 0;
                $peA.text("Today's Projected Earnings");
                $peSPAN.text("$0.00");
                $('#today_total').text("$"+(Number($('#bonus').text().replace('$','')) +Number($('#TPE_div span.reward').text().replace('$',''))).toFixed(2));

            }
            else if (pagereqerr) {
                console.log("set timeout");
                setTimeout(function(){ getDATA(URL); }, 2000);
            }
        });
    }

    function Unsynced(){
        var hitscalced = Object.keys(TPEhitLOG).length;
        var submitted  = Number($("a[href^='/mturk/statusdetail?encodedDate']:contains(Today)").eq(0).parent().next().text());

        if (hitscalced < submitted){
            $peSPAN.css({backgroundColor:"red"});
        }
        else {
            $peSPAN.css({backgroundColor:""});
        }
    }
}


function create_feedback_str(hitId)
{
    var time_str = '';
    var time_data = localStorage.getItem('time_data.' + hitId);
    if (time_data === null)
    {
        return time_str;
    }
    var last_start = time_data.split("$#$")[3].split('?');
    last_start = new Date(parseInt(last_start[last_start.length - 1]));
    var last_finish = time_data.split("$#$")[4].split('?');
    last_finish = new Date(parseInt(last_finish[last_finish.length - 1]));
    
    console.log(time_data, last_start, last_finish, time_data.split("$#$")[3].split('?'), time_data.split("$#$")[4].split('?'));
    var reward = time_data.split("$#$")[2];
    var time_spent = last_finish - last_start;
    var h = Math.floor(time_spent/(1000*60*60));
    var m = Math.floor((time_spent - h*1000*60*60)/(1000*60));
    var s = Math.floor((time_spent - h*1000*60*60 - m*1000*60)/(1000));
   // return time_data;
  //  return time_data.split("$#$")[0] + " - " + time_data.split("$#$")[1]  + " - " + time_data.split("$#$")[2] + "<br>" +last_start*1000 + "<br>" + last_finish*1000;
    return "Opened: " + last_start.toLocaleTimeString() + "<br>Submitted: " + last_finish.toLocaleTimeString()+ "<br>Time: " + (h.length?pad(h,2) + ":":"") + pad(m,2) + ":" + pad(s,2) + "<br>Hourly: $" + (reward/((last_finish-last_start)/(60*60*1000))).toFixed(2) ;

  //  return time_data.split("$#$")[0] + " - " + time_data.split("$#$")[1]  + " - " + time_data.split("$#$")[2] + "<br>" + (new Date(last_start)).toTimeString() + "<br>" + (new Date(last_finish)).toTimeString();
}

function store_data(action_type)
{
    var $isAccepted = $('input[type="hidden"][name="isAccepted"][value="true"]');
    if ($isAccepted.length > 0 && !hit_returned)
    {
        var hitReview_hitId = $('form[name="hitForm"][action="/mturk/hitReview"] input[name="hitId"]').val();
        console.log(hitReview_hitId);
        var hit_reward = $('form[name="hitForm"][action="/mturk/submit"] input[name="prevReward"]').val().replace('USD','');
        console.log(hit_reward);
        var requester_name = $('td.capsule_field_title:contains("Requester:"):eq(0)').next().text().trim();
        var hit_title = $('table:contains("Requester:"):eq(0) table:eq(0)').text().trim();
        var now_in_milliseconds = new Date().getTime();
        var open_list = "";
        var close_list = "";
        var stored_copy = localStorage.getItem('time_data.' + hitReview_hitId);
        if (stored_copy !== null){
            open_list = stored_copy.split('$#$')[3];
            close_list = stored_copy.split('$#$')[4];
        }
        if (action_type == "open"){
            open_list += "?" + now_in_milliseconds;
        }
        else{
            close_list += "?" + now_in_milliseconds;
        }
        var autoapprove_data = requester_name + "$#$" + hit_title + "$#$"+ hit_reward +"$#$" + open_list + "$#$" + close_list;
        console.log(autoapprove_data);
        localStorage.setItem('time_data.' + hitReview_hitId, autoapprove_data);
    }
}

function mergeIntervals(intervals) {
    // test if there are at least 2 intervals
    if(intervals.length <= 1)
        return intervals;

    var stack = [];
    var top   = null;

    // sort the intervals based on their start values
    intervals = intervals.sort(function (startValue, endValue) {
        if (startValue[0] > endValue[0]) {
            return 1;
        }
        if (startValue[0] < endValue[0]) {
            return -1;
        }
        return 0;
    });

    // push the 1st interval into the stack
    stack.push(intervals[0]);

    // start from the next interval and merge if needed
    for (var i = 1; i < intervals.length; i++) {
        // get the top element
        top = stack[stack.length - 1];

        // if the current interval doesn't overlap with the 
        // stack top element, push it to the stack
        if (top[1] < intervals[i][0]) {
            stack.push(intervals[i]);
        }
        // otherwise update the end value of the top element
        // if end of current interval is higher
        else if (top[1] < intervals[i][1])
        {
            top[1] = intervals[i][1];
            stack.pop();
            stack.push(top);
        }
    }

    return stack;
}

function combineIntervals(intervals, padding) {
    // test if there are at least 2 intervals
    if(intervals.length <= 1)
        return intervals;

    var stack = [];
    var top = null;

    // push the 1st interval into the stack
    stack.push(intervals[0]);

    // start from the next interval and merge if needed
    for (var i = 1; i < intervals.length; i++) {
        // get the top element
        top = stack[stack.length - 1];

        // if the current interval doesn't overlap with the 
        // stack top element, push it to the stack
        if ((intervals[i][0]-top[1]) > padding) {
            //  console.log("hi",(intervals[i][0]-top[1]) + padding);
            stack.push(intervals[i]);
        }
        // otherwise update the end value of the top element
        // if end of current interval is higher
        else if (top[1] < intervals[i][1])
        {
            top[1] = intervals[i][1];
            stack.pop();
            stack.push(top);
        }
    }
    //console.log("stack",stack);
    return stack;
}

function pad(n, width, z) {
  z = z || '0';
  n = n + '';
  return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n;
}