Warning: fopen(/www/sites/update.greasyfork.icu/index/store/temp/02deeb3e0b47a5edcbcb73d0c4ab8b6f.js): failed to open stream: No space left on device in /www/sites/update.greasyfork.icu/index/scriptControl.php on line 65
// ==UserScript== // @name Turkmaster (Mturk) // @namespace https://greasyfork.org/users/12875 // @author DonovanM // @description A fork of DonovanM's Turkmaster, now with watcher groups and other new features. A page-monitoring web app for Mturk (Mechanical Turk) designed to make turking more efficient. Easily monitor mturk search pages and requesters and Auto-Accept the HITs you missed. // @include https://www.mturk.com/mturk/* // @version 1.3.4e // @require https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js // @require https://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js // @grant GM_getValue // @grant GM_setValue // @downloadURL https://update.greasyfork.icu/scripts/11373/Turkmaster%20%28Mturk%29.user.js // @updateURL https://update.greasyfork.icu/scripts/11373/Turkmaster%20%28Mturk%29.meta.js // ==/UserScript== // Incorporates additions by Kevin Schumacher (tubedogg on MTG/GreasyFork): // - Basic support for groups // - Stop group if one PANDA catches // - Start with group (click on any member of the group to start them all) // - "Last Found" date (hover over last checked time at bottom-right of each watcher) // - Tracks date watcher was added (for watchers added from this point forward - also found by hovering over last checked time) // - Deselect all (from gear/settings menu) // - New option: "Show other page" - load something else where the dashboard normally is (specify URL in gear/settings menu) // - New option: Force TurkMaster URL: only load if ?turkmaster is added to dashboard URL e.g. if you go to http://www.mturk.com/mturk/dashboard it would not load, but if you go to http://www.mturk.com/mturk/dashboard?turkmaster it would do so. Useful if you normally keep the dashboard open in another tab/window/browser so it can auto-refresh, and especially so when paired with the "Show other page" setting var settings = (function() { var LOCAL_STORAGE = "turkmaster_settings"; var pub = { sound : true, animation : true, preloadHits : false, volume : 50, notifications : true, alertOnly : false, fontSize : 10, typeface : "Oxygen", desktopNotifications : false, canHide : false, customURL : '', forceUniqueURL: false }; _load(); function _setfontSize(val) { if (val >= 5 && val <= 20) { pub.fontSize = val; $("#dispatcher div").css("font-size", val + "pt"); $(".notification_panel p").css("font-size", val + "pt"); $("#settingsDialog, #settingsDialog div, #settingsDialog li, #settingsDialog input, #settingsDialog button").css("font-size", val + "pt"); _save(); } } function _setDesktopNotifications(val, callback) { if (val) { requestDesktopNotifications(function(isPermitted) { callback(isPermitted); pub.desktopNotifications = isPermitted; _save(); }); pub.desktopNotifications = false; } else { pub.desktopNotifications = false; } _save(); } function _setVolume(val) { if (val >= 0 && val <= 100) { Sound.setVolume(val); pub.volume = val; _save(); } } function _setCustomFrameURL(val) { var urlregex = new RegExp( "^(http|https)\://([a-zA-Z0-9\.\-]+(\:[a-zA-Z0-9\.&%\$\-]+)*@)*((25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9])\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[0-9])|([a-zA-Z0-9\-]+\.)*[a-zA-Z0-9\-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(\:[0-9]+)*(/($|[a-zA-Z0-9\.\,\?\'\\\+&%\$#\=~_\-]+))*$"); if (urlregex.test(val) && $("#externalFrame").length) { $("#externalFrame").src = val; pub.customURL = val; _save(); } else if (urlregex.test(val) || (val === '' && pub.customURL !== '')) { // save settings with the new URL (blank or otherwise) and reload the page to add/remove the iframe pub.customURL = val; _save(); location.reload(); } else if (!urlregex.test(val)) { alert('The URL entered is not valid. Please try again. If you are trying to revert to showing the MTurk dashboard, please empty the URL box.'); } } function _save() { // localStorage.setItem(LOCAL_STORAGE, JSON.stringify(pub)); GM_setValue(LOCAL_STORAGE, JSON.stringify(pub)); } function _load() { var values = GM_getValue(LOCAL_STORAGE); if (typeof values === 'undefined') values = localStorage.getItem(LOCAL_STORAGE); if (values) { values = JSON.parse(values); for (i in values) pub[i] = values[i]; } } pub.setfontSize = _setfontSize; pub.setVolume = _setVolume; pub.setDesktopNotifications = _setDesktopNotifications; pub.setCustomFrameURL = _setCustomFrameURL; pub.save = _save; return pub; }()); var pageType = { MAIN : true, // This is so remote watcher requests don't add new watchers to multiple pages and cause mturk errors. DASHBOARD : false, HIT : false, REQUESTER : false, SEARCH : false }; var loadError = false; var wasViewed = false; var dispatch; var notificationPanel; if(!('contains' in String.prototype)) { String.prototype.contains = function(str, startIndex) { return -1 !== String.prototype.indexOf.call(this, str, startIndex); }; } $(document).ready(function(){ console.log('Ready'); checkPageType(); loadFonts(); if (pageType.DASHBOARD) { dispatch = new Dispatch(); DispatchUI.create(dispatch); createDetailsPanel(); IgnoreList.init(); if (settings.preloadHits) loadDefaultWatchers(); else dispatch.load(); requestMain(); preloadImages(); addFormStyle(); } if (pageType.HIT || pageType.REQUESTER || pageType.SEARCH) addWatchButton(); notificationPanel = new NotificationPanel(); // Listen to messages window.addEventListener('storage', onStorageEvent, false); }); $(window).on('beforeunload', function() { if (pageType.DASHBOARD && pageType.MAIN) { dispatch.save(); } }); function loadFonts() { WebFont.load({ google: { families: [ 'Oxygen:400,700:latin', 'Droid Sans Mono' ] } }); } function onStorageEvent(event) { if (event.key.substring(0,13) === "notifier_msg_") onMessageReceived(event.key.substring(13), JSON.parse(event.newValue).content); } function checkPageType() { // Dashboard, hit, requester, search if (document.URL.search("https://www.mturk.com/mturk/dashboard") > -1 && ((document.URL.search("turkmaster") > -1 && settings.forceUniqueURL) || !settings.forceUniqueURL)) { pageType.DASHBOARD = true; } else if (document.URL.match(/https:\/\/www.mturk.com\/mturk\/(preview|accept).+groupId=.*/) !== null) { pageType.HIT = true; } else if (document.URL.match(/requesterId=([A-Z0-9]+)/) !== null) { pageType.REQUESTER = true; } else if (document.URL.match(/(searchbar|findhits)/) !== null) { pageType.SEARCH = true; } } function requestMain() { sendMessage({ header: "request_main" }); } function preloadImages() { var images = [ 'https://i.imgur.com/guRzYEL.png', 'https://i.imgur.com/5snaSxU.png', 'https://i.imgur.com/VTHXHI4.png', 'https://i.imgur.com/peEhuHZ.png' ]; $(images).each(function(){ $('')[0].src = this; }); } var SettingsDialog = function() { var DOMElement, TOGGLE = ''; function _show() { if (!DOMElement) _createDOMElement() _getSettings(); DOMElement.show(); $(window).on('click', _handleWindowClick); } function _isVisible() { if (DOMElement) return DOMElement.is(":visible"); else return false; } function _getSettings() { if (settings.sound) DOMElement.find("#soundSettings > .on_off").addClass("on"); DOMElement.find("#volume input").val(settings.volume); if (settings.notifications) DOMElement.find("#notificationSettings > .on_off").addClass("on"); if (settings.desktopNotifications) DOMElement.find("#desktopNotifications .on_off").addClass("on"); if (settings.alertOnly) DOMElement.find("#alertOnly .on_off").addClass("on"); if (settings.canHide) DOMElement.find("#hideable .on_off").addClass("on"); DOMElement.find("#fontSize input").val(settings.fontSize); DOMElement.find("#typeface input").val(settings.typeface); if (settings.customURL) DOMElement.find("#customURL input").val(settings.customURL); if (settings.forceUniqueURL) DOMElement.find("#forceUniqueURL .on_off").addClass("on"); if (settings.stopCaptcha) DOMElement.find("#stopCaptcha .on_off").addClass("on"); } function _save() { } function _cancel() { DOMElement.hide(); } function _createDOMElement() { _addStyle(); DOMElement = $('

Settings

').append( $('

General

\ \
'), $('
' + TOGGLE + '

Sound

\ \
'), $('
' + TOGGLE + '

Notifications

\ \
'), $('

Font

\ \
'), $('

User Interface

\ \
'), $('

Backup

\ \
'), $('

Deselect All

\ \
') ); _addHandlers(); $("body").append(DOMElement); } function _addHandlers() { DOMElement.on('click', function(e) { if (e.target.tagName === "BUTTON" || e.target.parentNode.tagName === "BUTTON") _handleButtonToggle(e); }); DOMElement.on('change', _handleInputChange); } function _handleWindowClick(e) { var target = e.target; if (!DOMElement.is(target) && DOMElement.has(target).length === 0 && $("#settings img").get(0) !== target) { _cancel(); $(window).off('click', _handleWindowClick); } } function _handleInputChange(e) { var target = $(e.target), value = target.val(), id = target.parent().attr('id'); if (id === "volume") settings.setVolume(value); else if (id === "fontSize") settings.setfontSize(value); else if (id === "typeface") settings.typeface = value; else if (id === "customURL") settings.setCustomFrameURL(value); } function _handleButtonToggle(e) { e.preventDefault(); // Chrome returns the span as the target while FF returns the button var target = (e.target.tagName === "BUTTON") ? $(e.target) : $(e.target).parent(), value = target.hasClass("on"), id = target.parent().attr('id'); if (id !== "desktopNotifications") { if (target.hasClass("on")) { target.removeClass("on"); value = false; } else { target.addClass("on"); value = true; } } if (id === "soundSettings") { settings.sound = value; } else if (id === "notificationSettings") { settings.notifications = value; } else if (id === "desktopNotifications") { if (value) target.removeClass("on"); // Desktop notification requests require user action so we need a callback // for when the user responds. settings.setDesktopNotifications(!value, function(isPermitted) { if (isPermitted) { target.addClass("on"); } else { target.removeClass("on"); console.error("Desktop notifications are blocked."); } }); } else if (id === "alertOnly") { settings.alertOnly = value; } else if (id === "hideable") { settings.canHide = value; setTimeout(function () { DispatchUI.setHide() }, 50); } else if (id === "forceUniqueURL") { settings.forceUniqueURL = value; } else if (id === "stopCaptcha") { settings.stopCaptcha = value; } settings.save(); if (id === "export") { _showExport(); } else if (id === "import") { _showImport(); } if (id === "deselect_all") { _deselectAll(); } if (id === "startNonHitWatchers") { _startNonHitWatchers(); } if (id === "stopNonHitWatchers") { _stopNonHitWatchers(); } } function _showExport() { var div = $('

Export Watchers

Copy the text below. (Triple-click to highlight all)

' + dispatch.exportWatchers() + '

'); $('') .click(function() { div.remove(); }) .appendTo(div); div.appendTo($("body")); } function _showImport() { var div = $('

Import Watchers

Paste the backup text to load watchers

'); $('') .click(function() { dispatch.importWatchers($("#import-box textarea").val()); div.remove() }) .appendTo(div); $('') .click(function() { div.remove() }) .appendTo(div); div.appendTo($("body")); div.find("textarea").focus(); } function _deselectAll() { dispatch.deselectAll(); } function _startNonHitWatchers() { for (var i = 0, len = dispatch.watchers.length; i < len; i++) { if (dispatch.watchers[i].type !== 'hit' && !dispatch.watchers[i].isRunning) { dispatch.watchers[i].start(); } } } function _stopNonHitWatchers() { for (var i = 0, len = dispatch.watchers.length; i < len; i++) { if (dispatch.watchers[i].type !== 'hit' && dispatch.watchers[i].isRunning) { dispatch.watchers[i].stop(); } } } function _addStyle() { addStyle("\ #settingsDialog {\ position: fixed;\ top: 16px;\ left: 249px;\ background-color: #fafafa;\ padding: 10px;\ width: 300px;\ font: " + settings.fontSize + "pt 'Oxygen', verdana, sans-serif;\ border-bottom: 1px solid #DDD;\ border-right: 1px solid #DDD;\ border-radius: 0.3em;\ }\ #settingsDialog div, #settingsDialog li, #settingsDialog input, #settingsDialog button {\ font: " + settings.fontSize + "pt 'Oxygen', verdana, sans-serif;\ }\ #settingsDialog > div {\ margin: 0px 0px 0.5em;\ border: 1px solid #eee;\ padding: 0.75em;\ background-color: #fff;\ }\ #settingsDialog h2, #settingsDialog h3 {\ font-weight: 400;\ margin: 0 0 0.5em;\ }\ #settingsDialog h2 {\ text-align: center;\ font-size: 140%;\ color: #333;\ }\ #settingsDialog button.on_off {\ background: none;\ border: none;\ padding: 0;\ outline: none;\ height: 1.3em;\ margin-top: 0em;\ }\ #settingsDialog .on_off span { color: #333; margin: 1px; font-size: 56%; font-weight: bold; border-radius: 1.6em; }\ #settingsDialog .on_off span:nth-child(2) { background-color: #aeaeae; color: #fff; padding: 0.4em 0.8em; }\ #settingsDialog .on_off.on span:nth-child(1) { background-color: #55b8ea; color: #fff; padding: 0.4em 0.8em; }\ #settingsDialog .on_off.on span:nth-child(2) { background-color: inherit; color: #333; padding: 0 0.8em 0 0; }\ #settingsDialog .on_off { margin-top: 6px; }\ #settingsDialog ul { margin: 0 0 0.2em; padding: 0 0 0 1.9em }\ #settingsDialog ul li { list-style: none; margin-bottom: 0.5em; }\ #settingsDialog li input, #settingsDialog li button { float: right; }\ #settingsDialog li input[type='text'] { width: 3em; font-size: 80%; margin-right: 0.8em; text-align: right; padding-right: 0.5em }\ #settingsDialog li .more { width: 24px; border: none; color: #808080; font: bold 160% inital; line-height: 0%; transform: rotate(90deg); background-color: transparent; position: relative; top: 8px; cursor: pointer; height: 0.7em; padding: 0 0 0.65em; }\ #settingsDialog li#typeface input { width: 8em }\ .dialog-big { position: fixed; top: 2em; left: 50%; width: 860px; margin-left: -430px; background-color: white; padding: 2%; border: 1px solid #ddd; font-family: 'Oxygen'; box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.4); border-radius: 10px; }\ .dialog-big p { background-color: #f7f7f7; padding: 1.5em; height: 500px; overflow: scroll; }\ .dialog-big h2, #export-box h3 { font-weight: 400 }\ .dialog-big h2 { font-size: 170%; margin-top: 0 }\ .dialog-big button { background-color: #cecece; color: white; padding: 3px 10px; font-family: 'Oxygen'; border: none; border-radius: 3px; font-weight: bold; transition: background-color 0.3s; margin-right: 0.5em }\ .dialog-big button:hover { background-color: #55B8EA }\ .dialog-big textarea { display: block; width: 100%; height: 500px; margin: 1em 1em 1em 0; }\ "); } return { show: _show, hide: _cancel, isVisible: _isVisible } }(); function addWatchButton() { var type = (pageType.HIT) ? 'hit' : (pageType.REQUESTER) ? 'requester' : (pageType.SEARCH) ? 'page' : ''; var typeCase = (pageType.HIT) ? 'HIT' : type; var button = $("
").addClass("watcher_button") .append($("") .text("Watch this " + typeCase) .attr('href', "javascript:void(0)") .css({ color: 'black', border: '1px solid black' }) .click(addWatcher) ); function addWatcher() { // Get current and default values var time = 60, auto = true, alert = false, name = "", groupid = "", dateAdded = Math.floor(Date.now() / 1000), stopOnCatch = true, stopGroup = true, startGroup = true; // Find the name if available if (pageType.REQUESTER) { if ($(".title_orange_text_bold").length > 0) { name = $(".title_orange_text_bold").text().match(/Created by '(.+)'/); name = (typeof name !== 'undefined') ? name[1] : ""; } else if (document.URL.match(/prevRequester=/)) { name = document.URL.match(/prevRequester=([^&]*)/)[1]; } } else if (pageType.SEARCH) { name = document.URL.match(/searchWords=([^&]*)/); if (name !== null) { name = name[1].replace('+', ' '); name = name.charAt(0).toUpperCase() + name.slice(1); // Capitalize first letter } else { name = ""; } } else if (pageType.HIT) { name = $(".capsulelink_bold > div:nth-child(1)").text().trim(); } // Pull up a Watcher Dialog with default values set watcherDialog( { name: name, groupid: groupid, dateAdded: dateAdded, time: time * 1000, type: type, option: { auto : auto, alert : alert, stopOnCatch : stopOnCatch, stopGroup : stopGroup, startGroup : startGroup } }, function(values) { var id = (document.URL.match(/groupId=([A-Z0-9]+)/) || document.URL.match(/requesterId=([A-Z0-9]+)/) || [,document.URL])[1], watcher = { id : id, duration : values.time, type : (type === "page") ? "url" : type, name : values.name, groupid : values.groupid, dateAdded : dateAdded, auto : values.auto, alert : values.alert, stopOnCatch : values.stopOnCatch, stopGroup : values.stopGroup, startGroup : values.startGroup }; sendMessage({ header : 'add_watcher', content : watcher, timestamp : true }); } ); } var location; // Location to add the watch button if (pageType.HIT) { if ($(".message.success h6").length) { location = $(".message.success h6"); } else if ($("#javascriptDependentFunctionality").length) { if ($("td:contains('Want to see other')").length) { location = $("#javascriptDependentFunctionality").parents("tr").eq(0).children('td').eq(1); location.attr('align', 'center'); location.parents('td table').eq(0).attr('width', '450'); } else { location = $("#javascriptDependentFunctionality").parents("tr").eq(0); var newCell = ($("") .attr('rowspan', '2') .attr('align', 'center') ); location.append(newCell); location.parents("td table").eq(0).attr('width', '350'); location = location.children('td').eq(1); } var nextRowUp = location.parents("table").eq(0).children().eq(0).children().eq(0).children().eq(1); nextRowUp.remove("img"); nextRowUp.text('Want to watch this HIT?').attr('align', 'center').attr('width', '250'); } else if ($("body > form:nth-child(7) > table:nth-child(9) > tbody:nth-child(1) > tr:nth-child(1) > td:nth-child(2) > table:nth-child(1) > tbody:nth-child(1) > tr:nth-child(2) > td:nth-child(1)").length) { location = $("body > form:nth-child(7) > table:nth-child(9) > tbody:nth-child(1) > tr:nth-child(1) > td:nth-child(2) > table:nth-child(1) > tbody:nth-child(1) > tr:nth-child(2) > td:nth-child(1)"); } } else if (pageType.REQUESTER || pageType.SEARCH) { if ($(".title_orange_text_bold").length) location = $(".title_orange_text_bold"); else location = $(".error_title"); } location.append(button); addFormStyle(); } function addFormStyle() { addStyle("\ #add_watcher_form {\ position: fixed;\ width: 600px;\ top: 50px;\ left: 50%;\ margin: 50px -300px;\ background-color: #fcfcfe;\ border: 1px solid #aaa;\ border-radius: 1px;\ text-align: center;\ }\ #add_watcher_form h3 {\ font: 12pt Verdana;\ margin: 0 0 15px;\ background-color: #def;\ background-color: rgba(230, 230, 230, 1);\ padding: 3px;\ color: #111;\ }\ #add_watcher_form input[type='text'] {\ font: 10pt Verdana;\ margin: 10px 20px 0 0;\ }\ #add_watcher_form input[type='button'] {\ margin-top: 20px;\ font: 9pt Verdana;\ color: #444;\ background-color: #eee;\ border: 1px solid #999;\ }\ #add_watcher_form input[type='button']:hover {\ background-color: #9df;\ }\ #add_watcher_form p {\ margin: 10px;\ font: 11pt Verdana;\ }\ #add_watcher_form .form_buttons input {\ margin: 5px;\ }\ .watcher_button { display: inline; }\ .watcher_button a {\ text-decoration: none;\ font-weight: normal;\ background-color: #CECECE;\ color: white;\ padding: 3px 10px;\ border-radius: 8px;\ font-family: 'Oxygen', verdana, sans-serif;\ transition: background-color 0.4s;\ }\ .watcher_button a:hover { background-color: #55B8EA }\ .error_title .watcher_button { display: block; margin: 15px }\ "); } function addStyle(styleText) { var style = ''; $("head").append(style); } function loadDefaultWatchers() { // Add a few watchers. Won't be done like this in the future dispatch.isLoading = true; dispatch.add(new Watcher({ id: "https://www.mturk.com/mturk/searchbar?selectedSearchType=hitgroups&searchWords=survey&minReward=0.25&qualifiedFor=on&x=13&y=10", time: 60000, type: 'url', name: "Surveys $0.25 and up"})); //$.25 surveys dispatch.add(new Watcher({ id: "https://www.mturk.com/mturk/searchbar?selectedSearchType=hitgroups&searchWords=survey&minReward=0.75&qualifiedFor=on&x=13&y=10", time: 60000, type: 'url', name: "Surveys $0.75 and up"})); //$.75 surveys dispatch.add(new Watcher({ id: "A11L036EBWKONR", time: 120000, type: 'requester', name: "Project Endor", option: {alert:true}})); // Endor dispatch.add(new Watcher({ id: "A6YG5FKV2TAVC", time: 300000, type: 'requester', name: "Agent Agent", option: {alert:true}})); // Agent Agent dispatch.add(new Watcher({ id: "A2SUM2D7EOAK1T", time: 120000, type: 'requester', name: 'Crowdsource'})); dispatch.add(new Watcher({ id: "AKEBQYX32KM19", time: 120000, type: 'requester', name: 'Crowdsurf Support'})); dispatch.add(new Watcher({ id: "https://www.mturk.com/mturk/searchbar?selectedSearchType=hitgroups&searchWords=transcri&minReward=0.00&qualifiedFor=on&x=0&y=0", time: 60000, type: 'url', name: "Transcription HITs"})); // Transcription HITs dispatch.isLoading = false; dispatch.save(); } function onMessageReceived(header, message) { if (pageType.DASHBOARD && pageType.MAIN) { switch(header) { case 'notification_viewed' : wasViewed = true; break; case 'add_watcher' : var msg = message; dispatch.add(new Watcher({ id : msg.id, time : msg.duration, type : msg.type, groupid: msg.groupid, name : msg.name, option : { auto: msg.auto, stopOnCatch: msg.stopOnCatch, stopGroup: msg.stopGroup, startGroup: msg.startGroup, alert: msg.alert } }, true)).start(); break; case 'ignore_requester' : console.log("Ignore requester", message); IgnoreList.add(IgnoreList.REQUESTER, message.id); break; case 'mute_hit' : var id = message.id; IgnoreList.add(IgnoreList.HIT, id); break; case 'unmute_hit' : var id = message.id; IgnoreList.remove(IgnoreList.HIT, id); break; case 'request_main' : sendMessage({ header: "request_denied" }); break; case 'request_denied' : dispatch.onRequestMainDenied(); break; case 'show_main' : alert("Showing the main dashboard. (Close this Mturk page to establish a notifier in a different tab or window)"); break; } } else if (!pageType.DASHBOARD || (pageType.DASHBOARD && !pageType.MAIN)) { switch(header) { case 'new_hits' : var hits = message.hits; // Re-create the hits so their methods can be used for(var i = hits.length; i--;) hits[i] = new Hit(hits[i]); // Show the hits and let the dashboard know it was seen if (document.hasFocus()) sendMessage({ header: "notification_viewed" }); notificationPanel.add(new NotificationGroup({ title: message.title, hits: hits, url: message.url })); break; case 'captcha' : if (document.hasFocus()) alert("Captcha Alert!"); break; case 'turkopticon' : // This needs a more elegant solution. If the servers start lagging we might be // using addTO() for the wrong group. It won't show the TO for the wrong requester, // though, so it's safe to use for now. It's just that some ratings could be missing. notificationPanel.notifications[notificationPanel.notifications.length - 1].addTO(message); break; } } } function sendMessage(message) { var header = message.header; var content = message.content || new Date().getTime(); // Make the content a timestamp when there's no actual content var timestamp = message.timestamp && new Date().getTime(); // If wanted, adds a timestamp to the content so messages with the same content will still trigger the event consecutively localStorage.setItem('notifier_msg_' + header, JSON.stringify({ content: content, timestamp: timestamp})); } function sendDesktopNotification(hits, watcher) { // Let's check if the user is okay to get some notification if (Notification.permission === "granted" && settings.desktopNotifications) { // If the user isn't on a mturk page to receive a rich notification, then send a web notification if (!wasViewed) { var bodyText = ""; for (var i = 0, len = hits.length; i < len; i++) bodyText += "\n" + hits[i].title.substring(0, 40) + ((hits[i].title.length > 40) ? "..." : "") + "\n" + hits[i].reward + "\n"; var notification = new Notification( watcher.name, { body: bodyText, icon: "http://halfelf.org/wp-content/uploads/sites/2/2012/06/amazon_icon.png" } ); notification.onclick = function() { window.focus(); // Focus this window (dashboard) this.close(); // Closes the notification showDetailsPanel(watcher); // Opens the details panel for whatever watcher the notification was for }; notification.onshow = function() { setTimeout(function() { notification.close() }, 5000) }; // Need to set a close time for Chrome } } } function requestDesktopNotifications(callback) { // Let's check if the browser supports notifications if (!("Notification" in window)) { alert("This browser does not support desktop notification"); } else { window.Notification.requestPermission(function (permission) { // Whatever the user answers, we make sure Chrome stores the information if(!('permission' in Notification)) window.Notification.permission = permission; // If the user is okay, let's create a notification if (permission === "granted") { var notification = new window.Notification("Desktop notifications enabled."); notification.onshow = function() { setTimeout(function() { notification.close() }, 5000) }; callback(true); } else { callback(false); } }); } } function Hit(attrs) { attrs = attrs || {}; this.id = attrs.id; this.uid = attrs.uid; this.isAutoAccept = attrs.isAutoAccept || false; this.requester = attrs.requester; this.requesterID = attrs.requesterID; this.url = attrs.url; this.title = attrs.title; this.reward = attrs.reward; this.description = attrs.description; this.available = attrs.available; this.time = attrs.time; this.isQualified = (typeof attrs.isQualified !== 'undefined') ? attrs.isQualified : true; this.canPreview = (typeof attrs.canPreview !== 'undefined') ? attrs.canPreview : true; } Hit.prototype.getURL = function(type) { switch(type) { case 'preview': return "https://www.mturk.com/mturk/preview?groupId=" + this.id; case 'accept' : return (this.isQualified) ? "https://www.mturk.com/mturk/previewandaccept?groupId=" + this.id : null; case 'auto' : return "https://www.mturk.com/mturk/previewandaccept?groupId=" + this.id + "&autoAcceptEnabled=true"; case 'view' : return "https://www.mturk.com/mturk/continue?hitId=" + this.uid; case 'return' : // This will need to be changed. It's the same as 'view' until more testing is done on AMT's return functionality return "https://www.mturk.com/mturk/preview?hitId=" + this.uid; default: return ""; } }; // Returns the position of a hit in a hit array by its ID Hit.indexOf = function(hitId, hits) { for (var i = 0, len = hits.length; i < len; i++) { if (hitId === hits[i].id) return i; } return -1; }; // Returns true if there are multiple hits in the array and all of the hits are from the same requester Hit.isSameRequester = function(hits) { if (hits.length > 1) { var compareRequester = hits[0].requester; for (var i = 1, len = hits.length; i < len; i++) { if (compareRequester !== hits[i].requester) return false; } return true; } else { return false; } }; // Returns a list of unique requester IDs from an array of hits Hit.getUniqueReqeusters = function(hits) { var ids = []; for (var i = 0, len = hits.length; i < len; ++i) { var id = hits[i].requesterID; if (ids.indexOf(id) === -1) ids.push(id); } return ids; }; // Message object (Not used) function Message() { /* Status (changed): Unchanged, Added, Removed, Count We should mark each Hit in the message with what has changed. The count change should be sent with this. The message will also tell the client whether or not to pop-up the notification. */ } // The details panel for each watcher function createDetailsPanel() { var div = $('
').attr('id', 'details_panel').addClass('notification_panel'); addStyle("#details_panel {\ background-color: #fff;\ position: absolute;\ top: 0px;\ margin-left: 1px;\ left: 270px;\ width: 500px;\ border: 1px solid #e3e3e3;\ border-radius: 0 0 3px 0;\ border-width: 0 1px 1px 0;\ transition: left 0.5s ease;\ display: none }\ #details_panel h4 { display: none }\ #details_panel.left { left: 30px }"); $(div).mouseleave(function() { $(this).hide() }); $("body").append(div); } var lastWatcher = ""; function showDetailsPanel(watcher) { var panel = $("#details_panel"); var group; // Only change the panel contents if it's a different watcher or the same one, but updated if (watcher !== lastWatcher || (watcher === lastWatcher && watcher.isUpdated)) { $("*", panel).remove(); if (watcher.lastHits.length > 0) { group = new NotificationGroup({ hits: watcher.lastHits, isSticky: false, watcher: watcher }); $(panel).append((group).getDOMElement()); // This doesn't need a callback since the data will already be cached at this point group.addTO(TO.get(Hit.getUniqueReqeusters(watcher.lastHits), _handleTOReceived)); } else { $(panel).append($('
').append('

').css('text-align', 'center').html("
There are no HITs available.

")); } } $(panel).show(); function _handleTOReceived(data) { group.addTO(data); } lastWatcher = watcher; } var IgnoreList = (function() { var _time = 60000, _hits = []; _requesters = [], _HIT = 0, _REQUESTER = 1; function _init() { // _clear(); _load(); _addListeners(); } function _addListeners() { $(window).on('unload', function() { _save(); }); } function _save() { localStorage.setItem('notifier_ignore', JSON.stringify(_hits)); localStorage.setItem('notifier_ignore_requesters', JSON.stringify(_requesters)); // console.log("Saving ignore list", _hits, _requesters); } function _load() { var storedHits = localStorage.getItem('notifier_ignore'); var storedRequesters = localStorage.getItem('notifier_ignore_requesters'); if (storedHits !== null) { try { _hits = JSON.parse(storedHits); } catch (e) { _clear(); _save(); console.log("Ignored hits couldn't be loaded correctly."); } } else { console.log("No ignored hits found"); } if (storedRequesters !== null) { try { _requesters = JSON.parse(storedRequesters); } catch (e) { _clear(); _save(); console.log("Ignored requesters couldn't be loaded correctly."); } } else { console.log("No ignored requesters found"); } // console.log("Ignored requesters", _requesters); } function _clear() { _hits = []; _requesters = []; localStorage.removeItem('notifier_ignore'); localStorage.removeItem('notifier_ignore_requesters'); } function _contains(type, item) { if (type === _HIT) return (_hits.indexOf(item) !== -1); else return (_requesters.indexOf(item) !== -1); } function _isIgnored(requester) { return (_requesters.indexOf(requester) !== -1); } function _isMuted(item) { return (_hits.indexOf(item) !== -1); } function _filter(hits) { var filteredHits = []; for (var i = 0, len = hits.length; i < len; i++) { var hit = hits[i]; if ((_hits.indexOf(hit.id) === -1) && (_requesters.indexOf(hit.requester) === -1)) filteredHits.push(hit); } return filteredHits; } function _add(type, id) { if (type === _HIT) { if (_hits.indexOf(id) === -1) _hits.push(id); } else if (type === _REQUESTER) { if (_requesters.indexOf(id) === -1) _requesters.push(id); } _save(); } function _remove(type, id) { if (type === _HIT) { var index = _hits.indexOf(id); if (index !== -1) _hits.splice(index, 1); } else if (type === _REQUESTER) { var index = _requesters.indexOf(id); if (index !== -1) _requesters.splice(index, 1); } _save(); } return { init: _init, add: _add, remove: _remove, filter: _filter, isMuted: _isMuted, isIgnored: _isIgnored, HIT: _HIT, REQUESTER: _REQUESTER } })(); function Evt() { /* Nothing */ }; Evt.ADD = 1; Evt.REMOVE = 2; Evt.START = 3; Evt.STOP = 4; Evt.CHANGE = 5; Evt.UPDATE = 6; Evt.HITS_CHANGE = 7; Evt.DELETE = 8; Evt.VIEW_DETAILS = 9; Evt.prototype.addListener = function(type, callback) { switch(type) { case Evt.ADD: this.listener.onadd.push(callback); break; case Evt.REMOVE: this.listener.onremove.push(callback); break; case Evt.START: this.listener.onstart.push(callback); break; case Evt.STOP: this.listener.onstop.push(callback); break; case Evt.CHANGE: this.listener.onchange.push(callback); break; case Evt.UPDATE: this.listener.onupdate.push(callback); break; case Evt.HITS_CHANGE: this.listener.onhitschange.push(callback); break; case Evt.DELETE: this.listener.ondelete.push(callback); break; case Evt.VIEW_DETAILS: this.listener.onviewdetails.push(callback); break; default: console.error("Invalid Event type in addListener()"); } } Evt.prototype.notify = function(type, data) { switch(type) { case Evt.ADD: this.callFunctionArray(this.listener.onadd, data); break; case Evt.REMOVE: this.callFunctionArray(this.listener.onremove, data); break; case Evt.START: this.callFunctionArray(this.listener.onstart, data); break; case Evt.STOP: this.callFunctionArray(this.listener.onstop, data); break; case Evt.CHANGE: this.callFunctionArray(this.listener.onchange, data); break; case Evt.UPDATE: this.callFunctionArray(this.listener.onupdate, data); break; case Evt.HITS_CHANGE: this.callFunctionArray(this.listener.onhitschange, data); break; case Evt.DELETE: this.callFunctionArray(this.listener.ondelete, data); break; case Evt.VIEW_DETAILS: this.callFunctionArray(this.listener.onviewdetails, data); break; default: console.error("Unknown event type:", type); } } Evt.prototype.callFunctionArray = function(functions, data) { if (functions.length > 0) for (var i = 0, len = functions.length; i < len; i++) functions[i](data); } var DispatchUI = { create: function(dispatch) { DispatchUI.dispatch = dispatch; DispatchUI.init(); DispatchUI.addStyle(); DispatchUI.addActions(); DispatchUI.addListeners(); DispatchUI.addDragAndDrop(); return DispatchUI.div; }, init: function() { var div = DispatchUI.div = $("
").attr('id', "dispatcher") .append($("
").attr('id', "controller")) .append($("
").attr('id', "watcher_container")); DispatchUI.watchers = []; // Move dashboard contents to the right and put the dispatch panel on the left var pageElements = $("body > *"); $("body").html(""); $("body").append( $("
") .attr('id', "content_container") ); if (settings.customURL) { $("#content_container").append($("