// ==UserScript==
// @name Hermes HIT exporter
// @namespace mobiusevalon.tibbius.com
// @version 2.7
// @author Mobius Evalon <[email protected]>
// @description Adds an Export button to MTurk HIT capsules to share HITs on forums, reddit, etc.
// @license Creative Commons Attribution-ShareAlike 4.0; http://creativecommons.org/licenses/by-sa/4.0/
// @include /^https{0,1}:\/\/\w{0,}\.?mturk\.com\/mturk/
// @exclude /&hit_scraper$/
// @grant none
// ==/UserScript==
if(window.olympus === undefined) window.olympus = {};
olympus.hermes = {
__name:"hermes",
// properties
_format:"",
_template:"",
tokens: {},
// methods
__init:function() {
console.log("hermes init");
olympus.style.add(
".hermes_export_button {height: 16px; font-size: 10px; font-weight: bold; border: 1px solid; margin-left: 5px; padding: 0px 5px; background-color: transparent;} "+
"#hermes_export_window {position: fixed; left: 15vw; top: 10vh; background-color: #a5ccdd; border: 2px solid #5c9ebf; border-radius: 10px;} "+
"#hermes_export_window textarea {width: 400px; height: 250px; margin: 0px auto; display: block;} "+
"#hermes_export_window h1 {margin: 10px 0px; padding: 0px; font-size: 150%; font-weight: bold; text-align: center;} "+
"#hermes_export_window input#hhe_completion_time {width: 40px; text-align: center; margin: 0px 5px;} "+
"#hermes_export_window button#hhe_close {display: block; margin: 10px auto; clear: both;} "+
"#hermes_export_window select#hhe_export_format {margin: 0px 5px;} "+
"#hermes_export_window div.hhe_options div.left {display: inline-block; float: left; text-align: left;} "+
"#hermes_export_window div.hhe_options div.right {display: inline-block; float: right; text-align: right;} "+
"#hermes_export_window button {font: inherit;} "+
".noscroll {overflow: hidden;} "
);
Date.prototype.toString = function() {
return (""+this.getDate()+" "+this.getMonthString().slice(0,3)+" "+this.getFullYear()+", "+this.getTwoDigitUTCHours()+":"+this.getTwoDigitUTCMinutes()+"."+this.getTwoDigitUTCSeconds()+" UTC");
};
Date.prototype.getMonthString = function() {
switch(this.getMonth()) {
case 0: return "January";
case 1: return "February";
case 2: return "March";
case 3: return "April";
case 4: return "May";
case 5: return "June";
case 6: return "July";
case 7: return "August";
case 8: return "September";
case 9: return "October";
case 10: return "November";
case 11: return "December";
}
};
Date.prototype.getTwoDigitUTCHours = function() {
return olympus.utilities.pad_string(this.getUTCHours(),2);
};
Date.prototype.getTwoDigitUTCMinutes = function() {
return olympus.utilities.pad_string(this.getUTCMinutes(),2);
};
Date.prototype.getTwoDigitUTCSeconds = function() {
return olympus.utilities.pad_string(this.getUTCSeconds(),2);
};
$("body").append(
$("<div/>")
.attr("id","hermes_export_window")
.append(
$("<h1/>")
.text("Hermes HIT exporter"),
$("<textarea/>")
.attr("id","hhe_export_output")
.hide(),
$("<textarea/>")
.attr("id","hhe_edit_template")
.hide(),
$("<div/>")
.attr("class","hhe_options")
.append(
$("<div/>")
.attr("class","left")
.append(
$("<select/>")
.attr("id","hhe_export_format")
.append(
$("<option/>")
.attr("value","vbulletin")
.text("vBulletin"),
$("<option/>")
.attr("value","markdown")
.text("Markdown"),
$("<option/>")
.attr("value","plaintext")
.text("Plain text"),
$("<option/>")
.attr("value","irc")
.text("IRC")
)
.change(function() {
localStorage.hermes_export_format = $(this).val();
olympus.hermes.switch_format($(this).val());
}),
$("<button/>")
.attr("id","hhe_mode_swap")
.text("Edit")
.click(function() {
if($(this).text() === "Edit") {
$("#hhe_edit_template").show().text(olympus.hermes.get_raw_template($("#hhe_export_format").val()));
$("#hhe_export_output").hide();
$("#hhe_reset_template").show();
$("#hhe_export_format, #hhe_update_time").prop("disabled",true);
$(this).text("Done");
}
else {
localStorage.setItem(("hermes_"+$("#hhe_export_format").val()+"_template"),$("#hhe_edit_template").val());
$("#hhe_export_format, #hhe_update_time").prop("disabled",false);
$("#hhe_edit_template, #hhe_reset_template").hide();
$(this).text("Edit");
olympus.hermes.template_edited();
}
}),
$("<button/>")
.attr("id","hhe_reset_template")
.text("Reset")
.click(function() {
if($("#hhe_mode_swap").text() === "Done" && confirm("Are you sure you want to reset the "+$("#hhe_export_format option:selected").text()+" template to default?\n\nThis can't be undone.")) {
localStorage.removeItem("hermes_"+$("#hhe_export_format").val()+"_template");
$("#hhe_edit_template").val(olympus.hermes.default_template($("#hhe_export_format").val()));
}
})
.hide()
),
$("<div/>")
.attr("class","right")
.append(
$("<label/>")
.append(
document.createTextNode("Time:"),
$("<input/>")
.attr({
"type":"text",
"id":"hhe_completion_time"
})
),
$("<button/>")
.text("Update")
.attr("id","hhe_update_time")
.click(function() {
olympus.hermes.compute_pph($("#hhe_completion_time").val());
})
)
),
$("<button/>")
.attr("id","hhe_close")
.text("Close")
.click(function() {
olympus.hermes.hide();
})
)
.hide()
);
this.add_buttons();
},
__reset_tokens:function() {
this.tokens = {
hermes_version:"2.6",
author_url:"https://gf.qytechs.cn/en/users/9665-mobius-evalon",
hermes_url:"https://gf.qytechs.cn/en/scripts/21175-hermes-hit-exporter",
shortened_author_url:"http://goo.gl/jqpg0h",
shortened_hermes_url:"http://goo.gl/bNdTBj"
};
},
__reset_interface:function() {
$("#hhe_edit_template").hide();
$("#hhe_export_output").hide();
$("#hhe_mode_swap").text("Edit");
$("#hhe_update_time").prop("disabled",true);
$("#hhe_completion_time").val("");
$("#hhe_export_format").prop("disabled",true).val(localStorage.hermes_export_format || "vbulletin");
},
_async_turkopticon:function(result) {
// used as a callback in the olympian library's turkopticon function
function color_prop(n) {
switch(Math.round(n*1)) {
case 0: return "black";
case 1: return "red";
case 2: return "red";
case 3: return "orange";
default: return "green";
}
}
function symbol_prop(n) {
n = Math.round(n*1);
var filled = "⚫⚫⚫⚫⚫",
empty = "⚪⚪⚪⚪⚪";
return (filled.slice(0,n)+empty.slice(n));
}
if(result.hasOwnProperty(this.tokens.requester_id)) {
if($.type(result[this.tokens.requester_id]) === "object") {
$.each(result[this.tokens.requester_id].attrs,function(k,v) {
hermes_template.tokens["to_"+k] = v;
hermes_template.tokens["to_"+k+"_color"] = color_prop(v);
hermes_template.tokens["to_"+k+"_symbols"] = symbol_prop(v);
});
}
else this.tokens.to_wrapper = "[None]";
}
else this.tokens.to_wrapper = "[Error]";
this._process();
},
_async_url_shorten:function(result) {
// used as a callback for url_shortener()
if(Object.keys(result).length) {
$.each(result,function(key,val) {
hermes_template.tokens[key] = val;
});
if(this.tokens.hasOwnProperty("shortened_url_wrapper")) delete this.tokens.shortened_url_wrapper;
}
else this.tokens.shortened_url_wrapper = "[Error]";
this._process();
},
_contains_short_url_tokens:function() {
return /\{shortened_[^}]+\}/i.test(this._template);
},
_contains_to_tokens:function () {
return /{to_(?:pay|fair|fast|comm|graphic)(?:_symbols|_color)?}/i.test(this._template);
},
_expand_tokens:function() {
var tmp = this._template;
$.each(this.tokens,function(key,val) {
if(key.slice(-8) === "_wrapper") tmp = tmp.replace(new RegExp(("<"+key+":[^>]+>"),"gi"),val);
else tmp = tmp.replace(new RegExp(("\\{"+key+"\\}"),"gi"),val);
});
tmp = tmp.replace(/{date_time}/ig,new Date().toString()).replace(/<[^>]+_wrapper:([^>]+)>/gi,"$1");
return tmp;
},
_process:function() {
// this function is called repeatedly to determine the status of the template display
if(!this.tokens.hasOwnProperty("to_pay") && !this.tokens.hasOwnProperty("to_wrapper") && this._contains_to_tokens()) {
this.output("Gathering Turkopticon data for "+this.tokens.requester_name+"...");
olympus.utilities.turkopticon([this.tokens.requester_id],this._async_turkopticon,this);
}
else if(!this.tokens.hasOwnProperty("shortened_url_wrapper") && this._contains_short_url_tokens() && this.tokens_to_shorten().length) {
this.output("Shortening URLs...");
this._url_shortener(this._async_url_shorten,this);
}
else this.display();
},
_url_shortener:function(callback,scope) {
var mirrors = [
"https://ns4t.net/yourls-api.php?action=bulkshortener&title=MTurk&signature=39f6cf4959"
],
params = "",
tokens = [],
result = {};
function exit() {
if($.type(callback) === "function") callback.call(scope,result);
}
tokens = olympus.hermes.tokens_to_shorten();
if(tokens.length) {
params = (
"&urls[]="+
tokens
.map(function(val,idx,arr) {
return olympus.hermes.tokens[val];
})
.join("&urls[]=")
);
olympus.utilities.ajax_get(mirrors,params,function(response) {
if(response.length) {
response = response.split(";");
$.each(tokens,function(key,val) {
result["shortened_"+val] = response[key];
});
}
else console.log("Hermes HIT exporter: url shortening service appeared to be queried successfully but returned no data");
exit();
});
}
else exit();
},
add_buttons:function() {
$("a.capsulelink").each(function() {
if(!$(this).siblings(".hermes_export_button").length) $(this).append(
$("<button/>")
.attr("class","hermes_export_button")
.text("Export")
.click(function() {
olympus.hermes.__reset_interface();
$("div#hermes_export_window").show();
$("body").addClass("noscroll");
olympus.hermes.begin({
hit:$(this).closest("table").parent().closest("table"),
format:$("#hhe_export_format").val()
});
})
);
});
},
begin:function(obj) {
if($.type(obj) === "object" && obj.hasOwnProperty("hit") && obj.hasOwnProperty("format")) {
this.__reset_tokens();
this.mturk_info(obj.hit);
this.switch_format(obj.format);
}
else throw new Error("Hermes HIT exporter: template was initialized with improper arguments");
},
compute_pph:function(time) {
var mins = 0,
secs = 0;
if(time.indexOf(":") > -1) {
mins = Math.floor(time.split(":")[0]*1);
secs = Math.floor(time.split(":")[1]*1);
// in case some smart aleck enters something like 1:75
while(secs > 59) {
mins++;
secs -= 60;
}
}
else {
mins = Math.floor(time*1);
secs = (((time*1)-mins)*60);
}
if((mins+secs) <= 0) {
if(this.tokens.hasOwnProperty("my_time")) delete this.tokens.my_time;
if(this.tokens.hasOwnProperty("hourly_rate")) delete this.tokens.hourly_rate;
this.tokens.pph_wrapper = "";
}
else {
this.tokens.my_time = (""+mins+":"+olympus.utilities.pad_string(secs,2));
this.tokens.hourly_rate = "$"+(((this.tokens.hit_reward.slice(1)*1)/((mins*60)+secs))*3600).toFixed(2);
if(this.tokens.hasOwnProperty("pph_wrapper")) delete this.tokens.pph_wrapper;
}
this._process();
},
default_template:function(format) {
if(format === "vbulletin") return "[url={preview_link}][color=blue]{hit_name}[/color][/url] [[url={panda_link}][color=blue]PANDA[/color][/url]]\n"+
"[b]Reward[/b]: {hit_reward}<pph_wrapper:/{my_time} ({hourly_rate}/hour)>\n"+
"[b]Time allowed[/b]: {hit_time}\n"+
"[b]Available[/b]: {hits_available}\n"+
"[b]Description[/b]: {hit_desc}\n"+
"[b]Qualifications[/b]: {quals}\n\n"+
"[b]Requester[/b]: [url={requester_hits}][color=blue]{requester_name}[/color][/url] [[url={contact_requester}][color=blue]Contact[/color][/url]]\n"+
"[url={to_reviews}][color=blue][b]Turkopticon[/b][/color][/url]: <to_wrapper:[Pay: [color={to_pay_color}]{to_pay}[/color]] [Fair: [color={to_fair_color}]{to_fair}[/color]] [Fast: [color={to_fast_color}]{to_fast}[/color]] [Comm: [color={to_comm_color}]{to_comm}[/color]]>\n"+
"[size=8px]Generated {date_time} with [url={hermes_url}][color=blue]Hermes HIT Exporter[/color][/url] {hermes_version} by [url={author_url}][color=blue]Mobius Evalon[/color][/url][/size]";
else if(format === "markdown") return "[{hit_name}]({preview_link}) \\[[PANDA]({panda_link})\\] \n"+
"**Reward**: {hit_reward}<pph_wrapper:/{my_time} ({hourly_rate}/hour)> \n"+
"**Time allowed**: {hit_time} \n"+
"**Available**: {hits_available} \n"+
"**Description**: {hit_desc} \n"+
"**Qualifications**: {quals}\n\n"+
"**Requester**: [{requester_name}]({requester_hits}) \\[[Contact]({contact_requester})\\] \n"+
"[**Turkopticon**]({to_reviews}): <to_wrapper:\\[Pay: {to_pay}\\] \\[Fair: {to_fair}\\] \\[Fast: {to_fast}\\] \\[Comm: {to_comm}\\]> \n"+
"^Generated {date_time} with [Hermes HIT Exporter]({hermes_url}) {hermes_version} by [Mobius Evalon]({author_url})";
else if(format === "plaintext") return "{hit_name} [{preview_link}] \n"+
"Reward: {hit_reward}<pph_wrapper:/{my_time} ({hourly_rate}/hour)> \n"+
"Time allowed: {hit_time} \n"+
"Available: {hits_available} \n"+
"Description: {hit_desc} \n"+
"Qualifications: {quals}\n\n"+
"Requester: {requester_name} [{requester_hits}] \n"+
"Turkopticon: <to_wrapper:[Pay: {to_pay}] [Fair: {to_fair}] [Fast: {to_fast}] [Comm: {to_comm}]> [{to_reviews}] \n"+
"Generated {date_time} with Hermes HIT Exporter {hermes_version} by Mobius Evalon [{hermes_url}]";
else if(format === "irc") return "{hit_name} [ View: {shortened_preview_link} PANDA: {shortened_panda_link} ] "+
"Reward: {hit_reward}<pph_wrapper:/{my_time} ({hourly_rate}/hour)> Time: {hit_time} | "+
"Requester: {requester_name} [ HITs: {shortened_requester_hits} TO: {shortened_to_reviews} ] Pay={to_pay} Fair={to_fair} Fast={to_fast} Comm={to_comm}";
},
display:function() {
$("#hhe_update_time, #hhe_export_format").prop("disabled",false);
this.output(this._expand_tokens(this._template));
},
get_raw_template:function(string) {
return (localStorage.getItem("hermes_"+string+"_template") || this.default_template(string));
},
hide:function() {
$("#hermes_export_window").hide();
$("body").removeClass("noscroll");
},
mturk_info:function($element) {
function scrape_from_tooltip(tt) {
return $("a[id*='"+tt+".tooltip']",$element).parent().next().text().collapseWhitespace();
}
// basic HIT info that can be scraped off the page
this.tokens.hit_name = $("a.capsulelink[href='#']",$element).first().text().collapseWhitespace();
this.tokens.hit_id = $("a[href*='groupId']",$element).first().attr("href").match(/groupId=([A-Z0-9]{30})(?:&|$)/)[1];
this.tokens.hit_desc = scrape_from_tooltip("description");
this.tokens.hit_time = scrape_from_tooltip("duration_to_complete");
this.tokens.hits_available = scrape_from_tooltip("number_of_hits");
this.tokens.hit_reward = scrape_from_tooltip("reward");
this.tokens.requester_name = $("a[href*='selectedSearchType=hitgroups']",$element).first().text().collapseWhitespace();
this.tokens.requester_id = $("a[href*='requesterId']",$element).first().attr("href").match(/requesterId=([A-Z0-9]{12,14})(?:&|$)/)[1];
// link properties for convenience, since these are long URLs that only use one bit of previously collected info
this.tokens.preview_link = ("https://www.mturk.com/mturk/preview?groupId="+this.tokens.hit_id);
this.tokens.panda_link = ("https://www.mturk.com/mturk/previewandaccept?groupId="+this.tokens.hit_id);
this.tokens.requester_hits = ("https://www.mturk.com/mturk/searchbar?selectedSearchType=hitgroups&requesterId="+this.tokens.requester_id);
this.tokens.contact_requester = ("https://www.mturk.com/mturk/contact?requesterId="+this.tokens.requester_id+"&requesterName="+this.tokens.requester_name);
this.tokens.to_reviews = ("https://turkopticon.ucsd.edu/"+this.tokens.requester_id);
// parse qualifications
var $qual_anchor = $("a[id*='qualificationsRequired.tooltip']",$element);
if($qual_anchor.parent().next().text().trim() === "None") this.tokens.quals = "None";
else {
var quals = [];
$("tr:not(:first-of-type) td:first-of-type",$qual_anchor.closest("table")).each(function() {quals.push($(this).text().collapseWhitespace());});
this.tokens.quals = quals.join("; ");
}
// by default the user has provided no completion time estimate, so this wrapper needs to not be displayed until the user provides that info
this.tokens.pph_wrapper = "";
},
output:function(string) {
$("div#hermes_export_window textarea#hhe_export_output").show().text(string);
},
switch_format:function(string) {
string = (string || $("#hhe_export_format").val());
this._format = string;
this._template = this.get_raw_template(string);
this._process();
},
template_edited:function() {
this._template = this.get_raw_template(this._format);
this._process();
},
tokens_to_shorten:function() {
var arr = [];
$.each(this._template.match(/(\{shortened_[^}]+\})/gi),function(key,val) {
if(hermes_template.tokens.hasOwnProperty(val.slice(11,-1)) && !hermes_template.tokens.hasOwnProperty(val.slice(1,-1)) && arr.indexOf(val.slice(11,-1)) === -1)
arr.push(val.slice(11,-1));
});
return arr;
}
};