Greasy Fork is available in English.

Worker-MTurk Qual Sorter (with fix)

Keep Track Of Qualifications And Create A More Sortable List.

  1. // ==UserScript==
  2. // @name Worker-MTurk Qual Sorter (with fix)
  3. // @author Eisenpower
  4. // @namespace Uchiha Clan
  5. // @version 0.531
  6. // @icon https://i.imgur.com/M0jWVYS.png
  7. // @description Keep Track Of Qualifications And Create A More Sortable List.
  8. // @include https://worker.mturk.com/dashboard*
  9. // @include https://worker.mturk.com/qualtable*
  10. // @require https://code.jquery.com/jquery-3.3.1.min.js
  11. // @require https://code.jquery.com/ui/1.12.1/jquery-ui.min.js
  12. // @require https://cdnjs.cloudflare.com/ajax/libs/datatables/1.10.1/js/jquery.dataTables.js
  13. // @resource fa https://ddvx2zdcut4vw.cloudfront.net/assets/application-c6bbd5228d4b2574a79e8828bf98dfe4b2f37ab3cc3052173436ac49f2b434f0.css
  14. // @resource datatab https://greasyfork.org/scripts/4258-datatables-css-cdn-with-images/code/Datatables%20CSS%20CDN%20with%20Images.js?version=13654
  15. // @grant GM_setValue
  16. // @grant GM_getValue
  17. // @grant GM_addStyle
  18. // @grant GM_getResourceText
  19. // ==/UserScript==
  20. // Original Code By 'DeliriumTremens 2014' - Patched And Revamped
  21. //
  22. // GET CSS MODULES
  23. //
  24. GM_addStyle(GM_getResourceText("datatab"));
  25. GM_addStyle(GM_getResourceText("fa"));
  26. //
  27. // END CSS MODULES
  28. //
  29. // ******************************************************************
  30. //
  31. // START VARIABLE DEFINITIONS
  32. //
  33. var qualCount = null; // hold onto that sweet, sweet qual count
  34. var qualPrev = GM_getValue("quals"); // store the previous qual count
  35. var qualDiff = 0; // difference in qual count between dashboard refreshes
  36. var nextPage = "https://worker.mturk.com/qualifications/assigned.json?page_size=100"; // Starting Page of Scrape
  37. var qualObject = {}; // Storage for qualification details to be sent to database
  38. var QualStorage = {}; // QualStorage object definition
  39. var Scraping = true; // Used to start and kill scraping
  40. var scrapeNumber = 0; // Unused currently
  41. var currScrape = null; // Container for current page being scraped
  42. var skipQuals = ['Exc: ', 'Qualification_', 'SurveyGroup ', 'TP Panel:', 'Inc: ', 'campaign_'];
  43. //
  44. // END VARIABLE DEFINITIONS
  45. //
  46. // ******************************************************************
  47. //
  48. // START INDEXEDDB METHODS
  49. //
  50. var indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB;
  51. window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.mozIDBTransaction;
  52. window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.mozIDBKeyRange;
  53. var idbKeyRange = window.IDBKeyRange;
  54. QualStorage.indexedDB = {};
  55. QualStorage.indexedDB.db = null;
  56. var v = 1; // Database version.
  57. var dbExists = true; // Boolean if database exists.
  58. // Method for creating the new database.
  59. QualStorage.indexedDB.create = function () {
  60. var request = indexedDB.open("QualDB", v);
  61. request.onupgradeneeded = function (e) { QualStorage.indexedDB.upgrade(e) };
  62. request.onsuccess = function (e) {
  63. QualStorage.indexedDB.db = e.target.result;
  64. var db = QualStorage.indexedDB.db;
  65. db.close();
  66. };
  67. //request.onerror = console.log(request.errorCode);
  68. };
  69. QualStorage.indexedDB.upgrade = function (e) {
  70. QualStorage.indexedDB.db = e.target.result;
  71. var db = QualStorage.indexedDB.db;
  72. var newDB = false;
  73. if(!db.objectStoreNames.contains("Quals")) {
  74. var store = db.createObjectStore("Quals", {
  75. keyPath: "qualId"
  76. });
  77. store.createIndex("qualName", "qualName", {
  78. unique: false
  79. });
  80. store.createIndex("author", "author", {
  81. unique: false
  82. });
  83. store.createIndex("desc", "desc", {
  84. unique: false
  85. });
  86. store.createIndex("assDate", "assDate", {
  87. unique: false
  88. });
  89. store.createIndex("retake", "retake", {
  90. unique: false
  91. });
  92. store.createIndex("hasTest", "hasTest", {
  93. unique: false
  94. });
  95. store.createIndex("value", "value", {
  96. unique: false
  97. });
  98. newDB = true;
  99. }
  100. db.close();
  101. }
  102. QualStorage.indexedDB.addQual = function (qual) {
  103. var request = indexedDB.open("QualDB", v);
  104. var qualPut = qual;
  105. request.onsuccess = function (e) {
  106. QualStorage.indexedDB.db = e.target.result;
  107. var db = QualStorage.indexedDB.db;
  108. var newDB = false;
  109. if (!db.objectStoreNames.contains("Quals")) {
  110. db.close();
  111. } else {
  112. var trans = db.transaction(["Quals"], 'readwrite');
  113. var store = trans.objectStore("Quals");
  114. var request;
  115. request = store.put({
  116. qualId: qualPut["qualId"],
  117. qualName: qualPut["qualName"],
  118. author: qualPut["author"],
  119. desc: qualPut["desc"],
  120. assDate: qualPut["assDate"],
  121. retake: qualPut["retake"],
  122. hasTest: qualPut["hasTest"],
  123. value: qualPut["value"]
  124. });
  125. request.onsuccess = function (e) {
  126. };
  127. request.onerror = function (e) {
  128. };
  129. }
  130. db.close();
  131. };
  132. request.onerror = QualStorage.indexedDB.onerror;
  133. };
  134. QualStorage.indexedDB.getQuals = function (e) {
  135. var request = indexedDB.open("QualDB", v);
  136. request.onupgradeneeded = function (e) {
  137. QualStorage.indexedDB.upgrade(e);
  138. QualStorage.indexedDB.getQuals();
  139. };
  140. request.onsuccess = function (e) {
  141. if (e.target.result.objectStoreNames.length === 0) {
  142. e.target.result.close();
  143. var DBDeleteRequest = indexedDB.deleteDatabase('QualDB')
  144. DBDeleteRequest.onsuccess = function (e) {
  145. return QualStorage.indexedDB.getQuals();
  146. }
  147. }
  148. QualStorage.indexedDB.db = e.target.result;
  149. var db = QualStorage.indexedDB.db;
  150. var transaction = db.transaction('Quals', 'readonly');
  151. var store = transaction.objectStore('Quals');
  152. var results = [];
  153. var tmp_results = {};
  154. store.openCursor().onsuccess = function (event) {
  155. var cursor = event.target.result;
  156. if (cursor) {
  157. var qual = cursor.value;
  158. if (tmp_results[cursor.key] === undefined) {
  159. tmp_results[cursor.key] = [];
  160. tmp_results[cursor.key][0] = qual.qualId;
  161. tmp_results[cursor.key][1] = qual.qualName;
  162. tmp_results[cursor.key][2] = qual.author;
  163. tmp_results[cursor.key][3] = qual.desc;
  164. tmp_results[cursor.key][4] = qual.assDate;
  165. tmp_results[cursor.key][5] = qual.retake;
  166. tmp_results[cursor.key][6] = qual.hasTest;
  167. tmp_results[cursor.key][7] = qual.value;
  168. }
  169. cursor.continue();
  170. } else {
  171. for (var key in tmp_results) {
  172. results.push(tmp_results[key]);
  173. }
  174. buildQualTable(results);
  175. }
  176. };
  177. db.close();
  178. };
  179. };
  180. //
  181. // END INDEXEDDB METHODS
  182. //
  183. // ******************************************************************
  184. //
  185. // START SCRAPER METHODS
  186. //
  187. const scrapeQuals = function (page) {
  188. QualStorage.indexedDB.create();
  189. $.get(page, function(data) {
  190. const quals = data.assigned_qualifications;
  191. for (var i = 0; i < quals.length; i++) {
  192. qualObject = {
  193. "qualId": quals[i].retake_test_url.match(/[A-Z0-9]{15,}/)[0],
  194. "qualName": quals[i].name,
  195. "author": quals[i].creator_name,
  196. "desc": quals[i].description,
  197. "assDate": quals[i].grant_time,
  198. "retake": quals[i].can_retake_test_or_rerequest ? "Yes" : "No",
  199. "hasTest": quals[i].has_test ? quals[i].retake_test_url : "No",
  200. "value": quals[i].value
  201. };
  202. QualStorage.indexedDB.addQual(qualObject);
  203. }
  204. if (data.next_page_token) {
  205. const next = `https://worker.mturk.com/qualifications/assigned.json?page_size=100&next_token=${encodeURIComponent(data.next_page_token)}`
  206. setTimeout(() => scrapeQuals(next), 3000);
  207. }
  208. else {
  209. if (window.location.href === 'https://worker.mturk.com/qualtable') window.location.reload();
  210. $('.updatePercentage').css('visibility', 'visible');
  211. $('.updatePercentage').css('color', 'darkseagreen');
  212. $('.updateLink > .fa-refresh')[0].classList.remove("rotate");
  213. }
  214. });
  215. };
  216. //
  217. // END SCRAPER METHODS
  218. //
  219. // ******************************************************************
  220. //
  221. // START EVENT HANDLERS
  222. //
  223. const addQualElement = function () {
  224. $('.expand-collapse-projects-holder').prepend('<div class="qualSorterContainer" style="line-height: 1; white-space: nowrap; margin-right: 20px;"></div>');
  225. $('.qualSorterContainer').prepend('<span>Qual Sorter:</span>');
  226. $('.qualSorterContainer').append('<div class="qualSorterButtonContainer" style="font-size: large"></div>');
  227. $('.qualSorterButtonContainer').append('<a href="#" title="View Quals" class="qualTableBut" style="margin-left: 12px"><i class="fa fa-columns"></i></a>')
  228. $('.qualSorterButtonContainer').append('<a href="#" title="Update Quals" class="updateLink" style="margin-left: 8px"><i class="fa fa-refresh"></i></a>');
  229. $('.qualSorterButtonContainer').append('<span class="updatePercentage" style="font-size: small; visibility: hidden;"><i class="fa fa-check-circle-o"></i></span>')
  230. };
  231. const buildQualTable = function (qualStore) {
  232. $('#qualTable').append('<thead><tr><th>Qual ID</th><th>Qual Name</th><th>Author</th><th>Description</th><th width="75px" style="text-align: center">Assgn</th><th style="text-align: center">Retake Available</th><th style="text-align: center">Has Test</th><th style="text-align: center">Value</th></tr></thead>');
  233. $('#qualTable').append('<tbody></tbody>');
  234. for (var j = 0; j < qualStore.length; j++) {
  235. if (!skipQuals.some(function(v) { return qualStore[j][1].indexOf(v) >= 0; })) {
  236. var qualRow = String(".qualRow" + j);
  237. var tr = document.createElement("tr");
  238. tr.setAttribute('class', ('qualRow' + j));
  239. $(tr).append(`<td>${qualStore[j][0]}</td>`);
  240. $(tr).append(`<td>${qualStore[j][1]}</td>`);
  241. $(tr).append(`<td>${qualStore[j][2]}</td>`);
  242. $(tr).append(`<td style='width:350px'>${qualStore[j][3]}</td>`);
  243. $(tr).append(`<td>${qualStore[j][4]}</td>`);
  244. $(tr).append(`<td style='text-align: center'>${qualStore[j][5]}</td>`);
  245. $(tr).append(`<td style='text-align: center'>${qualStore[j][6] == "No" ? "No" : `<a href=${qualStore[j][6]}>TEST</a>`} </td>`);
  246. $(tr).append(`<td style='text-align: center'>${qualStore[j][7]}</td>`);
  247. $('#qualTable tbody').append(tr);
  248. }
  249. }
  250. $('#qualTable').dataTable({
  251. paging: false
  252. });
  253. $('#qualTable_wrapper').prepend(`<a href="#" title="Update Quals" class="updateLink" style="margin-left: 8px; font-size: x-large; color: #146EB4; vertical-align: -webkit-baseline-middle;"><i class="fa fa-refresh"></i></a>`);
  254. $('#qualTable_wrapper').prepend(`<div style="float: left; padding: 5px;"><b>Total Quals: </b>${qualStore.length.toLocaleString()}</div>`);
  255. };
  256. $(document).on('click', '.updateLink', function (e) {
  257. e.preventDefault();
  258. $('.updateLink > .fa-refresh')[0].classList.add("rotate");
  259. $('.updatePercentage').css('visibility', 'hidden');
  260. $('.updatePercentage').css('color', '');
  261. scrapeQuals(nextPage);
  262. });
  263. $(document).ready( function () {
  264. if (document.URL.includes("https://worker.mturk.com/qualtable")) {
  265. $('body').html('');
  266. var qualTable = document.createElement("table");
  267. qualTable.setAttribute('class', 'display');
  268. qualTable.setAttribute('id', 'qualTable');
  269. $('body').append(qualTable);
  270. var qualElements = QualStorage.indexedDB.getQuals();
  271. }
  272. });
  273. $(document).on('click', '.qualTableBut', function () {
  274. window.open("https://worker.mturk.com/qualtable", '_blank');
  275. });
  276. //
  277. // END EVENT HANDLERS
  278. //
  279. // ******************************************************************
  280. //
  281. // START STYLING
  282. //
  283. const styles = function() {
  284. var styleEm = document.createElement('style');
  285. document.head.appendChild(styleEm);
  286. var styleSheet = styleEm.sheet;
  287. var rule1 = `@-webkit-keyframes rotate {
  288. from{
  289. -webkit-transform: rotate(0deg);
  290. }
  291. to{
  292. -webkit-transform: rotate(360deg);
  293. }
  294. }`
  295. var rule2 = `.rotate {
  296. -webkit-animation: rotate 1s linear infinite;
  297. }`
  298. styleSheet.insertRule(rule1, styleSheet.cssRules.length);
  299. styleSheet.insertRule(rule2, styleSheet.cssRules.length);
  300. }
  301. //
  302. // END STYLING
  303. //
  304. addQualElement();
  305. styles();