Worker-MTurk Qual Sorter (with fix)

Keep Track Of Qualifications And Create A More Sortable List.

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name              Worker-MTurk Qual Sorter (with fix)
// @author            Eisenpower
// @namespace         Uchiha Clan
// @version           0.531
// @icon              https://i.imgur.com/M0jWVYS.png
// @description       Keep Track Of Qualifications And Create A More Sortable List.
// @include           https://worker.mturk.com/dashboard*
// @include           https://worker.mturk.com/qualtable*
// @require           https://code.jquery.com/jquery-3.3.1.min.js
// @require           https://code.jquery.com/ui/1.12.1/jquery-ui.min.js
// @require           https://cdnjs.cloudflare.com/ajax/libs/datatables/1.10.1/js/jquery.dataTables.js
// @resource       fa https://ddvx2zdcut4vw.cloudfront.net/assets/application-c6bbd5228d4b2574a79e8828bf98dfe4b2f37ab3cc3052173436ac49f2b434f0.css
// @resource  datatab https://greasyfork.org/scripts/4258-datatables-css-cdn-with-images/code/Datatables%20CSS%20CDN%20with%20Images.js?version=13654
// @grant             GM_setValue
// @grant             GM_getValue
// @grant             GM_addStyle
// @grant             GM_getResourceText
// ==/UserScript==
 
// Original Code By 'DeliriumTremens 2014' - Patched And Revamped
 
//
// GET CSS MODULES
//
 
GM_addStyle(GM_getResourceText("datatab"));
GM_addStyle(GM_getResourceText("fa"));
 
//
// END CSS MODULES
//
// ******************************************************************
//
//	START VARIABLE DEFINITIONS
//
 
var qualCount = null; // hold onto that sweet, sweet qual count
var qualPrev = GM_getValue("quals"); // store the previous qual count
var qualDiff = 0; // difference in qual count between dashboard refreshes
var nextPage = "https://worker.mturk.com/qualifications/assigned.json?page_size=100"; // Starting Page of Scrape
var qualObject = {}; // Storage for qualification details to be sent to database
var QualStorage = {}; // QualStorage object definition
var Scraping = true; // Used to start and kill scraping
var scrapeNumber = 0; // Unused currently
var currScrape = null; // Container for current page being scraped
var skipQuals = ['Exc: ', 'Qualification_', 'SurveyGroup ', 'TP Panel:', 'Inc: ', 'campaign_'];
 
//
//	END VARIABLE DEFINITIONS
//
// ******************************************************************
//
//	START INDEXEDDB METHODS
//
 
var indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB;
window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.mozIDBTransaction;
window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.mozIDBKeyRange;
var idbKeyRange = window.IDBKeyRange;
QualStorage.indexedDB = {};
QualStorage.indexedDB.db = null;
var v = 1; // Database version.
var dbExists = true; // Boolean if database exists.
 
// Method for creating the new database.
QualStorage.indexedDB.create = function () {
    var request = indexedDB.open("QualDB", v);
    request.onupgradeneeded = function (e) { QualStorage.indexedDB.upgrade(e) };
    request.onsuccess = function (e) {
        QualStorage.indexedDB.db = e.target.result;
        var db = QualStorage.indexedDB.db;
        db.close();
    };
    //request.onerror = console.log(request.errorCode);
};
 
QualStorage.indexedDB.upgrade = function (e) {
    QualStorage.indexedDB.db = e.target.result;
    var db = QualStorage.indexedDB.db;
    var newDB = false;
    if(!db.objectStoreNames.contains("Quals")) {
        var store = db.createObjectStore("Quals", {
            keyPath: "qualId"
        });
        store.createIndex("qualName", "qualName", {
            unique: false
        });
        store.createIndex("author", "author", {
            unique: false
        });
        store.createIndex("desc", "desc", {
            unique: false
        });
        store.createIndex("assDate", "assDate", {
            unique: false
        });
        store.createIndex("retake", "retake", {
            unique: false
        });
        store.createIndex("hasTest", "hasTest", {
            unique: false
        });
        store.createIndex("value", "value", {
            unique: false
        });
        newDB = true;
    }
    db.close();
}
 
QualStorage.indexedDB.addQual = function (qual) {
    var request = indexedDB.open("QualDB", v);
    var qualPut = qual;
    request.onsuccess = function (e) {
        QualStorage.indexedDB.db = e.target.result;
 
        var db = QualStorage.indexedDB.db;
        var newDB = false;
 
        if (!db.objectStoreNames.contains("Quals")) {
            db.close();
        } else {
            var trans = db.transaction(["Quals"], 'readwrite');
            var store = trans.objectStore("Quals");
            var request;
 
            request = store.put({
                qualId: qualPut["qualId"],
                qualName: qualPut["qualName"],
                author: qualPut["author"],
                desc: qualPut["desc"],
                assDate: qualPut["assDate"],
                retake: qualPut["retake"],
                hasTest: qualPut["hasTest"],
                value: qualPut["value"]
            });
            request.onsuccess = function (e) {
            };
            request.onerror = function (e) {
            };
        }
        db.close();
    };
    request.onerror = QualStorage.indexedDB.onerror;
};
 
QualStorage.indexedDB.getQuals = function (e) {
    var request = indexedDB.open("QualDB", v);
 
    request.onupgradeneeded = function (e) {
        QualStorage.indexedDB.upgrade(e);
        QualStorage.indexedDB.getQuals();
    };
 
    request.onsuccess = function (e) {
        if (e.target.result.objectStoreNames.length === 0) {
            e.target.result.close();
            var DBDeleteRequest = indexedDB.deleteDatabase('QualDB')
            DBDeleteRequest.onsuccess = function (e) {
                return QualStorage.indexedDB.getQuals();
            }
        }
 
        QualStorage.indexedDB.db = e.target.result;
 
        var db = QualStorage.indexedDB.db;
        var transaction = db.transaction('Quals', 'readonly');
        var store = transaction.objectStore('Quals');
 
        var results = [];
        var tmp_results = {};
 
        store.openCursor().onsuccess = function (event) {
            var cursor = event.target.result;
            if (cursor) {
                var qual = cursor.value;
                if (tmp_results[cursor.key] === undefined) {
                    tmp_results[cursor.key] = [];
                    tmp_results[cursor.key][0] = qual.qualId;
                    tmp_results[cursor.key][1] = qual.qualName;
                    tmp_results[cursor.key][2] = qual.author;
                    tmp_results[cursor.key][3] = qual.desc;
                    tmp_results[cursor.key][4] = qual.assDate;
                    tmp_results[cursor.key][5] = qual.retake;
                    tmp_results[cursor.key][6] = qual.hasTest;
                    tmp_results[cursor.key][7] = qual.value;
                }
                cursor.continue();
            } else {
                for (var key in tmp_results) {
                    results.push(tmp_results[key]);
                }
                buildQualTable(results);
            }
        };
        db.close();
    };
};
 
//
//	END INDEXEDDB METHODS
//
// ******************************************************************
//
//	START SCRAPER METHODS
//
 
const scrapeQuals = function (page) {
    QualStorage.indexedDB.create();
    $.get(page, function(data) {
        const quals = data.assigned_qualifications;
 
        for (var i = 0; i < quals.length; i++) {
            qualObject = {
                "qualId": quals[i].retake_test_url.match(/[A-Z0-9]{15,}/)[0],
                "qualName": quals[i].name,
                "author": quals[i].creator_name,
                "desc": quals[i].description,
                "assDate": quals[i].grant_time,
                "retake": quals[i].can_retake_test_or_rerequest ? "Yes" : "No",
                "hasTest": quals[i].has_test ? quals[i].retake_test_url : "No",
                "value": quals[i].value
            };
            QualStorage.indexedDB.addQual(qualObject);
        }
 
        if (data.next_page_token) {
            const next = `https://worker.mturk.com/qualifications/assigned.json?page_size=100&next_token=${encodeURIComponent(data.next_page_token)}`
            setTimeout(() => scrapeQuals(next), 3000);
        }
 
        else {
            if (window.location.href === 'https://worker.mturk.com/qualtable') window.location.reload();
            $('.updatePercentage').css('visibility', 'visible');
            $('.updatePercentage').css('color', 'darkseagreen');
            $('.updateLink > .fa-refresh')[0].classList.remove("rotate");
        }
    });
};
 
//
//	END SCRAPER METHODS
//
// ******************************************************************
//
// START EVENT HANDLERS
//
 
const addQualElement = function () {
    $('.expand-collapse-projects-holder').prepend('<div class="qualSorterContainer" style="line-height: 1; white-space: nowrap; margin-right: 20px;"></div>');
    $('.qualSorterContainer').prepend('<span>Qual Sorter:</span>');
    $('.qualSorterContainer').append('<div class="qualSorterButtonContainer" style="font-size: large"></div>');
    $('.qualSorterButtonContainer').append('<a href="#" title="View Quals" class="qualTableBut" style="margin-left: 12px"><i class="fa fa-columns"></i></a>')
    $('.qualSorterButtonContainer').append('<a href="#" title="Update Quals" class="updateLink" style="margin-left: 8px"><i class="fa fa-refresh"></i></a>');
    $('.qualSorterButtonContainer').append('<span class="updatePercentage" style="font-size: small; visibility: hidden;"><i class="fa fa-check-circle-o"></i></span>')
};
 
const buildQualTable = function (qualStore) {
    $('#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>');
    $('#qualTable').append('<tbody></tbody>');
    for (var j = 0; j < qualStore.length; j++) {
        if (!skipQuals.some(function(v) { return qualStore[j][1].indexOf(v) >= 0; })) {
            var qualRow = String(".qualRow" + j);
 
            var tr = document.createElement("tr");
            tr.setAttribute('class', ('qualRow' + j));
 
            $(tr).append(`<td>${qualStore[j][0]}</td>`);
            $(tr).append(`<td>${qualStore[j][1]}</td>`);
            $(tr).append(`<td>${qualStore[j][2]}</td>`);
            $(tr).append(`<td style='width:350px'>${qualStore[j][3]}</td>`);
            $(tr).append(`<td>${qualStore[j][4]}</td>`);
            $(tr).append(`<td style='text-align: center'>${qualStore[j][5]}</td>`);
            $(tr).append(`<td style='text-align: center'>${qualStore[j][6] == "No" ? "No" : `<a href=${qualStore[j][6]}>TEST</a>`} </td>`);
            $(tr).append(`<td style='text-align: center'>${qualStore[j][7]}</td>`);
 
            $('#qualTable tbody').append(tr);
        }
    }
    $('#qualTable').dataTable({
        paging: false
    });
    $('#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>`);
    $('#qualTable_wrapper').prepend(`<div style="float: left; padding: 5px;"><b>Total Quals: </b>${qualStore.length.toLocaleString()}</div>`);
};
 
$(document).on('click', '.updateLink', function (e) {
    e.preventDefault();
    $('.updateLink > .fa-refresh')[0].classList.add("rotate");
    $('.updatePercentage').css('visibility', 'hidden');
    $('.updatePercentage').css('color', '');
    scrapeQuals(nextPage);
});
 
$(document).ready( function () {
    if (document.URL.includes("https://worker.mturk.com/qualtable")) {
        $('body').html('');
        var qualTable = document.createElement("table");
        qualTable.setAttribute('class', 'display');
        qualTable.setAttribute('id', 'qualTable');
        $('body').append(qualTable);
 
        var qualElements = QualStorage.indexedDB.getQuals();
    }
});
 
$(document).on('click', '.qualTableBut', function () {
    window.open("https://worker.mturk.com/qualtable", '_blank');
});
 
//
//	END EVENT HANDLERS
//
// ******************************************************************
//
//  START STYLING
//
 
const styles = function() {
  var styleEm = document.createElement('style');
  document.head.appendChild(styleEm);
  var styleSheet = styleEm.sheet;
 
  var rule1 = `@-webkit-keyframes rotate {
                    from{
                        -webkit-transform: rotate(0deg);
                    }
                    to{
                        -webkit-transform: rotate(360deg);
                    }
                }`
 
  var rule2 = `.rotate {
                    -webkit-animation: rotate 1s linear infinite;
                }`
 
    styleSheet.insertRule(rule1, styleSheet.cssRules.length);
    styleSheet.insertRule(rule2, styleSheet.cssRules.length);
}
 
//
//  END STYLING
//
 
addQualElement();
styles();