Hermes HIT exporter

Adds an Export button to MTurk HIT capsules to share HITs on forums, reddit, etc.

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Hermes HIT exporter
// @namespace    mobiusevalon.tibbius.com
// @version      4.0-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.+/
// @exclude      /&hit_scraper$/
// @exclude      /\/HM$/
// @grant        none
// ==/UserScript==

if(window.olympus === undefined) window.olympus = {};

olympus.hermes = {
	__name:"hermes",
	__version:"4.0-7",
	__href:"https://greasyfork.org/en/scripts/21175-hermes-hit-exporter",
	default_settings:{
		show_export_athena:false,
		show_export_mturk:true
	},
	// properties
	_format:"",
	_template:"",
	tokens:{},
	// methods
	__configurable:function() {
		return [
			olympus.settings.generate({
				option:"show_export_mturk",
				type:"checkbox",
				value:olympus.settings.get(olympus.hermes,"show_export_mturk"),
				name:"Export button (Turk)",
				desc:"Whether or not to enable Hermes to place its Export button on each HIT when browsing on Mechanical Turk."
			}),
			olympus.settings.generate({
				option:"show_export_athena",
				type:"checkbox",
				value:olympus.settings.get(olympus.hermes,"show_export_athena"),
				name:"Export button (Athena)",
				desc:
					"Toggles integration with the Athena scraper.  When enabled, Hermes will place export buttons on each result row "+
					"to allow exporting from the scraper."
			})
		];
	},
	__parse_settings:function(settings) {
		olympus.settings.update(olympus.hermes,settings);

		if(!settings.show_export_athena && $("body").hasClass("athena_interface")) $(".hermes_export_button").remove();
		else if(!settings.show_export_mturk && !$("body").hasClass("athena_interface")) $(".hermes_export_button").remove();
		else olympus.hermes.add_buttons();
	},
	__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: 1px 5px; background-color: transparent; cursor: pointer;} "+
			"#hermes_export_window {position: fixed; left: 15vw; top: 10vh; background-color: #a5ccdd; border: 2px solid #5c9ebf; border-radius: 10px; z-index: 150;} "+
			"#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;} "+
			"#hhe_completion_time {width: 40px; text-align: center; margin: 0px 5px;} "+
			"#hhe_update_time {margin-right: 5px;} "+
			"#hhe_close {display: block; margin: 10px auto; clear: both;} "+
			"#hhe_export_format {margin: 0px 5px;} "+
			"#hermes_export_window .hhe_options .left {display: inline-block; float: left; text-align: left;} "+
			"#hermes_export_window .hhe_options .right {display: inline-block; float: right; text-align: right;} "+
			"#hermes_export_window button {font: inherit;} "+
			".noscroll {overflow: hidden;} "
		);

		if($("a.capsulelink").length) {
			$("body").append(
				$("<div/>")
					.attr({
						"data-pantheon":"hermes",
						"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(hit) {
		this.tokens = {
			hermes_version:olympus.hermes.__version,
			author_url:"https://greasyfork.org/en/users/9665-mobius-evalon",
			hermes_url:olympus.hermes.__href,
			shortened_author_url:"http://goo.gl/jqpg0h",
			shortened_hermes_url:"http://goo.gl/bNdTBj",
			// by default the user has provided no completion time estimate, so this wrapper needs to not be displayed until the user provides that info
			pph_wrapper:""
		};
		var info = olympus.utilities.capsule_info(hit);
		$.each(info,function(key,val) {
			olympus.hermes.tokens[key] = val;
		});
	},
	__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) {
					olympus.hermes.tokens["to_"+k] = v;
					olympus.hermes.tokens["to_"+k+"_color"] = color_prop(v);
					olympus.hermes.tokens["to_"+k+"_symbols"] = symbol_prop(v);
				});
				this.tokens.to_avg = olympus.utilities.to_average(result[this.tokens.requester_id]).toFixed(2);
			}
			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) {
				olympus.hermes.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,olympus.utilities.datetime.output(Date.now(),"e U Z, C:w.x")+" UTC").replace(/<[^>]+_wrapper:([^>]+)>/gi,"$1");
		tmp = tmp.replace(/\\n/g,"\n");
		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() {
		var $anchors;
		if($("body").hasClass("athena_interface") && olympus.settings.get(olympus.hermes,"show_export_athena")) $anchors = $("#athena_results .title");
		else if(olympus.settings.get(olympus.hermes,"show_export_mturk")) $anchors = $("a.capsulelink");
		$.each($anchors,function() {
			if(!$(this).siblings(".hermes_export_button").length) $(this).after(
				$("<a/>")
					.attr({
						"class":"hermes_export_button",
						"title":"Use Hermes to generate an export of this HIT to share on forums, IRC, reddit, etc."
					})
					.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(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");
	},
	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(olympus.hermes.tokens.hasOwnProperty(val.slice(11,-1)) && !olympus.hermes.tokens.hasOwnProperty(val.slice(1,-1)) && arr.indexOf(val.slice(11,-1)) === -1)
				arr.push(val.slice(11,-1));
		});
		return arr;
	}
};