Hermes HIT exporter

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

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

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

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

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

您需要先安装一款用户脚本管理器扩展,例如 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;
	}
};