// ==UserScript== // @name HIT Forker // @version 1.2.4.3 // @description Monitors mturk.com for HITs // @author ThisPoorGuy // @icon https://i.imgur.com/RaPUMRP.png // @exclude https://worker.mturk.com/direct_deposit* // @exclude https://worker.mturk.com/payment_schedule* // @include https://worker.mturk.com/?finder_beta_test // @include https://worker.mturk.com/?hit_forker // @grant GM_log // @grant GM_setClipboard // @grant GM_xmlhttpRequest // @connect turkerview.com // @require https://code.jquery.com/jquery-3.1.0.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/dompurify/1.0.8/purify.min.js // @namespace https://greasyfork.org/users/163167 // @downloadURL https://update.greasyfork.icu/scripts/36368/HIT%20Forker.user.js // @updateURL https://update.greasyfork.icu/scripts/36368/HIT%20Forker.meta.js // ==/UserScript== // Acknowledgements // The core of this script was forked/stolen/adapted from Kadauchi's Hit Finder Beta script. Coding assistance in spots // provided by Salem Beats and ChrisTurk. TurkerView was created by ChrisTurk. This script hooks into JR Panda Crazy // Slothbear provided the code for text to speech, thanks! // Changelog // 1.2.4.3 - Fixed API domain references // 1.2.4.2 - Fixed hotlinking of sound files that began returning the incorrect sound file. // 1.2.4.1 - Fixed issue with sounds not playing. THANKS PHIL. // 1.2.4 - Added SLACK export type // 1.2.3 - Added an exclusion to specifically prevent running on the direct deposit pages now that amazon is dealing in bank information. While not strictly necessary, this update is intended to improve security by specifically disallowing this page. Hit Forker does not collect or transmit any personal or financial information. // 1.2.1 - Fixed bug with TO API call to catch JSON error (Thanks THFYM for the tip!) // 1.2 - Added option to turn off TurkerView if you desire such things. // 1.1.1 = Fixed an issue with the TV API where it will stop if it encounters an error // 1.1 - Added new TurkerView API code // 1.0.6.1 - Further input sanatization improvements // 1.0.5 - Minor update to change the soundjay links to https to stop mixed content complaining. // 1.0.4 - Added an I button to the HIT log/HIT list so you can just click once to add something to the include list. // 1.0.3 - Further work on input sanitization // 1.0.2 - Fixed the issue where some asshat decided to inject code into a HIT name. THIS IS WHY WE CAN'T HAVE NICE THINGS. // 1.0.1 - Fixed IRC Export function. You're welcome one guy using IRC exports. // 1.0.0 - Added Text to speech for include list hits. There's a TTS checkbox in the show config section, this will override any sounds set up on your include list with a // text to speech notification. In order for the alert to trigger you need an include list entry with play sound enabled. Which sound you pick won't matter as this // will ignore that setting and use a vocal alert. Thanks to slothbear for the code. // 0.8.1.2 - Fixed an issue exporting HITS that have quotes in the title. // 0.8.1.1 - Added a limit to the number of characters you can stick into the fields on adding something to the block list to prevent accidental pasting of a block list import into the wrong spot. // 0.8.1 - Fixed a couple of export formatting bugs. // 0.8.0 - Added the ability to press a button to launch a qualification test if you don't qualify for something and a test is availible. // Working on adding requesting for auto-grant quals, but that's...trickier. // 0.7.6 - Fixed issue with the search qualified check box not working. // 0.7.5 - Added abilible hits to the Log display. Note this only shows the data from the time when the HIT was first seen, not any subsequent scans of it. // 0.7.4 - Removed column for masters, it wasn't used for anything and was broken anyway. Also removed hide masters because it worked off of that code, which wasn't working anyway // 0.7.3 - Returned a semi-colon to it's rightful position, even though it wasn't missed // 0.7.2 - Made it so that the Panda button sets pandas without once=true set. Whoops. // 0.7.0 - Big under the hood changes with how TV and TO review scores are gathered. // 0.6.5 - Further modifications to prevent availibility of external sites causing HF to stop randomly. // 0.6.4 - Send RequesterID over to PandaCrazy along with the other info. // 0.6.3 - Bringing PC functionality in house // 0.6.1 - Fixed panda button shading // 0.6.0 - Re-jiggered panda crazy integration code to use Salem's PC library. Now detects if panda crazy is actually running when you click. // 0.5.3 - Slight modifications to break integration with JR Panda Crazy, due to a memory leak issue. Will reach out to dev to see if we can clear it up and be happy together... // 0.5.2 - Code cleanup. // 0.5.1 - Fixed the panda buttons on the Found hits table. // 0.5.0 - Hitting the panda button now sends full HIT name, pay and requester name info to panda crazy too. Have you noticed these version number leaps are pretty arbitratry? // - Fixed a stupid typo in a variable name. // 0.4.3 - Switched TO request from a .get call to a .ajax call with a timeout to prevent the entire thing from exploding when TO's servers do. // 0.4.2 - Link to TV Requester profile in export. // 0.4.1 - Show/Hide HITs and Logged Hits settings are now saved across sessions // 0.4.0 - Blocking a HIT or a Requester will now remove that Hit or all hits from said requester from display in the Hit Log // 0.3.5 - Added a button to hide the new HITS table. Moved the button to hide the logged hits for consistancy. // 0.3.2 - Modified icon for desktop notifications. Added Requester TV score to hit export // 0.3.1 - Fix for amazon screwing with things. Thanks ChrisTurk! // 0.3.0 - Under the hood changes, removed code for running on www, added new launch URL, old ?finder_beta will be phased out eventually // 0.2.9 - Now acceptable to people with red/green color blindness! // 0.2.6 - Fixed a minor error which caused colors to not work properly. // 0.2.5 - Changed Coloration to respect TV reviews FIRST and then fall back on TO Values. Also changed colors. // 0.2.0 - Added TurkerView Hourly ratings to HIT results, fixed export links. // 0.1.5 - Some minor UI tweaks // 0.1.4 - Added some indication that you have already clicked a button to send HIT to PC. Only works in log currently. // 0.1.3 - Fixed issue with the Panda buttons in the HIT log not having the right GID // 0.1.2 - Fixed an issue with HITs that have double quotes in the title not working with the ignore hit by title button. I think. // 0.1.1 - Cleaned up the header, removed unused audio files // 0.1.0 - Made modifications to launch links with worker website. Added buttons to send information to PandaCrazy directly instead of copying link // TODO: // Remove www code // Clean up interface // Delete the above todos because they're already done. const ver = GM_info.scriptMetaStr.match(/version.*?(\d+.*)/)[1]; var worker = true; var _config = JSON.parse(localStorage.getItem('_finder')) || {}; _config.tv_api_key = localStorage.getItem('turkerview_api_key') || ''; var blocklist = JSON.parse(localStorage.getItem('_finder_bl')) || {}; var includelist = JSON.parse(localStorage.getItem('_finder_il')) || {}; // Compatability check if (_config.version !== '1.1') { _config = {}; } var config = { version : _config.version || '1.1', delay : _config.delay || '3', type : _config.type || 'LastUpdatedTime%3A1&pageSize=', size : _config.size || '25', rew : _config.rew || '0.00', avail : _config.avail || '0', mto : _config.mto || '0.00', alert : _config.alert || '0', qual : _config.hasOwnProperty('qual') ? _config.qual : true, new : _config.hasOwnProperty('new') ? _config.new : true, newaudio : _config.newaudio || 'beep', pb : _config.hasOwnProperty('pb') ? _config.pb : false, to : _config.hasOwnProperty('to') ? _config.to : false, tv : _config.hasOwnProperty('tv') ? _config.tv : true, nl : _config.hasOwnProperty('nl') ? _config.nl : false, bl : _config.hasOwnProperty('bl') ? _config.bl : false, m : _config.hasOwnProperty('m') ? _config.m : false, tts : _config.hasOwnProperty('tts') ? _config.tts : false, push : _config.push || 'access_token_here', tv_api_key : _config.tv_api_key || '', disable_tv : _config.disable_tv || false, theme : _config.theme || 'default', custom : _config.custom || {main: 'FFFFFF', primary: 'CCCCCC', secondary: '111111', text: '000000', link: '0000EE', visited: '551A8B', prop : false}, to_theme : _config.to_theme || '1', h_hidden : _config.h_hidden || '0', l_hidden : _config.l_hidden || '0' }; console.log(config); console.log(config.tv_api_key); var themes = { 'default' : {main: 'FFFFFF', primary: 'CCCCCC', secondary: '111111', menu: '373b44', menutext: 'FFFFFF', text: '000000', link: '0000EE', visited: '551A8B', prop : true}, 'light' : {main: 'FFFFFF', primary: 'CCCCCC', secondary: '111111', menu: '373b44', menutext: 'FFFFFF', text: '000000', link: '0000EE', visited: '551A8B', prop : true}, 'dark' : {main: '404040', primary: '666666', secondary: 'FFFFFF', menu: '202020', menutext: 'FFFFFF', text: 'FFFFFF', link: 'FFFFFF', visited: 'B3B3B3', prop : true}, 'darker' : {main: '000000', primary: '262626', secondary: 'FFFFFF', menu: '373b44', menutext: 'FFFFFF', text: 'FFFFFF', link: 'FFFFFF', visited: 'B3B3B3', prop : true}, 'custom' : config.custom }; var turkerview = { }; var turkerview_update = 0; var requesters = [ ]; var tvTimeoutCache = [ ]; var searches = 0, logged = 0, hitlog = {}, noti_delay = [], push_delay = []; const ViewHeaders = new Headers([ ['X-VIEW-KEY', config.tv_api_key], ['X-APP-KEY', 'HIT Forker'], ['X-APP-VER', ver] //SemVer ]); // General Configuration variables var url, upd, num, rew, minrew, searchqual, pandaurl; url = 'https://worker.mturk.com/?'; pandaurl = 'https://worker.mturk.com'; upd = '&sort=updated_desc&page_size='; num = '&sort=num_hits_desc&page_size='; rew = '&sort=reward_desc&page_size='; minrew = '&filters%5Bmin_reward%5D='; searchqual = '&filters%5Bqualified='; var PandaCrazy = (function createPandaCrazy() { let _self = this; let _lastSentPingTime; let _lastReceivedPongTime; let _onlineSinceLastPing; let _pcListener; const MAX_WAIT_FOR_PANDA_CRAZY_RESPONSE_MS = 1000; function ping() { _lastSentPingTime = Date.now(); localStorage.setItem("JR_message_ping_pandacrazy", `{"theTarget": "${Math.random()}"}`); } function hasIndicatedOnlineSinceLastPing() { if(_lastSentPingTime !== undefined && _lastReceivedPongTime !== undefined) { return _lastReceivedPongTime >= _lastSentPingTime; } else { return undefined; } } function online() { function respondToStorage(resolve, reject, e) { if(e.key.includes("JR_message_pong") && Boolean(e.newValue)) { _lastReceivedPongTime = Date.now(); let pongData = JSON.parse(e.newValue); let lag = Number(pongData.time) - Number(_lastReceivedPongTime); if(hasIndicatedOnlineSinceLastPing()) { resolve("online"); } } } let isOnlinePromise = new Promise((resolve, reject) => { setTimeout(() => {reject("timeout");}, MAX_WAIT_FOR_PANDA_CRAZY_RESPONSE_MS); if(_pcListener) {window.removeEventListener("storage", _pcListener);} _pcListener = respondToStorage.bind(window, resolve, reject); window.addEventListener("storage", _pcListener); /* window.addEventListener("storage", e => { // console.log("Storage Event", e); if(e.key.includes("JR_message_pong") && Boolean(e.newValue)) { _lastReceivedPongTime = Date.now(); let pongData = JSON.parse(e.newValue); let lag = Number(pongData.time) - Number(_lastReceivedPongTime); if(hasIndicatedOnlineSinceLastPing()) { resolve("online"); } } }); */ }); ping(); return isOnlinePromise; } function addJob(gid, once, metadata) { let commandString = once ? "addOnceJob" : "addJob"; localStorage.setItem("JR_message_pandacrazy", JSON.stringify({ time: Date.now(), command: commandString, data: { groupId: gid, title: (metadata ? metadata.hitTitle || metadata.title : undefined), requesterName: (metadata ? metadata.requesterName : undefined), requesterId: (metadata ? metadata.requesterID || metadata.requesterId || metadata.rid : undefined), pay: (metadata ? metadata.hitValue || metadata.pay : undefined), duration: (metadata ? metadata.duration : undefined), hitsAvailable: (metadata ? metadata.hitsAvailable : undefined) } })); } function startJob(gid) { localStorage.setItem("JR_message_pandacrazy", JSON.stringify({ time: Date.now(), command: "startcollect", data: { groupId: gid } })); } return { addJob, startJob, ping, online }; })(); const SPEECH_VOICE = 3; //0 - 21ish const SPEECH_RATE = 0.9; //1 - 10 (default is 1) const SPEECH_VOLUME = 1; //0 - 1 (default is 1) const SPEECH_LANG = 'en-US'; //(default is 'en') //this is what does it all! unsafeWindow.slothbearsTTS = function(obj) { let phrase = "Hit Found!" + obj.name; var speech = new Speech(); if (speech.supported()) { speech.speak(phrase); } }; var Speech = function() { }; Speech.voices = null; (function() { if ('speechSynthesis' in window) { // First call to getVoices may be null...later an event indicates when it is loaded Speech.voices = window.speechSynthesis.getVoices(); // Save voices when loaded after first call window.speechSynthesis.onvoiceschanged = function() { Speech.voices = window.speechSynthesis.getVoices(); }; } })(); Speech.prototype.supported = function() { return Speech.voices !== null; }; Speech.prototype.speak = function(text) { if (Speech.voices !== null) { var speech = new SpeechSynthesisUtterance(text); speech.rate = SPEECH_RATE; speech.voice = speechSynthesis.getVoices()[SPEECH_VOICE]; speech.lang = SPEECH_LANG; speech.volume = SPEECH_VOLUME; window.speechSynthesis.speak(speech); } }; $('head').html( '