Adds a variety of improvements and extensions to CrowdSurf tasks. Works on AMT and CSW.
当前为
// ==UserScript==
// @name CrowdSurf productivity tools
// @namespace mobiusevalon.tibbius.com
// @version 0.3
// @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 capture(event)
{
/* this is the function that captures the ctrl+enter and enter key press events to
enable those macros in the editor frame. 13 is the ascii code for the enter key */
if(event.keyCode === 13)
{
var modal = document.getElementById("generic-modal")
var submit_button = document.getElementById("approve_button");
/* modals are prompts like the custom guidelines popup, the thing that
makes sure you really want to submit the finished transcript, etc */
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();
}
else if(exists(submit_button) && event.ctrlKey === true) submit_button.click();
}
}
function ckey(s)
{
/* "canonical" is a term i picked up from a python-like language called dm and is
not something i've seen referenced literally anywhere else */
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 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. mine is also immune to xss attacks */
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 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))
{
var guidelines = document.getElementById("guidelines");
span = document.createElement("span");
span.id = "cspt-display";
span.style.fontWeight = "bold";
span.style.fontSize = "16px";
span.style.paddingBottom = "15px";
span.style.cursor = "pointer";
guidelines.insertBefore(span,guidelines.childNodes[0]);
}
var generic_storage_prefix = ("cspt_"+domain+"_"+job_type+"_");
var earnings = GM_getValue(generic_storage_prefix+"earnings","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 transcription_storage_prefix = ("cspt_"+domain+"_");
var transcription_volume = (GM_getValue(transcription_storage_prefix+"transcription_jobs","0")*1);
var roboreview_volume = (GM_getValue(transcription_storage_prefix+"robo-review_jobs","0")*1);
var total_volume = (transcription_volume+roboreview_volume);
var time = GM_getValue(transcription_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 volume = GM_getValue(generic_storage_prefix+"jobs","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;
}
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("$"," ");
// 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)";
// this event listener allows the enter button to close modal dialogs
// and for ctrl+enter to submit a transcript
window.addEventListener("keydown",capture,false);
submit_button.addEventListener("click",function() {tabulate(domain,job_type,job_id,job_reward,secs)},false);
textarea.focus();
}
update_display(domain,job_type,job_level);
window.addEventListener("message",dom_message,false);
break;
}