CrowdSurf productivity tools

Adds a variety of improvements and extensions to CrowdSurf tasks. Works on AMT and CSW.

当前为 2015-11-10 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         CrowdSurf productivity tools
// @namespace    mobiusevalon.tibbius.com
// @version      0.5
// @description  Adds a variety of improvements and extensions to CrowdSurf tasks.  Works on AMT and CSW.
// @author       Mobius Evalon
// @include      /^https{0,1}:\/\/ops.cielo24.com\/mediatool\/.*$/
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// ==/UserScript==

function exists(element,tag)
{
	// this is a thing i find myself doing very often in my usersripts so i just turned it into a function
    if(element !== null && element !== undefined && (tag === undefined || element.tagName === tag)) return 1;
	return 0;
}

function dom_message(event)
{
    /* this technically interferes with the operation of the job frame because 
    crowdsurf uses this same manner of function to communicate data along the dom, 
    which makes it throw errors nearly every time this is called.  this is why the 
    format of my dom message is so complicated, so the job frame doesn't mistake 
    what i'm doing for something it needs to handle. */
    if(event.origin === "https://ops.cielo24.com")
    {
        var data = event.data.split("-");
        if(data[0] === "cspt")
        {
            switch(data[1])
            {
                case "reset":
                    var storage_prefix = ("cspt_"+data[2]+"_"+data[3]+"_");
                    GM_deleteValue(storage_prefix+"jobs");
                    GM_deleteValue(storage_prefix+"earnings");
                    GM_deleteValue(storage_prefix+"time");
                    update_display(data[2],data[3],data[4]);
                    break;
            }
        }
    }
}

function capture(event)
{
    if(event.ctrlKey === true)
    {
        var submit_button = document.getElementById("approve_button");
        if(event.keyCode === 13 && exists(submit_button,"BUTTON") && submit_button.disabled === false) submit_button.click();
    }
    else if(event.altKey === true)
    {
        if(event.keyCode === 13) window.top.postMessage("cspt-hotkey-accept","https://work.crowdsurfwork.com");
        else if(event.keyCode === 8) window.top.postMessage("cspt-hotkey-return","https://work.crowdsurfwork.com");
    }
    else // neither Ctrl nor Alt
    {
        var modal = document.getElementById("generic-modal");
        if(exists(modal) && modal.style.display === "block")
        {
            var ok_button = modal.getElementsByClassName("accept")[0];
            if(exists(ok_button,"BUTTON") && ok_button.innerHTML === "Ok") 
            {
                ok_button.click();
                event.preventDefault();
            }
        }
    }
}

function ckey(s)
{
    /* a function from a language i used years ago called dm */
    return s.replace(/[^a-z]/ig,"-").toLowerCase();
}

function tabulate(url,type,id,reward,secs)
{
    /* the guts of the script.  this is called by clicking the submit button and
    then stores all of this relevant data from the event */
    
    var storage_prefix = ("cspt_"+url+"_"+type+"_");
    var last_job = GM_getValue(storage_prefix+"last","");
    if(id != last_job)
    {
        var jobs = GM_getValue(storage_prefix+"jobs","0");
        var earnings = GM_getValue(storage_prefix+"earnings","0");
        var time = GM_getValue(storage_prefix+"time","0");

        // math operations on strings automatically juggle the type to numerical
        jobs++;
        earnings = ((earnings*1)+(reward*1));
        time = ((time*1)+((new Date().getTime()/1000)-secs));

        /* GM_getValue and GM_setValue basically only work with strings, so
        that is all that can be stored and retrieved safely */
        GM_setValue(storage_prefix+"jobs",(""+jobs));
        GM_setValue(storage_prefix+"earnings",(""+earnings.toFixed(2)));
        GM_setValue(storage_prefix+"time",(""+time));
        GM_setValue(storage_prefix+"last",id);
    }
}

function l1_bonus(v)
{
    v = Math.floor(v*1) ; // make sure it's an integer
    var t1 = ((v >= 100) ? 2 : 0);
    var t2 = ((v >= 200) ? 5 : 0);
    var t3 = ((v >= 300) ? (9+((v-300)*0.03)) : 0);
    var t4 = ((v >= 500) ? (20+((v-500)*0.04)) : 0);
    var t5 = ((v >= 1000) ? (50+((v-1000)*0.05)) : 0);
    return Math.max(t1,t2,t3,t4,t5);
}

function dbl(n)
{
    // just returns a float rounded to two decimal points
    n *= 1;
    return n.toFixed(2);
}

function mmss(t)
{
    // minutes:seconds display from a total number of seconds
    var m = (Math.floor(t/60) || 0);
    t %= 60;
    var s = (Math.ceil(t) || 0);
    return (m+":"+(s < 10 ? "0" : "")+s);
}

function update_display(domain,job_type,job_level)
{
    var span = document.getElementById("cspt-display");
    if(!exists(span))
    {
        span = document.createElement("span");
        span.id = "cspt-display";
        span.style.fontWeight = "bold";
        span.style.fontSize = "16px";
        span.style.paddingBottom = "15px";
        span.style.cursor = "pointer";
        var guidelines = document.getElementById("tab_content");
        guidelines.insertBefore(span,guidelines.childNodes[0]);
    }
    
    var display_msg = ("<b style='font-size: 125%;'>L"+job_level+": "+job_type.ucfirst()+" <span onclick=\"window.postMessage('cspt-reset-"+domain+"-"+job_type+"-"+job_level+"','https://ops.cielo24.com');\" style='color: #c36969'>[Reset]</span></b><br>");
    if(job_type === "transcription" || job_type === "robo-review")
    {
        var storage_prefix = ("cspt_"+domain+"_");
        var transcription_volume = (GM_getValue(storage_prefix+"transcription_jobs","0")*1);
        var roboreview_volume = (GM_getValue(storage_prefix+"robo-review_jobs","0")*1);
        var total_volume = (transcription_volume+roboreview_volume);
        var earnings = GM_getValue(storage_prefix+"transcription_earnings","0");
        var time = GM_getValue(storage_prefix+"transcription_time","0");
        var avg_pay = (earnings/Math.max(1,total_volume));
        var avg_time = (time/Math.max(1,total_volume));
        var jpd = ((1/avg_pay) || 0);
        var tpd = ((jpd*avg_time) || 0);
        var jph = ((3600/avg_time) || 0);
        var pph = ((jph*avg_pay) || 0);
        display_msg += ("Volume: "+total_volume+" <b style='font-size: 75%; color: #555555;'>("+transcription_volume+" transcription + "+roboreview_volume+" robo-review)</b><br>Earnings: $"+dbl(earnings)+"<br><b style='font-size: 75%; color: #555555;'>Per job avg: $"+(avg_pay.toFixed(4))+" pay, "+mmss(avg_time)+" time<br>Per dollar: "+jpd.toFixed(2)+" jobs, "+mmss(tpd)+" time<br>Per hour: "+jph.toFixed(2)+" jobs, $"+pph.toFixed(2)+" pay</b>");
    }
    else if(job_type === "review")
    {
        var storage_prefix = ("cspt_"+domain+"_"+job_type+"_");
        var volume = GM_getValue(storage_prefix+"jobs","0");
        var earnings = GM_getValue(storage_prefix+"earnings","0");
        display_msg += ("Volume: "+volume+"<br>Earnings: $"+dbl(earnings));
    }
    
    span.innerHTML = display_msg;
}

String.prototype.slice_substring = function(s,e)
{
    var start = this.indexOf(s);
    var end = this.indexOf(e);
    var len = (start+s.length);
    if(start > -1 && end > -1 && end > len) return this.substring(len,end);
}

String.prototype.ucfirst = function()
{
    return (this.charAt(0).toUpperCase()+this.slice(1));
}

var domain = "";
var job_type = "";
var job_level = "";
var job_id = "";
var job_reward = "";

var regex = /.*\/mediatool\/(\w+)\/.*&(?:amp;)?crowd=([\w ]+).*&(?:amp;)?crowd_assignment_id=(\w+)/ig;
var matches = regex.exec(unescape(window.location.href));

if(matches !== null)
{
    domain = ckey(matches[matches.length-2]);
    job_type = ckey(matches[matches.length-3]);
    job_id = matches[matches.length-1];
}

// doing a bit of manual reassignment here
switch(job_type)
{
    case "transcription":
        // general content and media transcription queues
        job_level = "1";
        break;
    case "transcription-asr":
        // "review and edit" queue, rolls together with transcription
        job_type = "robo-review";
        job_level = "1";
        break;
    case "transcription-review":
        // this is mainly so i can use the word in display later
        job_type = "review";
        job_level = "2"
        break;
}
console.log("wtf");

if(document.readyState === "interactive" || document.readyState === "complete") initialize();
else document.addEventListener("DOMContentLoaded",initialize,false); //firefox workaround, mostly

function initialize()
{
    switch(job_type)
    {
        case "transcription": case "robo-review": case "review":
            if((domain === "mechanical-turk" && job_id.length === 30) || (domain === "crowdsurf" && job_id.length === 32))
            {
                var submit_button = document.getElementById("approve_button");
                var price_header = document.getElementById("price_header");
                var textarea = document.getElementById("plaintext_edit");
                var secs = (new Date().getTime()/1000);

                // find the reward amount for this job
                job_reward = price_header.innerHTML.slice_substring("$","&nbsp;");

                // update help areas
                var help_table = document.getElementById("hotkeys");
                if(exists(help_table,"TABLE"))
                {
                    var tbody = help_table.getElementsByTagName("tbody")[0];
                    var tr = document.createElement("tr");
                    tr.innerHTML = "<td>CTRL+Enter</td><td>Submit job.</td>";
                    tbody.insertBefore(tr,tbody.childNodes[0]);

                }
                submit_button.title = "Submit this Transcript (Ctrl + Enter)";

                submit_button.addEventListener("click",function() {tabulate(domain,job_type,job_id,job_reward,secs)},false);
                textarea.focus();
            }
            else window.focus();
            update_display(domain,job_type,job_level);
            window.addEventListener("keydown",capture,false); // global hook for keyboard shortcuts
            window.addEventListener("message",dom_message,false);
            break;
    }
}