CH Plaintext HIT Export

Export HIT information in multi-line plain text format.

目前为 2015-10-05 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name CH Plaintext HIT Export
  3. // @description Export HIT information in multi-line plain text format.
  4. // @version 1.7.1c
  5. // @include https://www.mturk.com/mturk/findhits*
  6. // @include https://www.mturk.com/mturk/viewhits*
  7. // @include https://www.mturk.com/mturk/sorthits*
  8. // @include https://www.mturk.com/mturk/searchbar*selectedSearchType=hitgroups*
  9. // @include https://www.mturk.com/mturk/viewsearchbar*selectedSearchType=hitgroups*
  10. // @include https://www.mturk.com/mturk/sortsearchbar*HITGroup*
  11. // @include https://www.mturk.com/mturk/preview*
  12. // @exclude https://www.mturk.com/mturk/findhits?*hit_scraper*
  13. // @grant GM_setClipboard
  14. // @author Cristo + clickhappier
  15. // @namespace mturkgrind
  16. // ==/UserScript==
  17.  
  18.  
  19. // based on 'IRC Export (reformatted output mod)': https://greasyfork.org/en/scripts/6254-irc-export-reformatted-output-mod
  20.  
  21.  
  22. var accountStatus = "loggedOut";
  23. if ( !document.getElementById("lnkWorkerSignin") ) // if sign-in link not present
  24. {
  25. accountStatus = "loggedIn";
  26. }
  27.  
  28.  
  29. function getUrlVariable(url, variable)
  30. {
  31. var query = url.split('?');
  32. var vars = query[1].split("&");
  33. for ( var i=0; i<vars.length; i++ )
  34. {
  35. var pair = vars[i].split("=");
  36. if ( pair[0] == variable )
  37. { return pair[1]; }
  38. }
  39. return(false);
  40. }
  41.  
  42.  
  43. var caps = document.getElementsByClassName('capsulelink');
  44. for (var c = 0; c < caps.length/2; c++){
  45. var button = document.createElement('button');
  46. button.setAttribute("place",c);
  47. button.textContent = 'TXT';
  48. button.style.height = '14px';
  49. button.style.width = '30px';
  50. button.style.fontSize = '8px';
  51. button.style.border = '1px solid';
  52. button.style.padding = '0px';
  53. button.style.backgroundColor = 'transparent';
  54. button.title = 'Click to save HIT information to your clipboard in multi-line plaintext format.';
  55. button.addEventListener("click", display, false);
  56. document.getElementById('capsule'+c+'-0').parentNode.appendChild(button);
  57. }
  58.  
  59. function getTO(f){
  60. var toComp = [];
  61. var toUrl = 'https://mturk-api.istrack.in/multi-attrs.php?ids='+f;
  62. var toUrl2 = 'https://turkopticon.ucsd.edu/api/multi-attrs.php?ids='+f;
  63. var requestTO = new XMLHttpRequest();
  64. try{ // first try main TO server
  65. requestTO.onreadystatechange = function () {
  66. if ((requestTO.readyState ===4) && (requestTO.status ===200)) {
  67. if (requestTO.responseText.split(':').length > 2) {
  68. var toInfo = requestTO.responseText.split('{')[3].split('}')[0].split(',');
  69. for (var t = 0; t < 4; t++) {
  70. var arrTo = toInfo[t].split(':');
  71. toComp.push(arrTo[1].substring(1,4));
  72. }
  73. }
  74. else { toComp = ['-','-','-','-']; }
  75. }
  76. };
  77. requestTO.open('GET', toUrl2, false);
  78. requestTO.send(null);
  79. return toComp;
  80. }
  81. catch(err){ // if main TO server unavailable, try Miku's TO mirror server (istrack.in)
  82. try{
  83. requestTO.onreadystatechange = function () {
  84. if ((requestTO.readyState ===4) && (requestTO.status ===200)) {
  85. if (requestTO.responseText.split(':').length > 2) {
  86. var toInfo = requestTO.responseText.split('{')[3].split('}')[0].split(',');
  87. for (var t = 0; t < 4; t++) {
  88. var arrTo = toInfo[t].split(':');
  89. toComp.push(arrTo[1].substring(1,4));
  90. }
  91. }
  92. else { toComp = ['-','-','-','-']; }
  93. }
  94. };
  95. requestTO.open('GET', toUrl, false);
  96. requestTO.send(null);
  97. return toComp;
  98. }
  99. catch(err){ // if both unavailable, return 'na's
  100. toComp = ['na','na','na','na'];
  101. return toComp;
  102. }
  103. }
  104. }
  105.  
  106. // output display box
  107. var txtexportdiv = document.createElement('div');
  108. var txtexporttextarea = document.createElement('textarea');
  109. txtexportdiv.style.position = 'fixed';
  110. txtexportdiv.style.width = '500px';
  111. txtexportdiv.style.height = '255px';
  112. txtexportdiv.style.left = '50%';
  113. txtexportdiv.style.right = '50%';
  114. txtexportdiv.style.margin = '-250px 0px 0px -250px';
  115. txtexportdiv.style.top = '300px';
  116. txtexportdiv.style.padding = '5px';
  117. txtexportdiv.style.border = '2px';
  118. txtexportdiv.style.backgroundColor = 'black';
  119. txtexportdiv.style.color = 'white';
  120. txtexportdiv.style.zIndex = '100';
  121. txtexportdiv.setAttribute('id','txtexport_div');
  122. txtexportdiv.style.display = 'none';
  123. txtexporttextarea.style.padding = '2px';
  124. txtexporttextarea.style.width = '500px';
  125. txtexporttextarea.style.height = '230px';
  126. txtexporttextarea.title = 'Plaintext Export Output';
  127. txtexporttextarea.setAttribute('id','txtexport_text');
  128. txtexportdiv.textContent = 'Plaintext Export: Press Ctrl+C to (re-)copy to clipboard. Click textarea to close.';
  129. txtexportdiv.style.fontSize = '12px';
  130. txtexportdiv.appendChild(txtexporttextarea);
  131. document.body.insertBefore(txtexportdiv, document.body.firstChild);
  132. txtexporttextarea.addEventListener("click", function(){ txtexportdiv.style.display = 'none'; }, false);
  133.  
  134.  
  135. function display(e){
  136. var theButton = e.target;
  137. theButton.style.backgroundColor = "#CC0000";
  138. var capHand = document.getElementById('capsule' + theButton.getAttribute("place") + '-0');
  139. var tBodies = capHand.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode;
  140. var thisReq = tBodies.getElementsByClassName('requesterIdentity')[0];
  141. var thisReqName = thisReq.textContent;
  142. var thisReqId = "unavailable"; // handle logged-out export requests now that requester ID links are unavailable as of 2015-07-20
  143. if ( accountStatus == "loggedIn" )
  144. {
  145. thisReqId = getUrlVariable(thisReq.parentNode.href, "requesterId");
  146. }
  147. var thisTitle = capHand.textContent.trim();
  148. thisTitle = thisTitle.replace(/<(\w+)[^>]*>.*<\/\1>/gi, "").trim(); // addition to strip html tags and their contents, appearing inside the title link (re 10-20-2014 appearance of "<span class="tags"></span>")
  149.  
  150. var thisHitGroup = "unavailable"; // handle logged-out export requests for HITs with no preview/notqualified links
  151. // if hit has a preview or notqualified link
  152. var thisHitLink = capHand.parentNode.parentNode.getElementsByClassName('capsulelink')[1].firstChild.nextSibling;
  153. if ( thisHitLink.href !== '' )
  154. {
  155. // if this is a preview link
  156. if ( thisHitLink.href.indexOf('preview') > -1 )
  157. {
  158. thisHitGroup = getUrlVariable(thisHitLink.href, "groupId");
  159. }
  160. // if this is a notqualified link
  161. else if ( thisHitLink.href.indexOf('notqualified') > -1 )
  162. {
  163. thisHitGroup = getUrlVariable(thisHitLink.href, "hitId");
  164. // Amazon messed up the notqualified links, now looking like https://www.mturk.com/mturk/notqualified?hitGroupId=3ID43DSF4IQ1X8LO308D15ZSD5J5GX&hitId=3ID43DSF4IQ1X8LO308D15ZSD5J5GX ; and then they flipped the order of these values on 6/2/15
  165. }
  166. // if this is a requestqualification link we shouldn't be on, but are anyway because of stuff Amazon screwed with on 6/2/15
  167. else if ( thisHitLink.href.indexOf('requestqualification') > -1 )
  168. {
  169. // go to the next link, the "(why?)" notqualified link instead
  170. thisHitGroup = getUrlVariable(thisHitLink.nextElementSibling.href, "hitId");
  171. // Amazon messed up the notqualified links, now looking like https://www.mturk.com/mturk/notqualified?hitGroupId=3ID43DSF4IQ1X8LO308D15ZSD5J5GX&hitId=3ID43DSF4IQ1X8LO308D15ZSD5J5GX ; and then they flipped the order of these values on 6/2/15
  172. }
  173. }
  174. var thisReward = tBodies.getElementsByClassName('reward')[0].textContent.trim();
  175.  
  176. var thisTimeLimit = tBodies.getElementsByClassName('capsule_field_text')[2].textContent.trim();
  177. var thisHitsAvail = "??"; // handle Amazon removing HITs Available data from logged-out view 2015-07-20
  178. if ( accountStatus == "loggedIn" )
  179. {
  180. thisHitsAvail = tBodies.getElementsByClassName('capsule_field_text')[4].textContent.trim();
  181. }
  182. var thisQualTable = document.getElementById('capsule'+theButton.getAttribute("place")+'target').getElementsByTagName('tbody')[2];
  183. if ( document.location.href.indexOf('?last_hits_previewed') > -1 ) // for compatibility with mmmturkeybacon Last Hits Previewed
  184. {
  185. thisQualTable = document.getElementById('capsule'+theButton.getAttribute("place")+'target').getElementsByTagName('tbody')[1];
  186. }
  187. var thisQualRows = thisQualTable.getElementsByTagName('td');
  188. var qualStart = 3; // standard starting row
  189. if ( accountStatus == "loggedOut" ) // handle logged-out export requests - difference in qual table coding
  190. {
  191. qualStart = 1;
  192. }
  193. if ( document.location.href.indexOf('?last_hits_previewed') > -1 ) // for compatibility with mmmturkeybacon Last Hits Previewed
  194. {
  195. qualStart = 2;
  196. }
  197. var masterQual = '';
  198. for ( var m = qualStart; m < thisQualRows.length; m++ )
  199. {
  200. if ( thisQualRows[m].textContent.indexOf('Masters') > -1 )
  201. {
  202. masterQual = 'MASTERS ';
  203. }
  204. }
  205. var thisPreviewUrl = "(url n/a)";
  206. var thisPandaUrl = "(url n/a)";
  207. if ( thisHitGroup != "unavailable" ) // handle logged-out export requests for HITs with no preview/notqualified links
  208. {
  209. thisPreviewUrl = 'https://www.mturk.com/mturk/preview?groupId=' + thisHitGroup;
  210. thisPandaUrl = 'https://www.mturk.com/mturk/previewandaccept?groupId=' + thisHitGroup;
  211. }
  212. var thisReqUrl = "(url n/a)";
  213. if ( thisReqId != "unavailable" )
  214. {
  215. thisReqUrl = 'https://www.mturk.com/mturk/searchbar?selectedSearchType=hitgroups&requesterId=' + thisReqId;
  216. }
  217. else if ( thisReqId == "unavailable" ) // handle 2015-07-20 loss of logged-out requester ids
  218. {
  219. thisReqUrl = 'https://www.mturk.com/mturk/searchbar?selectedSearchType=hitgroups&searchWords=' + thisReqName.replace(/ /g, "+") + " (search)";
  220. }
  221.  
  222. var thisTOUrl = "(url n/a)";
  223. var thisTOStats = "??";
  224. if ( thisReqId != "unavailable" )
  225. {
  226. thisTOUrl = 'http://turkopticon.ucsd.edu/' + thisReqId;
  227. thisTOStats = getTO(thisReqId);
  228. }
  229. else if ( thisReqId == "unavailable" ) // handle 2015-07-20 loss of logged-out requester ids
  230. {
  231. thisTOUrl = 'https://turkopticon.ucsd.edu/main/php_search?query=' + thisReqName.replace(/ /g, "+") + " (search)";
  232. }
  233.  
  234.  
  235. // additions for plaintext export:
  236. function DST() { // check if daylight savings time should be adjusted for, from http://www.mresoftware.com/simpleDST.htm
  237. var today = new Date();
  238. var yr = today.getFullYear();
  239. var dst_start = new Date("March 14, "+yr+" 02:00:00"); // 2nd Sunday in March can't occur after the 14th
  240. var dst_end = new Date("November 07, "+yr+" 02:00:00"); // 1st Sunday in November can't occur after the 7th
  241. var day = dst_start.getDay(); // day of week of 14th
  242. dst_start.setDate(14-day); // Calculate 2nd Sunday in March of this year
  243. day = dst_end.getDay(); // day of the week of 7th
  244. dst_end.setDate(7-day); // Calculate first Sunday in November of this year
  245. if (today >= dst_start && today < dst_end) { //does today fall inside of DST period?
  246. return true; //if so then return true
  247. }
  248. return false; //if not then return false
  249. }
  250.  
  251. var currentDate = new Date();
  252. var utc = currentDate.getTime() + (currentDate.getTimezoneOffset() * 60000); // http://www.techrepublic.com/article/convert-the-local-time-to-another-time-zone-with-this-javascript/
  253. var offset = '';
  254. if ( DST() == true ) { offset = "-7"; } else { offset = "-8"; } // adjust Pacific Time's UTC offset for daylight savings time - http://stackoverflow.com/questions/8207655/how-to-get-time-of-specific-timezone-using-javascript/8207708#8207708
  255. var amazonDate = new Date(utc + (3600000*offset));
  256. var month = amazonDate.getMonth() + 1;
  257. var day = amazonDate.getDate();
  258. var year = amazonDate.getFullYear();
  259. var hours = amazonDate.getHours();
  260. if (hours < 10) { hours = '0' + hours; } // http://stackoverflow.com/questions/6838197/get-local-date-string-and-time-string/6838658#6838658
  261. var minutes = amazonDate.getMinutes();
  262. if (minutes < 10) { minutes = '0' + minutes; }
  263. var dateStr = month + "/" + day + "/" + year + " " + hours + ":" + minutes + " PT";
  264. var thisDesc = '"' + tBodies.getElementsByClassName('capsule_field_text')[5].textContent.trim().replace(/(\t)+/g,' ').replace(/(\n)+/g,' ').replace(/(\r)+/g,' ').replace(/( )+/g,' ').replace(/(\s)+/g,' ') + '"';
  265. if (thisDesc == '""') { thisDesc = "none"; }
  266.  
  267. var thisKeywords = "";
  268. if ( document.location.href.indexOf('?last_hits_previewed') > -1 )
  269. {
  270. thisKeywords = "unavailable (exported from Last HITs Previewed)";
  271. }
  272. else
  273. {
  274. thisKeywords = '"' + tBodies.getElementsByClassName('capsule_field_text')[6].textContent.trim().replace(/(\t)+/g,' ').replace(/(\n)+/g,' ').replace(/(\r)+/g,' ').replace(/( )+/g,' ').replace(/(\s)+/g,' ') + '"';
  275. }
  276. if (thisKeywords == '""' || thisKeywords == '')
  277. {
  278. thisKeywords = "none";
  279. }
  280.  
  281. var qualStr = "";
  282. for ( var q = qualStart; q < thisQualRows.length; q++ ) {
  283. if ( ( (thisQualRows[q].textContent.indexOf('is') > -1) || (thisQualRows[q].textContent.indexOf('has') > -1) ) && (thisQualRows[q].textContent.indexOf('You meet this') < 0) && (thisQualRows[q].textContent.indexOf('Contact the Requester') < 0) ) {
  284. if (qualStr != "") { qualStr += ' '; }
  285. qualStr += thisQualRows[q].textContent.trim().replace(/(\t)+/g,' ').replace(/(\n)+/g,' ').replace(/(\r)+/g,' ').replace(/( )+/g,' ').replace(/(\s)+/g,' ') + ' \r\n';
  286. }
  287. }
  288. if (qualStr == "") { qualStr = "none \r\n"; }
  289.  
  290. var exportOutput = "";
  291. var loggedOutApology = " (Info missing since logged out.)";
  292. if ( accountStatus == "loggedIn" )
  293. {
  294. exportOutput = dateStr + ' \r\n'
  295. + masterQual + 'HIT: ' + thisTitle + ' - ' + thisPreviewUrl + ' \r\n'
  296. + 'Requester: ' + thisReqName + ' - ' + thisReqUrl + ' \r\n'
  297. + 'TO Ratings: ' + 'Pay='+thisTOStats[1] + ' Fair='+thisTOStats[2] + ' Comm='+thisTOStats[0] + ' Speed='+thisTOStats[3] + ' - ' + thisTOUrl + ' \r\n'
  298. + 'Time Allotted: ' + thisTimeLimit + ' \r\n'
  299. + 'Reward: ' + thisReward + ' \r\n'
  300. + 'HITs Available: ' + thisHitsAvail + ' \r\n'
  301. + 'Description: ' + thisDesc + ' \r\n'
  302. + 'Keywords: ' + thisKeywords + ' \r\n'
  303. + 'Qualifications: ' + qualStr + ' \r\n' ;
  304. }
  305. else if ( accountStatus == "loggedOut" )
  306. {
  307. exportOutput = dateStr + loggedOutApology + ' \r\n'
  308. + masterQual + 'HIT: ' + thisTitle + ' - ' + thisPreviewUrl + ' \r\n'
  309. + 'Requester: ' + thisReqName + ' - ' + thisReqUrl + ' \r\n'
  310. + 'TO Ratings: ??' + ' - ' + thisTOUrl + ' \r\n'
  311. + 'Time Allotted: ' + thisTimeLimit + ' \r\n'
  312. + 'Reward: ' + thisReward + ' \r\n'
  313. + 'HITs Available: ' + thisHitsAvail + ' \r\n'
  314. + 'Description: ' + thisDesc + ' \r\n'
  315. + 'Keywords: ' + thisKeywords + ' \r\n'
  316. + 'Qualifications: ' + qualStr + ' \r\n' ;
  317. }
  318.  
  319. if (GM_setClipboard) { GM_setClipboard(exportOutput); }
  320. window.setTimeout(function(){ theButton.style.backgroundColor = 'transparent'; }, 500);
  321. txtexporttextarea.textContent = exportOutput;
  322. txtexportdiv.style.display = 'block';
  323. txtexporttextarea.select();
  324. }