turkopticon.net

Review requesters on Amazon Mechanical Turk

  1. // ==UserScript==
  2. // @name turkopticon.net
  3. // @version 2021.08.23.1
  4. // @description Review requesters on Amazon Mechanical Turk
  5. // @author Lilly Irani, Six Silberman, Phil, anonymous contributors
  6. // @homepage https://turkopticon.net
  7. // @include http://*.mturk.com/*
  8. // @include https://*.mturk.com/*
  9. // @namespace https://greasyfork.org/users/431111
  10. // ==/UserScript==
  11.  
  12. var TURKOPTICON_BASE = "https://turkopticon.net/";
  13. var API_BASE = "https://turkopticon.net/api/";
  14. var API_MULTI_ATTRS_URL = API_BASE + "multi-attrs.php?ids=";
  15.  
  16. function getRequesterAnchorsAndIds(a) {
  17. //a is a list of anchor DOM elements derived in the previous function
  18. var rai = {};
  19. var requesterRegex = new RegExp(/requesterId=([0-9A-Z]+)|\/requesters\/([0-9A-Z]+)\/projects/);
  20. var rf = new RegExp(/contact/);
  21. var isContactLink = new RegExp(/Contact/);
  22. var isImgButton = new RegExp(/img/);
  23. var requestersHere = false;
  24.  
  25. for (var i = 0; i < a.length; i++) {
  26. var href = a[i].getAttribute('href');
  27. var requesterIdMatch = requesterRegex.exec(href);
  28. if ((requesterIdMatch) /*&& !rf.test(href)*/ ) {
  29. var innards = a[i].innerHTML;
  30. if (!isContactLink.test(innards) && !isImgButton.test(innards)) {
  31. var id = requesterIdMatch[1] || requesterIdMatch[2];
  32. if (!rai.hasOwnProperty(id)) {
  33. rai[id] = [];
  34. }
  35. rai[id].push(a[i]);
  36. requestersHere = true;
  37. }
  38. }
  39. }
  40.  
  41. rai = (requestersHere) ? rai : null;
  42. return rai;
  43. }
  44.  
  45. function buildXhrUrl(rai) {
  46. var url = API_MULTI_ATTRS_URL;
  47. var ri = Object.keys(rai);
  48. for(var i = 0; i < ri.length; i++) {
  49. url += ri[i];
  50. if (i < ri.length - 1) { url += ","; }
  51. }
  52. return url;
  53. }
  54.  
  55. function makeXhrQuery(url) {
  56. var xhr = new XMLHttpRequest();
  57. xhr.open('GET', url, false);
  58. xhr.send(null);
  59. var resp = JSON.parse(xhr.response);
  60. return resp;
  61. }
  62.  
  63. function ul(cl, inner) {
  64. return "<ul class='" + cl + "'>" + inner + "</ul>"; }
  65.  
  66. function li(cl, inner) {
  67. return "<li class='" + cl + "'>" + inner + "</li>"; }
  68.  
  69. function span(cl, inner) {
  70. return "<span class='" + cl + "'>" + inner + "</span>"; }
  71.  
  72. function strmul(str, num) {
  73. return Array(num + 1).join(str); }
  74.  
  75. function pad(word, space) {
  76. if (word.length >= space) { return word; }
  77. else { return word + strmul("&nbsp;", space - word.length); } }
  78.  
  79. function long_word(word) {
  80. switch(word) {
  81. case "comm": return "communicativity"; break;
  82. case "pay" : return "generosity"; break;
  83. case "fair": return "fairness"; break;
  84. case "fast": return "promptness"; break;
  85. default: return word; break; } }
  86.  
  87. function visualize(i, max, size) {
  88. var color;
  89. if (i / max <= 2 / 5) { color = 'red'; }
  90. else if (i / max <= 3 / 5) { color = 'yellow'; }
  91. else { color = 'green'; }
  92. var filled = Math.round((i / max) * size);
  93. var unfilled = size - filled;
  94. var bar = span("bar", span(color, strmul("&nbsp;", filled)) + span("unf", strmul("&nbsp;", unfilled)));
  95. return bar; }
  96.  
  97. function visualize_buckets(i, max, size, bucket) {
  98. var color;
  99. if (bucket == "$15+") { color = 'green'; }
  100. else if (bucket == "$10-$15" || bucket == "$7-$10") { color = 'yellow'; }
  101. else { color = 'red'; }
  102. var filled = Math.round((i / max) * size);
  103. var unfilled = size - filled;
  104. var bar = span("bar", span(color, strmul("&nbsp;", filled)));// + span("unf", strmul("&nbsp;", unfilled)));
  105. return bar; }
  106.  
  107. function attr_html_max(n, i, max, size, bucket) {
  108. return pad(n, 7) + ": " + visualize_buckets(i, max, size, bucket); }
  109.  
  110. function attr_html(n, i) {
  111. return pad(long_word(n), 15) + ": " + visualize(i, 5, 25) + "&nbsp;" + i + " / 5"; }
  112.  
  113. function ro_html(ro, hide_generosity) {
  114. var rohtml = "";
  115. if (typeof ro.attrs != 'undefined') {
  116. var keys = Object.keys(ro.attrs);
  117. for (var i = 0; i < keys.length; i++) {
  118. if (!(keys[i] == "pay" && hide_generosity))
  119. rohtml += li("attr", attr_html(keys[i], ro.attrs[keys[i]])); } }
  120. return rohtml; }
  121.  
  122. function what(ro) {
  123. var str = "";
  124. if (typeof ro.attrs != 'undefined') {
  125. str = li("link", `<a target="_blank"href=${TURKOPTICON_BASE}help#pay_buckets>Where is pay/hr?</a>&nbsp<a target="_blank" href='` + TURKOPTICON_BASE + "help#attr'>What do these scores mean?</a>"); }
  126. return str; }
  127.  
  128. function nrs(rid, nrevs) {
  129. var str = "";
  130. if (typeof nrevs === 'undefined') {
  131. str = "<li>No reviews for this requester</li>"; }
  132. else { str = "<li>Scores based on&nbsp;<a target=\"_blank\" href='" + TURKOPTICON_BASE + rid + "'>" + nrevs + " reviews</a></li>"; }
  133. return str; }
  134.  
  135. function tos(tosflags) {
  136. var str = "<li>Terms of Service violation flags: " + tosflags + "</li>";
  137. return str; }
  138.  
  139. function rl(rid, name, hitid, hitname) {
  140. var rl = "<li><a href='" + TURKOPTICON_BASE + "report?requester[amzn_id]=" + rid;
  141. rl += "&requester[amzn_name]=" + name + "&report[hit_id]=" + hitid + "&report[hit_names]=" + hitname + "'>";
  142. rl += "Report your experience with this requester &raquo;</a></li>";
  143. return rl; }
  144. function pay_buckets(buckets) {
  145. var rohtml = "";
  146. var maxVal = 0;
  147. for (var i = 0; i < buckets.length; i++) {
  148. maxVal = buckets[i][1] > maxVal ? buckets[i][1] : maxVal; }
  149. if (!maxVal) return li("attr", "&nbspno data");
  150. for (var i = 0; i < buckets.length; i++) {
  151. rohtml += li("attr", attr_html_max(buckets[i][0], buckets[i][1], maxVal, 20, buckets[i][0])); }
  152. return rohtml;
  153. }
  154.  
  155. function div(cl, content) { return `<div class=${cl}>${content}</div>`; }
  156. function allBucketsZero(buckets) { for(i=0; i<buckets.length; i++) { if (buckets[i][1] != 0) return false; } return true; }
  157.  
  158. function dropDown(ro, rid, hitid, hitname) {
  159. var n = ro.name;
  160. var arrcls = "";
  161. var has_data = ro.pay_buckets && !allBucketsZero(ro.pay_buckets);
  162. if (typeof ro.attrs != 'undefined') { arrcls = "toc"; }
  163. var dd = ul("tob", li(arrcls, "&#9660;") + div("tom", div("to_original_attrs", ro_html(ro, has_data)) + div("to_pay_buckets", has_data ? div("to_pay_label", li("attr", "pay/<br>hr"))+div("to_pay_display", pay_buckets(ro.pay_buckets)) : "") + div("turkopticon_info",what(ro) + nrs(rid, ro.reviews) + tos(ro.tos_flags) + rl(rid, n, hitid, hitname))));
  164. return dd; }
  165.  
  166. function insertInlineCss() {
  167. if (document.getElementsByClassName("detail-bar-label")[0] &&
  168. document.getElementsByClassName("detail-bar-label")[0].innerText.indexOf("Requester") == 0) {
  169. document.getElementsByClassName("detail-bar-label")[0].parentNode.style.overflow = "visible";
  170. }
  171.  
  172. var head = document.getElementsByTagName("head")[0],
  173. style = document.createElement('style'),
  174. css = ".tob, .tom { list-style-type: none; padding-left: 0; }\n";
  175. css += ".tob { float: left; margin-right: 5px; }\n";
  176. css += ".tob > .tom { display: none; position: absolute; background-color: #ebe5ff; border: 1px solid #aaa; padding: 5px; z-index: 10;}\n";
  177. css += ".tob:hover > .tom { display: flex; }\n";
  178. css += ".tob:hover { padding-right: 30px; }\n";
  179. css += ".tob > li { border: 1px solid #9db9d1; background-color: #ebe5ff; color: #00c; padding: 3px 3px 1px 3px; }\n";
  180. css += ".tob > li.toc { color: #f33; }\n";
  181. css += "@media screen and (-webkit-min-device-pixel-ratio:0) { \n .tob { margin-top: -5px; } \n}\n";
  182. css += ".attr { font-family: Monaco, Courier, monospace; color: #333; }\n";
  183. css += ".bar { font-size: 0.6em; display: inline-flex; align-items: center; }\n";
  184. css += ".unf { background-color: #ddd; }\n";
  185. css += ".red { background-color: #f00; }\n";
  186. css += ".yellow { background-color: #f90; }\n";
  187. css += ".green { background-color: #6c6; }\n";
  188. css += ".gray_link { margin-bottom: 15px; }\n";
  189. css += ".gray_link a { color: #666; }\n";
  190. css += ".tob > .tom > .to_pay_buckets { display: flex; padding-left: 20px; }\n";
  191. css += ".tob > .tom > .turkopticon_info { padding-left: 20px; }\n";
  192. css += ".tob > .tom > .to_pay_buckets > .to_pay_label { padding-right: 5px; }\n";
  193. style.textContent = css;
  194. head.appendChild(style);
  195. }
  196.  
  197.  
  198. function getNames(rai, resp) {
  199. for(var rid in rai) {
  200. if (rai.hasOwnProperty(rid)) {
  201. if (resp[rid] == "") { // empty response, no data in Turkopticon DB for this ID
  202. resp[rid] = JSON.parse('{"name": "' + rai[rid][0].innerHTML.split("</span>")[0].split(">")[1] + '"}');
  203. }
  204. // overwrite name attribute of response object from page:
  205. if (rai[rid][0].innerText == "HITs") {
  206. resp[rid].name = rai[rid][3].innerText;
  207. } else {
  208. resp[rid].name = rai[rid][0].innerHTML;
  209. }
  210. }
  211. } // .split("</span>")[0].split(">")[1]; /* deprecated due to MTurk interface change */
  212. return resp; }
  213.  
  214. function insertDropDowns(rai, resp) {
  215. for(var rid in rai) {
  216. if (rai.hasOwnProperty(rid)) {
  217. for(var i = 0; i < rai[rid].length; i++) {
  218. var td = rai[rid][i].parentNode;
  219. if (td.parentNode.parentNode.childNodes.length > 5) {
  220. if (td.parentNode.parentNode.childNodes[5].childNodes[0].childNodes[0])
  221. var check = td.parentNode.parentNode.childNodes[5].childNodes[0].childNodes[0].getAttribute("href");
  222. if (check != null) {
  223. var hitid = td.parentNode.parentNode.childNodes[5].childNodes[0].childNodes[0].getAttribute("href").match(/\/([A-Z0-9]+)\//)[1];
  224. } else {
  225. var hitid = "";
  226. }
  227. } else {
  228. var hitid = "";
  229. }
  230. if (td.parentNode.parentNode.childNodes[1]) {
  231. var hitname = td.parentNode.parentNode.childNodes[1].getAttribute("title");
  232. }
  233. td.innerHTML = dropDown(resp[rid], rid, hitid, hitname) + " " + td.innerHTML;
  234. }
  235. }
  236. }
  237. }
  238.  
  239. insertInlineCss();
  240. var a = document.getElementsByTagName('a');
  241. var reqAnchors = getRequesterAnchorsAndIds(a);
  242. if (reqAnchors) {
  243. var url = buildXhrUrl(reqAnchors);
  244. var resp = makeXhrQuery(url);
  245. resp = getNames(reqAnchors, resp);
  246. insertDropDowns(reqAnchors, resp);
  247. }