Mturk Hourly

Record time spent working on HITs.

目前为 2017-07-10 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name Mturk Hourly
  3. // @author @Kerek
  4. // @description Record time spent working on HITs.
  5. // @match https://www.mturk.com/mturk/accept*
  6. // @match https://www.mturk.com/mturk/preview*
  7. // @match https://www.mturk.com/mturk/continue*
  8. // @match https://www.mturk.com/mturk/submit
  9. // @match https://www.mturk.com/mturk/return*
  10. // @match https://www.mturk.com/mturk/statusdetail*
  11. // @match https://www.mturk.com/mturk/dashboard
  12. // @require http://code.jquery.com/jquery-latest.min.js
  13. // @version 1.0
  14. // @grant none
  15. // @namespace https://greasyfork.org/users/11205
  16. // ==/UserScript==
  17.  
  18. if (window.location.href == "https://www.mturk.com/mturk/dashboard" && $("#total_earnings_amount").length){
  19. Todays_Projected_Earnings();
  20. }
  21. else{
  22. var hit_returned = false;
  23. if(typeof(Storage)!=="undefined")
  24. {
  25. $('img[src="/images/return_hit.gif"]').parent().click(function(){
  26. hit_returned = true;
  27. });
  28. store_data('open');
  29. window.addEventListener('beforeunload', function(){store_data('close');});
  30.  
  31. var $requesters = $('td[class="statusdetailRequesterColumnValue"]');
  32. if ($requesters.length > 0)
  33. {
  34. $requesters.each(function(){
  35. var hitId = $(this).find('a[href^="/mturk/contact?"]').attr('href').match(/[A-Z0-9]{30}/);
  36. var $feedback_value = $(this).parent().find('td[class="statusdetailStatusColumnValue"]').next();
  37. var feedback_str = create_feedback_str(hitId);
  38. var status_value = $feedback_value.html() + feedback_str;
  39. $feedback_value.html(status_value);
  40. });
  41. }
  42. }
  43. }
  44.  
  45.  
  46.  
  47. function Todays_Projected_Earnings(){
  48. var TPEhitLOG = {}; var TPEdetailsLOG = {}; var TPEhourlyLOG = {}; var pe = 0;
  49.  
  50. var today = $("a[href^='/mturk/statusdetail?encodedDate']:contains(Today)").eq(0).prop("href");
  51.  
  52. var $peTR = $('<div id="TPE_div" class="even" style="display:table-row">');
  53. var $peTD1 = $('<td class="metrics-table-first-value">');
  54. var $peTD2 = $('<td>');
  55. var $peA = $('<a href="javascript:void(0)">Today\'s Projected Earnings</a>');
  56. var $TPE_details = $('<span style="color: blue; font-size: 10px; cursor: pointer; float: right;">Details<img style="margin-left: 5px;" src="/media/more.gif" border="0/"></span>');
  57. var $peSPAN = $('<span class="reward">$0.00</span>');
  58. $("td.metrics-table-first-value:contains(Total Earnings)").parent().after($peTR);
  59. $peTR.append($peTD1.append($peA,$TPE_details),$peTD2.append($peSPAN));
  60.  
  61. var $TPED_table = $('<table style="display: none;" width="760" align="center" cellspacing="0" cellpadding="0">');
  62. var $TPED_tboday = $('<tbody>');
  63. var $TPED_tr_1 = $('<tr id="TPE_tr" height="25px"><td width="10" bgcolor="#7fb4cf" style="padding-left: 10px;"></td><td width="100%" bgcolor="#7fb4cf" class="white_text_14_bold">Today\'s Projected Earnings Details&nbsp;&nbsp;<a id="fourmEXPORT" href="javascript:void(0)" class="whatis" >(Forum Export)</a></td><td width="10" align="right" bgcolor="#7fb4cf"></td></tr>');
  64. var $TPED_tr_2 = $('<tr><td class="container-content" colspan="3"><table class="metrics-table" width="100%"><tbody><tr><td width="100%"><table class="metrics-table" width="100%"><tbody id="tbody2"></tbody></table></td></tr></tbody></table></td></tr>');
  65. var $TPED_tr_h = $('<tr class="metrics-table-header-row"><th class="metrics-table-first-header">Requester</th><th>Submitted</th><th>Projected</th><th>Hourly</th></tr>');
  66.  
  67. $("#subtabs_and_searchbar").next().next().after($TPED_table);
  68. $TPED_table.append($TPED_tboday);
  69. $TPED_tboday.append($TPED_tr_1,$TPED_tr_2);
  70. $("#tbody2").append($TPED_tr_h);
  71.  
  72. $("#fourmEXPORT").click(function(){
  73. var exportcode = "";
  74. var bonus = $("#bonus").text();
  75. if (bonus !== "$0.00"){
  76. var total = (Number(pe)+Number(bonus.replace(/[^0-9.]/g, ""))).toFixed(2);
  77. exportcode += "[b]Today's Projected Earnings: $"+Number(pe).toFixed(2)+" + Bonuses: "+bonus+" = $"+total+"[/b]\n";
  78.  
  79. }
  80. else {
  81. exportcode += "[b]Today's Projected Earnings: $"+Number(pe).toFixed(2)+"[/b]\n";
  82. }
  83. exportcode += "[spoiler=Today's Projected Earnings Full Details][table][tr][th][b]Requester[/b][/th][th][b]Submitted[/b][/th][th][b]Projected[/b][/th][/tr]";
  84.  
  85. var x_sorted = Object.keys(TPEdetailsLOG).sort(function(a,b){return TPEdetailsLOG[a].reward - TPEdetailsLOG[b].reward;});
  86. for (var j = x_sorted.length-1; j > -1; j--){
  87. var xkey = x_sorted[j];
  88. var x_req = TPEdetailsLOG[xkey].req;
  89. var x_reqid = TPEdetailsLOG[xkey].reqid;
  90. var x_submitted = TPEdetailsLOG[xkey].submit;
  91. var x_reward = Number(TPEdetailsLOG[xkey].reward).toFixed(2);
  92. if (x_req === "Bonuses"){
  93. if (x_reward !== "0.00"){
  94. exportcode += "[tr][td]"+x_req+"[/td][td]"+x_submitted+"[/td][td]$"+x_reward+"[/td][/tr]\n";
  95. }
  96. }
  97. else {
  98. exportcode += "[tr][td][url=https://www.mturk.com/mturk/searchbar?selectedSearchType=hitgroups&requesterId="+x_reqid+"]"+x_req+"[/url][/td][td]"+x_submitted+"[/td][td]$"+x_reward+"[/td][/tr]\n";
  99. }
  100. }
  101. exportcode += "[/table][/spoiler]";
  102.  
  103. GM_setClipboard(exportcode);
  104. alert("Forum Export copied to your clipboard.");
  105. });
  106.  
  107. $peA.click(function(){
  108. if ($peA.text() === "Today's Projected Earnings"){
  109. var confirmation = confirm("Are you sure you want to recalculate Today's Projected Earnings?");
  110. if (confirmation === true){
  111. TPEhitLOG = {}; TPEdetailsLOG = {}; TPEhourlyLOG = {}; pe = 0;
  112. $("#tbody2").find("tr.odd, tr.even").remove();
  113. getDATA(today);
  114. $peSPAN.text("$0.00");
  115. }
  116. }
  117. });
  118.  
  119. $TPE_details.click(function(){
  120. $TPE_details.find("img").attr("src", ($TPE_details.find("img").attr("src") === "/media/more.gif") ? "/media/less.gif" : "/media/more.gif");
  121. $TPED_table.toggle();
  122. });
  123.  
  124. if (today){
  125. var date = today.split("encodedDate=")[1];
  126. if (date === localStorage.TPE_date){
  127. if (localStorage.TPEhitLOG){
  128. TPEhitLOG = JSON.parse(localStorage.TPEhitLOG);
  129. }
  130. pe = Number(localStorage.TPE_pe) || 0;
  131. $peSPAN.text("$"+Number(pe).toFixed(2));
  132. getDATA(localStorage.TPE_lastpage);
  133. }
  134. else {
  135. localStorage.TPE_date = date;
  136. localStorage.Goal_progress = 0;
  137. TPEhitLOG = {}; pe = 0;
  138. $peSPAN.text("$0.00");
  139. getDATA(today);
  140. }
  141. }
  142.  
  143. function getDATA(URL){
  144. var page = URL.match(/Number=([0-9]*)/g);
  145. if (page){
  146. $peA.text("Calculating Page "+page.toString().replace(/[^0-9.]/g, ""));
  147. }
  148. else {
  149. localStorage.removeItem("TPEhitLOG");
  150. localStorage.Goal_progress = 0;
  151. $peA.text("Calculating Page 1");
  152. }
  153.  
  154. $.get(URL, function(data){
  155. var $data = $(data);
  156. var $hits = $data.find("#dailyActivityTable").find("tr[valign='top']");
  157. var pagereqerr = $data.find("td.error_title:contains(You have exceeded the maximum allowed page request rate for this website.)").length;
  158. var noactivity = $data.find("#dailyActivityTable").find("td:contains(You have no HIT activity on this day matching the selected status.)").length;
  159. if ($hits.length){
  160. console.log("hit length");
  161. var url = $data.find("a:contains(Next)").eq(0).prop("href");
  162. for (var i = 0; i < $hits.length; i++){
  163. var req = $hits.eq(i).find("td.statusdetailRequesterColumnValue").text().trim();
  164. var title = $hits.eq(i).find("td.statusdetailTitleColumnValue").text().trim();
  165. var reward = $hits.eq(i).find("td.statusdetailAmountColumnValue").text().trim();
  166. var status = $hits.eq(i).find("td.statusdetailStatusColumnValue").text().trim();
  167. var reqid = $hits.eq(i).find("a").prop("href").split("requesterId=")[1].split("&")[0];
  168. var hitid = $hits.eq(i).find("a").prop("href").split("HIT+")[1];
  169.  
  170. if (!TPEhitLOG[hitid]){
  171. TPEhitLOG[hitid] = {
  172. req : req,
  173. title : title,
  174. reward : reward,
  175. status : status,
  176. reqid : reqid,
  177. hitid : hitid
  178. };
  179. }
  180. }
  181. if (url){
  182. getDATA(url);
  183. }
  184. else {
  185. pe = 0;
  186. for(var key in TPEhitLOG){
  187. if (TPEhitLOG[key].status !== "Rejected"){
  188. pe += parseFloat(TPEhitLOG[key].reward.replace(/[^0-9.]/g, ""));
  189. }
  190. if (!TPEdetailsLOG[TPEhitLOG[key].reqid]){
  191. TPEdetailsLOG[TPEhitLOG[key].reqid] = {
  192. req : TPEhitLOG[key].req,
  193. submit : 1,
  194. reward : parseFloat(TPEhitLOG[key].reward.replace(/[^0-9.]/g, "")),
  195. reqid : TPEhitLOG[key].reqid
  196. };
  197. }
  198. else {
  199. TPEdetailsLOG[TPEhitLOG[key].reqid].submit = TPEdetailsLOG[TPEhitLOG[key].reqid].submit + 1;
  200. TPEdetailsLOG[TPEhitLOG[key].reqid].reward = TPEdetailsLOG[TPEhitLOG[key].reqid].reward + parseFloat(TPEhitLOG[key].reward.replace(/[^0-9.]/g, ""));
  201. }
  202. if (!TPEhourlyLOG[TPEhitLOG[key].reqid]){
  203. var time_data = localStorage.getItem('time_data.' + TPEhitLOG[key].hitid.split('&')[0]);
  204. if (time_data != null){
  205. var starts = time_data.split("$#$")[3].split('?');
  206. var last_start = starts[starts.length-1];
  207.  
  208. var stops = time_data.split("$#$")[4].split('?');
  209. var last_stop = stops[stops.length-1];
  210.  
  211. if (last_start.length && last_stop.length){
  212. TPEhourlyLOG[TPEhitLOG[key].reqid] = {
  213. req : TPEhitLOG[key].req,
  214. intervals :[[last_start,last_stop]],
  215. totalReward : parseFloat(time_data.split("$#$")[2]),
  216. };
  217. }
  218.  
  219. }
  220. }
  221. else{
  222. var time_data = localStorage.getItem('time_data.' + TPEhitLOG[key].hitid.split('&')[0]);
  223. if (time_data != null){
  224.  
  225. var starts = time_data.split("$#$")[3].split('?');
  226. var last_start = starts[starts.length-1];
  227. var stops = time_data.split("$#$")[4].split('?');
  228. var last_stop = stops[stops.length-1];
  229. if (last_start.length && last_stop.length){
  230.  
  231. //console.log(TPEhourlyLOG[TPEhitLOG[key].reqid].intervals);
  232. TPEhourlyLOG[TPEhitLOG[key].reqid].intervals.push([last_start,last_stop]);
  233. }
  234. // console.log(TPEhourlyLOG[TPEhitLOG[key].reqid].intervals);
  235. TPEhourlyLOG[TPEhitLOG[key].reqid].totalReward += parseFloat(time_data.split("$#$")[2]);
  236. }
  237. }
  238. }
  239.  
  240. if (!TPEdetailsLOG.bonuses && $("#bonus").length){
  241. TPEdetailsLOG.bonuses = {
  242. req : "Bonuses",
  243. submit : "N/A",
  244. reward : parseFloat($("#bonus").text().replace(/[^0-9.]/g, "")),
  245. reqid : "N/A"
  246. };
  247. }
  248. else if ($("#bonus").length){
  249. TPEdetailsLOG.bonuses.reward = parseFloat($("#bonus").text().replace(/[^0-9.]/g, ""));
  250. }
  251.  
  252. var d_sorted = Object.keys(TPEdetailsLOG).sort(function(a,b){return TPEdetailsLOG[a].reward - TPEdetailsLOG[b].reward;});
  253. var oddeven = true;
  254. for (var j = d_sorted.length-1; j > -1; j--){
  255. var dkey = d_sorted[j];
  256. var d_req = TPEdetailsLOG[dkey].req;
  257. var d_submitted = TPEdetailsLOG[dkey].submit;
  258. var d_reward = Number(TPEdetailsLOG[dkey].reward).toFixed(2);
  259. var d_hourly = "N/A";
  260. if (d_req !== "Bonuses" && TPEhourlyLOG[TPEdetailsLOG[dkey].reqid]){
  261. var intervals = TPEhourlyLOG[TPEdetailsLOG[dkey].reqid].intervals;
  262. var d_intervals_sum = 0;
  263. for (i=0;i<intervals.length;i++){
  264. d_intervals_sum += (intervals[i][1]-intervals[i][0]);
  265. }
  266. var d_intervals_avg = d_intervals_sum / intervals.length;
  267. var d_intervals = mergeIntervals(intervals);
  268. d_intervals = combineIntervals(intervals,Math.max(2*60*1000,Math.min(10*d_intervals_avg,15*60*1000)));
  269. console.log(d_req, "padding", Math.min(10*d_intervals_avg,15*60*1000));
  270. var d_time = 0;
  271. for (i=0;i<d_intervals.length; i++){
  272. var s = new Date(parseInt(d_intervals[i][0]));
  273. var f = new Date(parseInt(d_intervals[i][1]));
  274. console.log(d_req, i, s.toLocaleTimeString(),f.toLocaleTimeString());
  275. d_time += (d_intervals[i][1] - d_intervals[i][0])/(1000*60*60);
  276. }
  277. d_hourly = "$" + (TPEhourlyLOG[TPEdetailsLOG[dkey].reqid].totalReward / d_time).toFixed(2);
  278. }
  279. if (oddeven){
  280. oddeven = false;
  281. $("#tbody2").append('<tr class="odd"><td class="metrics-table-first-value">'+d_req+'</td><td>'+d_submitted+'</td><td><span class="reward">$'+d_reward+'</span></td><td><span class="reward">'+d_hourly+'</span></tr>');
  282. }
  283. else {
  284. oddeven = true;
  285. $("#tbody2").append('<tr class="even"><td class="metrics-table-first-value">'+d_req+'</td><td>'+d_submitted+'</td><td><span class="reward">$'+d_reward+'</span><td><span class="reward">'+d_hourly+'</span></td></tr>');
  286. }
  287. }
  288. localStorage.TPEhitLOG = JSON.stringify(TPEhitLOG);
  289. localStorage.TPE_lastpage = URL;
  290. localStorage.TPE_pe = pe;
  291. $peA.text("Today's Projected Earnings");
  292. $peSPAN.text("$"+Number(pe).toFixed(2));
  293. $('#today_total').text("$"+(Number($('#bonus').text().replace('$','')) +Number($('#TPE_div span.reward').text().replace('$',''))).toFixed(2));
  294. document.title = $('#today_total').text();
  295. localStorage.Goal_percent = ((Number(localStorage.TPE_pe)/Number(localStorage.Goal_goal))*100);
  296. localStorage.Goal_progress = Number(pe)-Number(localStorage.Goal_goal);
  297. if ($("#goalDIV").length){
  298. $("#progress").width(Number(localStorage.Goal_percent)+"%");
  299. $("#progressper").text(Number(localStorage.Goal_progress).toFixed(2));
  300. }
  301. Unsynced();
  302. }
  303. }
  304. else if (noactivity){
  305. console.log("no activity");
  306. localStorage.TPE_lastpage = URL;
  307. localStorage.TPE_pe = 0;
  308. localStorage.Goal_progress = 0;
  309. $peA.text("Today's Projected Earnings");
  310. $peSPAN.text("$0.00");
  311. $('#today_total').text("$"+(Number($('#bonus').text().replace('$','')) +Number($('#TPE_div span.reward').text().replace('$',''))).toFixed(2));
  312.  
  313. }
  314. else if (pagereqerr) {
  315. console.log("set timeout");
  316. setTimeout(function(){ getDATA(URL); }, 2000);
  317. }
  318. });
  319. }
  320.  
  321. function Unsynced(){
  322. var hitscalced = Object.keys(TPEhitLOG).length;
  323. var submitted = Number($("a[href^='/mturk/statusdetail?encodedDate']:contains(Today)").eq(0).parent().next().text());
  324.  
  325. if (hitscalced < submitted){
  326. $peSPAN.css({backgroundColor:"red"});
  327. }
  328. else {
  329. $peSPAN.css({backgroundColor:""});
  330. }
  331. }
  332. }
  333.  
  334.  
  335. function create_feedback_str(hitId)
  336. {
  337. var time_str = '';
  338. var time_data = localStorage.getItem('time_data.' + hitId);
  339. if (time_data === null)
  340. {
  341. return time_str;
  342. }
  343. var last_start = time_data.split("$#$")[3].split('?');
  344. last_start = new Date(parseInt(last_start[last_start.length - 1]));
  345. var last_finish = time_data.split("$#$")[4].split('?');
  346. last_finish = new Date(parseInt(last_finish[last_finish.length - 1]));
  347. console.log(time_data, last_start, last_finish, time_data.split("$#$")[3].split('?'), time_data.split("$#$")[4].split('?'));
  348. var reward = time_data.split("$#$")[2];
  349. var time_spent = last_finish - last_start;
  350. var h = Math.floor(time_spent/(1000*60*60));
  351. var m = Math.floor((time_spent - h*1000*60*60)/(1000*60));
  352. var s = Math.floor((time_spent - h*1000*60*60 - m*1000*60)/(1000));
  353. // return time_data;
  354. // return time_data.split("$#$")[0] + " - " + time_data.split("$#$")[1] + " - " + time_data.split("$#$")[2] + "<br>" +last_start*1000 + "<br>" + last_finish*1000;
  355. return "Opened: " + last_start.toLocaleTimeString() + "<br>Submitted: " + last_finish.toLocaleTimeString()+ "<br>Time: " + (h.length?pad(h,2) + ":":"") + pad(m,2) + ":" + pad(s,2) + "<br>Hourly: $" + (reward/((last_finish-last_start)/(60*60*1000))).toFixed(2) ;
  356.  
  357. // return time_data.split("$#$")[0] + " - " + time_data.split("$#$")[1] + " - " + time_data.split("$#$")[2] + "<br>" + (new Date(last_start)).toTimeString() + "<br>" + (new Date(last_finish)).toTimeString();
  358. }
  359.  
  360. function store_data(action_type)
  361. {
  362. var $isAccepted = $('input[type="hidden"][name="isAccepted"][value="true"]');
  363. if ($isAccepted.length > 0 && !hit_returned)
  364. {
  365. var hitReview_hitId = $('form[name="hitForm"][action="/mturk/hitReview"] input[name="hitId"]').val();
  366. console.log(hitReview_hitId);
  367. var hit_reward = $('form[name="hitForm"][action="/mturk/submit"] input[name="prevReward"]').val().replace('USD','');
  368. console.log(hit_reward);
  369. var requester_name = $('td.capsule_field_title:contains("Requester:"):eq(0)').next().text().trim();
  370. var hit_title = $('table:contains("Requester:"):eq(0) table:eq(0)').text().trim();
  371. var now_in_milliseconds = new Date().getTime();
  372. var open_list = "";
  373. var close_list = "";
  374. var stored_copy = localStorage.getItem('time_data.' + hitReview_hitId);
  375. if (stored_copy !== null){
  376. open_list = stored_copy.split('$#$')[3];
  377. close_list = stored_copy.split('$#$')[4];
  378. }
  379. if (action_type == "open"){
  380. open_list += "?" + now_in_milliseconds;
  381. }
  382. else{
  383. close_list += "?" + now_in_milliseconds;
  384. }
  385. var autoapprove_data = requester_name + "$#$" + hit_title + "$#$"+ hit_reward +"$#$" + open_list + "$#$" + close_list;
  386. console.log(autoapprove_data);
  387. localStorage.setItem('time_data.' + hitReview_hitId, autoapprove_data);
  388. }
  389. }
  390.  
  391. function mergeIntervals(intervals) {
  392. // test if there are at least 2 intervals
  393. if(intervals.length <= 1)
  394. return intervals;
  395.  
  396. var stack = [];
  397. var top = null;
  398.  
  399. // sort the intervals based on their start values
  400. intervals = intervals.sort(function (startValue, endValue) {
  401. if (startValue[0] > endValue[0]) {
  402. return 1;
  403. }
  404. if (startValue[0] < endValue[0]) {
  405. return -1;
  406. }
  407. return 0;
  408. });
  409.  
  410. // push the 1st interval into the stack
  411. stack.push(intervals[0]);
  412.  
  413. // start from the next interval and merge if needed
  414. for (var i = 1; i < intervals.length; i++) {
  415. // get the top element
  416. top = stack[stack.length - 1];
  417.  
  418. // if the current interval doesn't overlap with the
  419. // stack top element, push it to the stack
  420. if (top[1] < intervals[i][0]) {
  421. stack.push(intervals[i]);
  422. }
  423. // otherwise update the end value of the top element
  424. // if end of current interval is higher
  425. else if (top[1] < intervals[i][1])
  426. {
  427. top[1] = intervals[i][1];
  428. stack.pop();
  429. stack.push(top);
  430. }
  431. }
  432.  
  433. return stack;
  434. }
  435.  
  436. function combineIntervals(intervals, padding) {
  437. // test if there are at least 2 intervals
  438. if(intervals.length <= 1)
  439. return intervals;
  440.  
  441. var stack = [];
  442. var top = null;
  443.  
  444. // push the 1st interval into the stack
  445. stack.push(intervals[0]);
  446.  
  447. // start from the next interval and merge if needed
  448. for (var i = 1; i < intervals.length; i++) {
  449. // get the top element
  450. top = stack[stack.length - 1];
  451.  
  452. // if the current interval doesn't overlap with the
  453. // stack top element, push it to the stack
  454. if ((intervals[i][0]-top[1]) > padding) {
  455. // console.log("hi",(intervals[i][0]-top[1]) + padding);
  456. stack.push(intervals[i]);
  457. }
  458. // otherwise update the end value of the top element
  459. // if end of current interval is higher
  460. else if (top[1] < intervals[i][1])
  461. {
  462. top[1] = intervals[i][1];
  463. stack.pop();
  464. stack.push(top);
  465. }
  466. }
  467. //console.log("stack",stack);
  468. return stack;
  469. }
  470.  
  471. function pad(n, width, z) {
  472. z = z || '0';
  473. n = n + '';
  474. return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n;
  475. }