Advertisement
Guest User

Untitled

a guest
Feb 11th, 2020
109
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // ==UserScript==
  2. // @name         HIT Forker
  3. // @version      1.2.4
  4. // @description  Monitors mturk.com for HITs
  5. // @author       ThisPoorGuy
  6. // @icon         https://i.imgur.com/RaPUMRP.png
  7. // @exclude      https://worker.mturk.com/direct_deposit*
  8. // @exclude      https://worker.mturk.com/payment_schedule*
  9. // @include      https://worker.mturk.com/?finder_beta_test
  10. // @include      https://worker.mturk.com/?hit_forker
  11. // @grant        GM_log
  12. // @grant        GM_setClipboard
  13. // @grant        GM_xmlhttpRequest
  14. // @connect      turkerview.com
  15. // @require      https://code.jquery.com/jquery-3.1.0.min.js
  16. // @require      https://cdnjs.cloudflare.com/ajax/libs/dompurify/1.0.8/purify.min.js
  17.  
  18. // @namespace https://greasyfork.org/users/163167
  19. // ==/UserScript==
  20.  
  21. // Acknowledgements
  22. // The core of this script was forked/stolen/adapted from Kadauchi's Hit Finder Beta script. Coding assistance in spots
  23. // provided by Salem Beats and ChrisTurk. TurkerView was created by ChrisTurk. This script hooks into JR Panda Crazy
  24. // Slothbear provided the code for text to speech, thanks!
  25.  
  26. // Changelog
  27. // 1.2.4 - Added SLACK export type
  28. // 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.
  29. // 1.2.1 - Fixed bug with TO API call to catch JSON error (Thanks THFYM for the tip!)
  30. // 1.2 - Added option to turn off TurkerView if you desire such things.
  31. // 1.1.1 = Fixed an issue with the TV API where it will stop if it encounters an error
  32. // 1.1 - Added new TurkerView API code
  33. // 1.0.6.1 - Further input sanatization improvements
  34. // 1.0.5 - Minor update to change the soundjay links to https to stop mixed content complaining.
  35. // 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.
  36. // 1.0.3 - Further work on input sanitization
  37. // 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.
  38. // 1.0.1 - Fixed IRC Export function. You're welcome one guy using IRC exports.
  39. // 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
  40. //         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
  41. //         will ignore that setting and use a vocal alert. Thanks to slothbear for the code.
  42. // 0.8.1.2 - Fixed an issue exporting HITS that have quotes in the title.
  43. // 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.
  44. // 0.8.1 - Fixed a couple of export formatting bugs.
  45. // 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.
  46. //         Working on adding requesting for auto-grant quals, but that's...trickier.
  47. // 0.7.6 - Fixed issue with the search qualified check box not working.
  48. // 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.
  49. // 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
  50. // 0.7.3 - Returned a semi-colon to it's rightful position, even though it wasn't missed
  51. // 0.7.2 - Made it so that the Panda button sets pandas without once=true set. Whoops.
  52. // 0.7.0 - Big under the hood changes with how TV and TO review scores are gathered.
  53. // 0.6.5 - Further modifications to prevent availibility of external sites causing HF to stop randomly.
  54. // 0.6.4 - Send RequesterID over to PandaCrazy along with the other info.
  55. // 0.6.3 - Bringing PC functionality in house
  56. // 0.6.1 - Fixed panda button shading
  57. // 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.
  58. // 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...
  59. // 0.5.2 - Code cleanup.
  60. // 0.5.1 - Fixed the panda buttons on the Found hits table.
  61. // 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?
  62. //       - Fixed a stupid typo in a variable name.
  63. // 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.
  64. // 0.4.2 - Link to TV Requester profile in export.
  65. // 0.4.1 - Show/Hide HITs and Logged Hits settings are now saved across sessions
  66. // 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
  67. // 0.3.5 - Added a button to hide the new HITS table. Moved the button to hide the logged hits for consistancy.
  68. // 0.3.2 - Modified icon for desktop notifications. Added Requester TV score to hit export
  69. // 0.3.1 - Fix for amazon screwing with things. Thanks ChrisTurk!
  70. // 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
  71. // 0.2.9 - Now acceptable to people with red/green color blindness!
  72. // 0.2.6 - Fixed a minor error which caused colors to not work properly.
  73. // 0.2.5 - Changed Coloration to respect TV reviews FIRST and then fall back on TO Values. Also changed colors.
  74. // 0.2.0 - Added TurkerView Hourly ratings to HIT results, fixed export links.
  75. // 0.1.5 - Some minor UI tweaks
  76. // 0.1.4 - Added some indication that you have already clicked a button to send HIT to PC. Only works in log currently.
  77. // 0.1.3 - Fixed issue with the Panda buttons in the HIT log not having the right GID
  78. // 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.
  79. // 0.1.1 - Cleaned up the header, removed unused audio files
  80. // 0.1.0 - Made modifications to launch links with worker website. Added buttons to send information to PandaCrazy directly instead of copying link
  81. // TODO:
  82. // Remove www code
  83. // Clean up interface
  84. // Delete the above todos because they're already done.
  85.  
  86. const ver = GM_info.scriptMetaStr.match(/version.*?(\d+.*)/)[1];
  87. var worker = true;
  88.  
  89. var _config     = JSON.parse(localStorage.getItem('_finder'))    || {};
  90. _config.tv_api_key = localStorage.getItem('turkerview_api_key') || '';
  91. var blocklist   = JSON.parse(localStorage.getItem('_finder_bl')) || {};
  92. var includelist = JSON.parse(localStorage.getItem('_finder_il')) || {};
  93.  
  94. // Compatability check
  95. if (_config.version !== '1.1') { _config = {}; }
  96.  
  97. var config = {
  98.     version  : _config.version  || '1.1',
  99.     delay    : _config.delay    || '3',
  100.     type     : _config.type     || 'LastUpdatedTime%3A1&pageSize=',
  101.     size     : _config.size     || '25',
  102.     rew      : _config.rew      || '0.00',
  103.     avail    : _config.avail    || '0',
  104.     mto      : _config.mto      || '0.00',
  105.     alert    : _config.alert    || '0',
  106.     qual     : _config.hasOwnProperty('qual') ? _config.qual : true,
  107.     new      : _config.hasOwnProperty('new')  ? _config.new  : true,
  108.     newaudio : _config.newaudio || 'beep',
  109.     pb       : _config.hasOwnProperty('pb')   ? _config.pb   : false,
  110.     to       : _config.hasOwnProperty('to')   ? _config.to   : false,
  111.     tv       : _config.hasOwnProperty('tv')   ? _config.tv   : true,
  112.     nl       : _config.hasOwnProperty('nl')   ? _config.nl   : false,
  113.     bl       : _config.hasOwnProperty('bl')   ? _config.bl   : false,
  114.     m        : _config.hasOwnProperty('m')    ? _config.m    : false,
  115.     tts      : _config.hasOwnProperty('tts')  ? _config.tts  : false,
  116.     push     : _config.push     || 'access_token_here',
  117.     tv_api_key : _config.tv_api_key || '',
  118.     disable_tv : _config.disable_tv || false,
  119.     theme    : _config.theme    || 'default',
  120.     custom   : _config.custom   ||  {main: 'FFFFFF', primary: 'CCCCCC', secondary: '111111', text: '000000', link: '0000EE', visited: '551A8B', prop : false},
  121.     to_theme : _config.to_theme || '1',
  122.     h_hidden : _config.h_hidden || '0',
  123.     l_hidden : _config.l_hidden || '0'
  124. };
  125. console.log(config);
  126. console.log(config.tv_api_key);
  127. var themes = {
  128.     'default' : {main: 'FFFFFF', primary: 'CCCCCC', secondary: '111111', menu: '373b44', menutext: 'FFFFFF', text: '000000', link: '0000EE', visited: '551A8B', prop : true},
  129.     'light'   : {main: 'FFFFFF', primary: 'CCCCCC', secondary: '111111', menu: '373b44', menutext: 'FFFFFF', text: '000000', link: '0000EE', visited: '551A8B', prop : true},
  130.     'dark'    : {main: '404040', primary: '666666', secondary: 'FFFFFF', menu: '202020', menutext: 'FFFFFF', text: 'FFFFFF', link: 'FFFFFF', visited: 'B3B3B3', prop : true},
  131.     'darker'  : {main: '000000', primary: '262626', secondary: 'FFFFFF', menu: '373b44', menutext: 'FFFFFF', text: 'FFFFFF', link: 'FFFFFF', visited: 'B3B3B3', prop : true},
  132.     'custom'  : config.custom
  133. };
  134.  
  135. var turkerview = { };
  136. var turkerview_update = 0;
  137. var requesters = [ ];
  138. var tvTimeoutCache = [ ];
  139.  
  140. var searches    = 0,
  141.     logged      = 0,
  142.     hitlog      = {},
  143.     noti_delay  = [],
  144.     push_delay = [];
  145.  
  146. const ViewHeaders = new Headers([
  147.     ['X-VIEW-KEY', config.tv_api_key],
  148.     ['X-APP-KEY', 'HIT Forker'],
  149.     ['X-APP-VER', ver] //SemVer
  150. ]);
  151.  
  152. // General Configuration variables
  153. var url, upd, num, rew, minrew, searchqual, pandaurl;
  154.  
  155. url    = 'https://worker.mturk.com/?';
  156. pandaurl = 'https://worker.mturk.com';
  157. upd    = '&sort=updated_desc&page_size=';
  158. num    = '&sort=num_hits_desc&page_size=';
  159. rew    = '&sort=reward_desc&page_size=';
  160. minrew = '&filters%5Bmin_reward%5D=';
  161. searchqual = '&filters%5Bqualified=';
  162.  
  163. var PandaCrazy = (function createPandaCrazy() {
  164.     let _self = this;
  165.  
  166.     let _lastSentPingTime;
  167.     let _lastReceivedPongTime;
  168.  
  169.     let _onlineSinceLastPing;
  170.  
  171.     let _pcListener;
  172.  
  173.     const MAX_WAIT_FOR_PANDA_CRAZY_RESPONSE_MS = 1000;
  174.  
  175.     function ping() {
  176.         _lastSentPingTime = Date.now();
  177.         localStorage.setItem("JR_message_ping_pandacrazy", `{"theTarget": "${Math.random()}"}`);
  178.     }
  179.  
  180.     function hasIndicatedOnlineSinceLastPing() {
  181.         if(_lastSentPingTime !== undefined && _lastReceivedPongTime !== undefined) {
  182.             return _lastReceivedPongTime >= _lastSentPingTime;
  183.         }
  184.         else {
  185.             return undefined;
  186.         }
  187.     }
  188.  
  189.     function online() {
  190.  
  191.         function respondToStorage(resolve, reject, e) {
  192.             if(e.key.includes("JR_message_pong") && Boolean(e.newValue)) {
  193.  
  194.                 _lastReceivedPongTime = Date.now();
  195.  
  196.                 let pongData = JSON.parse(e.newValue);
  197.  
  198.                 let lag = Number(pongData.time) - Number(_lastReceivedPongTime);
  199.  
  200.                 if(hasIndicatedOnlineSinceLastPing()) {
  201.                     resolve("online");
  202.                 }
  203.             }
  204.         }
  205.  
  206.         let isOnlinePromise = new Promise((resolve, reject) => {
  207.  
  208.             setTimeout(() => {reject("timeout");}, MAX_WAIT_FOR_PANDA_CRAZY_RESPONSE_MS);
  209.  
  210.             if(_pcListener) {window.removeEventListener("storage", _pcListener);}
  211.  
  212.             _pcListener = respondToStorage.bind(window, resolve, reject);
  213.  
  214.             window.addEventListener("storage", _pcListener);
  215.  
  216.             /*
  217.              window.addEventListener("storage", e => {
  218.  
  219.              // console.log("Storage Event", e);
  220.  
  221.              if(e.key.includes("JR_message_pong") && Boolean(e.newValue)) {
  222.  
  223.              _lastReceivedPongTime = Date.now();
  224.  
  225.              let pongData = JSON.parse(e.newValue);
  226.  
  227.              let lag = Number(pongData.time) - Number(_lastReceivedPongTime);
  228.  
  229.              if(hasIndicatedOnlineSinceLastPing()) {
  230.              resolve("online");
  231.              }
  232.              }
  233.              });
  234.              */
  235.         });
  236.  
  237.         ping();
  238.  
  239.         return isOnlinePromise;
  240.     }
  241.  
  242.     function addJob(gid, once, metadata) {
  243.         let commandString = once ? "addOnceJob" : "addJob";
  244.  
  245.         localStorage.setItem("JR_message_pandacrazy", JSON.stringify({
  246.             time: Date.now(),
  247.             command: commandString,
  248.             data: {
  249.                 groupId: gid,
  250.                 title: (metadata ? metadata.hitTitle || metadata.title : undefined),
  251.                 requesterName: (metadata ? metadata.requesterName : undefined),
  252.                 requesterId: (metadata ? metadata.requesterID || metadata.requesterId || metadata.rid : undefined),
  253.                 pay: (metadata ? metadata.hitValue || metadata.pay : undefined),
  254.                 duration: (metadata ? metadata.duration : undefined),
  255.                 hitsAvailable: (metadata ? metadata.hitsAvailable : undefined)
  256.             }
  257.         }));
  258.     }
  259.  
  260.     function startJob(gid) {
  261.         localStorage.setItem("JR_message_pandacrazy", JSON.stringify({
  262.             time: Date.now(),
  263.             command: "startcollect",
  264.             data: {
  265.                 groupId: gid
  266.             }
  267.         }));
  268.     }
  269.  
  270.     return {
  271.         addJob,
  272.         startJob,
  273.         ping,
  274.         online
  275.     };
  276. })();
  277.  
  278. const SPEECH_VOICE    = 3;          //0 - 21ish
  279. const SPEECH_RATE     = 0.9;        //1 - 10 (default is 1)
  280. const SPEECH_VOLUME   = 1;          //0 - 1 (default is 1)
  281. const SPEECH_LANG     = 'en-US';    //(default is 'en')
  282.  
  283.  
  284.  
  285. //this is what does it all!
  286. unsafeWindow.slothbearsTTS = function(obj) {
  287.     let phrase = "Hit Found!" + obj.name;
  288.     var speech = new Speech();
  289.     if (speech.supported()) {
  290.         speech.speak(phrase);
  291.     }
  292. };
  293.  
  294.  
  295.  
  296. var Speech = function() {
  297. };
  298.  
  299. Speech.voices = null;
  300.  
  301. (function() {
  302.     if ('speechSynthesis' in window) {
  303.         // First call to getVoices may be null...later an event indicates when it is loaded
  304.         Speech.voices = window.speechSynthesis.getVoices();
  305.  
  306.         // Save voices when loaded after first call
  307.         window.speechSynthesis.onvoiceschanged = function() {
  308.             Speech.voices = window.speechSynthesis.getVoices();
  309.         };
  310.     }
  311. })();
  312.  
  313. Speech.prototype.supported = function() {
  314.     return Speech.voices !== null;
  315. };
  316.  
  317. Speech.prototype.speak = function(text) {
  318.     if (Speech.voices !== null) {
  319.         var speech = new SpeechSynthesisUtterance(text);
  320.  
  321.         speech.rate = SPEECH_RATE;
  322.         speech.voice = speechSynthesis.getVoices()[SPEECH_VOICE];
  323.         speech.lang = SPEECH_LANG;
  324.         speech.volume = SPEECH_VOLUME;
  325.         window.speechSynthesis.speak(speech);
  326.     }
  327. };
  328.  
  329. $('head').html(
  330.     '<title>HIT Forker</title>' +
  331.     '<base target="_blank">' +
  332.  
  333.     '<audio id="audio_1"><source src="https://www.soundjay.com/button/sounds/button-1.mp3" type="audio/mpeg"></audio>' +
  334.     '<audio id="audio_2"><source src="https://www.soundjay.com/button/sounds/button-3.mp3" type="audio/mpeg"></audio>' +
  335.     '<audio id="audio_3"><source src="https://www.soundjay.com/button/sounds/button-4.mp3" type="audio/mpeg"></audio>' +
  336.     '<audio id="audio_4"><source src="https://www.soundjay.com/button/sounds/button-5.mp3" type="audio/mpeg"></audio>' +
  337.     '<audio id="audio_beep"><source src="https://www.soundjay.com/button/sounds/beep-21.mp3" type="audio/mpeg"></audio>' +
  338.     '<audio id="audio_beepbeep"><source src="https://www.soundjay.com/button/sounds/beep-24.mp3" type="audio/mpeg"></audio>' +
  339.     '<audio id="audio_click"><source src="https://www.soundjay.com/button/sounds/button-20.mp3" type="audio/mpeg"></audio>' +
  340.  
  341.     '<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css" rel="stylesheet">' +
  342.     '<style id="css" type="text/css">'
  343. );
  344.  
  345. $('body').on('click', '.closeTvAlert', function(){
  346.     $(this).parent().parent().hide();
  347. });
  348.  
  349. $('body').html(
  350.     // Main
  351.     '<div style="margin-bottom: 5px; text-align:right;" id="menubar">' +
  352.     '<div style="position: absolute; top: 32px width: 100px; font-size: 14pt; font-weight: bold;" id="menu_title">HIT Forker</div>'+
  353.     '<div style="line-height: 30px; margin-right:5px;">'+
  354.     '<button id="scan_button" style="margin-right: 5px;">Start</button>' +
  355.     '<button id="bloc_button" style="margin-right: 5px;">Block List</button>' +
  356.     '<button id="incl_button" style="margin-right: 5px;">Include List</button>' +
  357.     '<button id="sett_button" style="margin-right: 5px;">Advanced Settings</button>' +
  358.     '<button id="conf_button" style="margin-right: 5px;">Show Config</button>' +
  359.  
  360.     '</div></div>' +
  361.  
  362.     // Config
  363.     '<div id="config" style="position: absolute; top: 37px; right: 5px; margin-bottom: 5px;" class="hidden">' +
  364.  
  365.     '<div style="margin-bottom: 5px;">' +
  366.     '<label style="margin-right: 5px; display: inline-block; border-bottom: 1px solid;" title="Delay in seconds between searches.">Search Delay: ' +
  367.     '<input id="delay" style="width: 50px;" type="number" step="1" min="1" value="' + config.delay + '">' +
  368.     '</label>' +
  369.  
  370.     '<label style="margin-right: 5px; display: inline-block; border-bottom: 1px solid;" title="Filter HITs by minimum reward.">Min Reward: ' +
  371.     '<input id="min_rew" style="width: 50px;" type="number" step="0.01" min="0" value="' + config.rew + '">' +
  372.     '</label>' +
  373.  
  374.     '<label style="margin-right: 5px; display: inline-block; border-bottom: 1px solid;" title="Filter HITs by minimum available.">Min Avail: ' +
  375.     '<input id="min_avail" style="width: 50px;" type="number" step="1" min="0" value="' + config.avail + '">' +
  376.     '</label>' +
  377.  
  378.     '<label style="margin-right: 5px; display: inline-block; border-bottom: 1px solid;" title="Filter HITs by minimum TO pay.">Min TO: ' +
  379.     '<input id="min_to" style="width: 50px;" type="number" step="0.1" min="0" max="5" value="' + config.mto + '">' +
  380.     '</label>' +
  381.  
  382.     '<label style="margin-right: 5px; display: inline-block; border-bottom: 1px solid;" title="Search for this many HITs.">Size: ' +
  383.     '<input id="size" style="width: 50px;" type="number" step="1" min="1" max="100" value="' + config.size + '">' +
  384.     '</label>' +
  385.  
  386.     '<label style="margin-right: 5px; display: inline-block; border-bottom: 1px solid;" title="Sort HITs by (Latest / Most Available / Highest Reward)">Sort by: ' +
  387.     '<select id="type" value="' + config.type + '">' +
  388.     '<option value="' + upd + '">Latest</option>' +
  389.     '<option value="' + num + '">Most Available</option>' +
  390.     '<option value="' + rew + '">Reward (Most)</option>' +
  391.     '</select>' +
  392.     '</label>' +
  393.  
  394.     '<label style="margin-right: 0px; display: inline-block; border-bottom: 1px solid;" title="Only show HITs that you are  for.">Qualified' +
  395.     '<input id="qual" type="checkbox" ' + (config.qual ? 'checked' : '') + '>' +
  396.     '</label>' +
  397.     '</div>' +
  398.  
  399.     '<div style="margin-bottom: 5px;">' +
  400.     '<label style="margin-right: 5px; display: inline-block; border-bottom: 1px solid;" title="Delay in seconds between desktop notifications and sound alerts for an include list match.">Alert Delay: ' +
  401.     '<input id="alert_delay" style="width: 50px;" type="number" step="1" min="0" value="' + config.alert + '">' +
  402.     '</label>' +
  403.  
  404.     '<label style="margin-right: 5px; display: inline-block; border-bottom: 1px solid;" title="Make a sound when a new HIT is found.">Sound On New HIT ' +
  405.     '<input id="new_sound" type="checkbox" ' + (config.new ? 'checked' : '') + '>' +
  406.     '<select id="new_audio" value="' + config.newaudio + '">' +
  407.     //'<option value="default">Default</option>' +
  408.     '<option value="beep">Beep</option>' +
  409.     '<option value="beepbeep">Beep Beep</option>' +
  410.     //'<option value="ding">Ding</option>' +
  411.     //'<option value="squee">Squee</option>' +
  412.     '<option value="click">Click</option>' +
  413.     '</select>' +
  414.     '</label>' +
  415.  
  416.     '<label style="margin-right: 5px; display: inline-block; border-bottom: 1px solid;" title="Allow include list to send a TTS notification when matched">TTS ' +
  417.     '<input id="tts" type="checkbox" ' + (config.tts ? 'checked' : '') + '>' +
  418.     '</label>' +
  419.  
  420.     '<label style="margin-right: 5px; display: inline-block; border-bottom: 1px solid;" title="Allow inludelist matches to send Pushbullet notifications if enabled for that match.">Pushbullet ' +
  421.     '<input id="pb" type="checkbox" ' + (config.pb ? 'checked' : '') + '>' +
  422.     '</label>' +
  423.  
  424.     '<label style="margin-right: 5px; display: inline-block; border-bottom: 1px solid;" title="Use turkopticon.">Enable TO ' +
  425.     '<input id="to" type="checkbox" ' + (config.to ? 'checked' : '') + '>' +
  426.     '</label>' +
  427.  
  428.     '<label style="margin-right: 5px; display: inline-block; border-bottom: 1px solid;" title="Hide all HITs that do not match your include list.">Hide Non Include List ' +
  429.     '<input id="nl_hide" type="checkbox" ' + (config.nl ? 'checked' : '') + '>' +
  430.     '</label>' +
  431.  
  432.     '<label style="margin-right: 5px; display: inline-block; border-bottom: 1px solid;" title="Hide HITs that match your block list.">Hide Block List ' +
  433.     '<input id="bl_hide" type="checkbox" ' + (config.bl ? 'checked' : '') + '>' +
  434.     '</label>' +
  435.  
  436.     '</div>' +
  437.  
  438.     '</div>' +
  439.  
  440.     '<div id="TVAlert" style="background-color: #dff0d8; border-color: #d0e9c6;' + (((config.tv_api_key == '' || config.tv_api_key.length != 40) && !config.disable_tv) ? `` : ` display: none; `) +' color: #3c763d; padding: 15px; margin-bottom: 1rem; border: 1px solid transparent; border-radius: 2px; margin: auto; width: 60%; padding-top: 10px; padding-bottom: 10px;">' +
  441.     '<h4 style="font-size: 0.857rem; margin-bottom: 0.5rem; margin-top: 0.5rem;">TurkerView API Changes (<span class="closeTvAlert" style="cursor: pointer;">Dismiss</span>)</h4>' +
  442.  
  443.         `<small>
  444.     <p>Sorry for the intrusion, but we're expanding our services & infrastructure and making huge improvements to the way we deliver information & data to Turkers in 2019!</p>
  445.    <p>HIT Forker has been updated to function with TurkerView's new View API [<a href="https://forum.turkerview.com/threads/hit-forker-update.2025/" target="_blank">details here</a>]
  446.     <p>TVJS 10 is out! You can read change details <a href="https://forum.turkerview.com/threads/turkerviewjs-10.2010/" target="_blank">here</a> - including improvements to approval (AA) time tracking! You can find more information about the full API changes <a href="https://forum.turkerview.com/threads/view-api-details.2012/" target="_blank" style="text-decoration: underline;">on our announcement here</a>.</p>
  447.     <p>Make sure to register & get your new access keys to our upgraded API by <a href="https://turkerview.com/account/api/" target="_blank" style="text-decoration: underline;">visiting your account dashboard</a>. We'll stop displaying this as soon as you do, but the script wont be able to retrieve TV data after February 1st without an API Key.</p>
  448.        </small><form action="saveForkerApiForm" onsubmit="return false;">
  449.        <input type="text" class="form-control" style="max-width: 50%; margin-top: 5px; margin-bottom: 5px;">
  450.        <button type="submit" class="btn btn-primary">Save API Key</button>
  451.    </form>
  452.    <script>
  453.        $('form[action*=saveForkerApiForm]').submit(function(e){
  454.            e.preventDefault();
  455.            let api_key = $(this).find('input[type=text]').val().trim();
  456.            console.log(api_key);
  457.            if (api_key.length == 40){
  458.                localStorage.setItem('turkerview_api_key', api_key);
  459.                alert('Awesome, we saved your API key for future use! HIT Forker will reload now.');
  460.                window.location.reload();
  461.            } else {
  462.                alert("We cannot save the provided key as it isn't valid.");
  463.            }
  464.        });
  465.    </script>` +
  466.  
  467.    '</div>'+
  468.  
  469.    '<div id="TVErrorMessage" style="display: none; background-color: #f2dede; border-color: #ebcccc; color: #a94442; padding: 15px; margin-bottom: 1rem; border: 1px solid transparent; border-radius: 2px; margin: auto; width: 60%; padding-top: 10px; padding-bottom: 10px;">' +
  470.    '<h4 style="font-size: 0.857rem; margin-bottom: 0.5rem; margin-top: 0.5rem;">TurkerView API Error (<span class="closeTvAlert" style="cursor: pointer;">Dismiss</span>)</h4>' +
  471.    '<p style="margin-bottom: 0.5rem; margin-top: 0.5rem;">Error Message.</p>' +
  472.    '</div>'+
  473.    // HITs
  474.    '<div id="latest_hits">' +
  475.    '<div style="border-bottom: 3px solid; margin-bottom: 5px;">' +
  476.    '<span style="font-size: 20px; font-weight: bold;">HITs</span>' +
  477.    '<span id="hits_data" style="font-size: 11px;"></span>' +
  478.    '<span id="hits_controls" style="float: right"><button id="hits_button" style="margin-right: 5px;">Hide New HITs</button></span>' +
  479.    '</div>' +
  480.    '<div id="hits_hidden" style="text-align: center; display: none;">--- HITS HIDDEN ---</div>' +
  481.    '<div id="hits_table">' +
  482.    '<div>' +
  483.    '<div style="overflow: hidden; white-space: nowrap;">' +
  484.    '<div style="float: left; width: calc(100% - 330px);">' +
  485.    '<span style="width: 34%; float: left;  display:inline-block; overflow: hidden;">Requester</span>' +
  486.    '<span style="width: 64%; float: right; display:inline-block; overflow: hidden;">Project</span>' +
  487.    '</div>' +
  488.  
  489.    '<div style="float: right;">' +
  490.    '<span style="width: 60px; display:inline-block; text-align: center;">Tasks</span>' +
  491.    '<span style="width: 60px; display:inline-block; text-align: center;">Accept</span>' +
  492.    '<span style="width: 60px; display:inline-block; text-align: center;">TV</span>' +
  493.    '<span style="width: 60px; display:inline-block; text-align: center;">TO</span>' +
  494.    '<span style="width: 60px; display:inline-block; text-align: center;">PANDA</span>' +
  495.    '</div>' +
  496.    '</div>' +
  497.    '</div>' +
  498.    '<div id="new_hits_"></div>' +
  499.    '</div>' +
  500.    '</div>' +
  501.  
  502.    '<br>' +
  503.  
  504.    //Logged HITs
  505.    '<div id="logged_hits">' +
  506.    '<div style="border-bottom: 3px solid; margin-bottom: 5px;">' +
  507.    '<span style="font-size: 20px; font-weight: bold;">Logged HITs</span>' +
  508.    '<span id="logged_hits_data" style="font-size: 11px;"></span>' +
  509.    '<span id="log_controls" style="float: right"><button id="logg_button" style="margin-right: 0px;">Hide Logged HITs</button></span>' +
  510.    '</div>' +
  511.    '<div id="log_hidden" style="text-align: center; display: none;">--- LOGGED HITS HIDDEN ---</div>' +
  512.    '<div id="log_table">' +
  513.    '<div>' +
  514.    '<div style="overflow: hidden; white-space: nowrap;">' +
  515.  
  516.    '<div style="float: left;">' +
  517.    '<span style="width: 80px; display:inline-block;">Time</span>' +
  518.    '</div>' +
  519.  
  520.    '<div style="float: left; width: calc(100% - 3500px);">' +
  521.    '<span style="width: 25%; float: left;  display:inline-block; overflow: hidden;">Requester</span>' +
  522.    '<span style="width: 75%; float: right; display:inline-block; overflow: hidden;">Project</span>' +
  523.  
  524.    '</div>' +
  525.  
  526.    '<div style="float: right;">' +
  527.    '<span style="width: 60px; display:inline-block; text-align: center;">Avail</span>' +
  528.    '<span style="width: 60px; display:inline-block; text-align: center;">Accept</span>' +
  529.    '<span style="width: 60px; display:inline-block; text-align: center;">TV</span>' +
  530.    '<span style="width: 60px; display:inline-block; text-align: center;">TO</span>' +
  531.    '<span style="width: 60px; display:inline-block; text-align: center;">PANDA</span>' +
  532.    '</div>' +
  533.    '</div>' +
  534.    '</div>' +
  535.    '<div id="log_hits_"></div>' +
  536.    '</div>' +
  537.    '</div>' +
  538.  
  539.    // Block List
  540.    '<div id="bl_div" style="z-index: 99; position: fixed; width: 80%; height: 80%; left: 10%; top: 300px; margin-top: -250px; display: none;">' +
  541.    '<div style="position: relative; width: 80%; left: 10%; border-bottom: 3px solid; padding: 2px; text-align: center;">Block List</div>' +
  542.    '<div id="bl_items"></div>' +
  543.    '<div style="text-align: center;">' +
  544.    '<button id="bl_add"    style="margin-right: 5px;">Add</button>' +
  545.    '<button id="bl_close"  style="margin-right: 5px;">Close</button>' +
  546.    '<button id="bl_import" style="margin-right: 5px;">Import</button>' +
  547.    '<button id="bl_export" style="margin-right: 0px;">Export</button>' +
  548.    '</div>' +
  549.    '</div>' +
  550.  
  551.    // Add Block List Popup
  552.    '<div id="bl" class="add" style="z-index: 100; position: fixed; width: 520px; top: 300px; left: 50%; margin: -250px; padding: 5px; text-align: center; display: none;">' +
  553.    '<div style="position: relative; width: 80%; left: 10%; border-bottom: 3px solid; padding: 2px; text-align: center;">Add To Block List</div>' +
  554.  
  555.    '<div>' +
  556.    '<p><label>Term: </label><input id="bl_term" value="" maxlength="300"></p>' +
  557.    '<p><label>Name: </label><input id="bl_name" value="" maxlength="300"></p>' +
  558.    '<input id="bl_gid" value="0" type="hidden">' +
  559.    '</div>' +
  560.  
  561.    '<div>' +
  562.    '<button id="bl_add_save"   style="margin-right: 5px;">Save</button>' +
  563.    '<button id="bl_add_cancel" style="margin-right: 0px;">Cancel</button>' +
  564.    '</div>' +
  565.    '</div>' +
  566.  
  567.    // Edit Block List Popup
  568.    '<div id="edit_bl" class="add" class="add" style="z-index: 100; position: fixed; width: 520px; top: 300px; left: 50%; margin: -250px; padding: 5px; text-align: center; display: none;">' +
  569.    '<div style="position: relative; width: 80%; left: 10%; border-bottom: 3px solid; padding: 2px; text-align: center;">Edit Block List Item</div>' +
  570.  
  571.    '<div>' +
  572.    '<p><label>Term: </label><input id="edit_bl_term"  value=""disabled></p>' +
  573.    '<p><label>Name: </label><input id="edit_bl_name" value=""></p>' +
  574.    '</div>' +
  575.  
  576.    '<div>' +
  577.    '<button id="edit_bl_save"   style="margin-right: 5px;">Save</button>' +
  578.    '<button id="edit_bl_delete" style="margin-right: 5px;">Delete</button>' +
  579.    '<button id="edit_bl_cancel" style="margin-right: 0px;">Cancel</button>' +
  580.    '</div>' +
  581.    '</div>' +
  582.  
  583.    // Include List
  584.    '<div id="il_div" style="z-index: 99; position: fixed; width: 80%; height: 80%; left: 10%; top: 300px; margin-top: -250px; display: none;">' +
  585.    '<div style="position: relative; width: 80%; left: 10%; border-bottom: 3px solid; padding: 2px; text-align: center;">Include List</div>' +
  586.    '<div id="il_items"></div>' +
  587.    '<div style="text-align: center;">' +
  588.    '<button id="il_add"    style="margin-right: 5px;">Add</button>' +
  589.    '<button id="il_close"  style="margin-right: 5px;">Close</button>' +
  590.    '<button id="il_import" style="margin-right: 5px;">Import</button>' +
  591.    '<button id="il_export" style="margin-right: 0px;">Export</button>' +
  592.    '</div>' +
  593.    '</div>' +
  594.  
  595.    // Add Include List Popup
  596.    '<div id="il" class="add" style="z-index: 100; position: fixed; width: 520px; top: 300px; left: 50%; margin: -250px; padding: 5px; text-align: center; display: none;">' +
  597.    '<div style="position: relative; width: 80%; left: 10%; border-bottom: 3px solid; padding: 2px; text-align: center;">Add To Include List</div>' +
  598.  
  599.    '<div>' +
  600.    '<p><label>Term: </label><input id="il_term" value=""></p>' +
  601.    '<p><label>Name: </label><input id="il_name" value=""></p>' +
  602.    '</div>' +
  603.  
  604.    '<div style="position: relative; width: 80%; left: 10%; border-bottom: 3px solid; padding: 2px; text-align: center;">Alerts</div>' +
  605.  
  606.    '<p>' +
  607.    '<label style="margin-right: 5px; display: inline-block; border-bottom: 1px solid;">Sound: ' +
  608.    '<select id="il_sound">' +
  609.    '<option value="1">Sound 1</option>' +
  610.    '<option value="2">Sound 2</option>' +
  611.    '<option value="3">Sound 3</option>' +
  612.    '<option value="4">Sound 4</option>' +
  613.    '</select>' +
  614.    '</label>' +
  615.  
  616.    '<label style="margin-right: 5px; display: inline-block; border-bottom: 1px solid;">Desktop Notifications' +
  617.    '<input id="il_noti_cb" type="checkbox">' +
  618.    '</label>' +
  619.  
  620.    '<label style="margin-right: 5px; display: inline-block; border-bottom: 1px solid;">Play Sound' +
  621.    '<input id="il_sound_cb" type="checkbox">' +
  622.    '</label>' +
  623.  
  624.    '<label style="margin-right: 5px; display: inline-block; border-bottom: 1px solid;">Send Pushbullet' +
  625.    '<input id="il_push_cb" type="checkbox">' +
  626.    '</label>' +
  627.    '</p>' +
  628.  
  629.    '<div>' +
  630.    '<button id="il_add_save"   style="margin-right: 5px;">Save</button>' +
  631.    '<button id="il_add_cancel" style="margin-right: 0px;">Cancel</button>' +
  632.    '</div>' +
  633.    '</div>' +
  634.  
  635.    // Edit Include List Popup
  636.    '<div id="edit_il" class="add" style="z-index: 100; position: fixed; width: 520px; top: 300px; left: 50%; margin: -250px; padding: 5px; text-align: center; display: none;">' +
  637.    '<div style="position: relative; width: 80%; left: 10%; border-bottom: 3px solid; padding: 2px; text-align: center;">Edit Include List Item</div>' +
  638.  
  639.    '<div>' +
  640.    '<p><label>Term: </label><input id="edit_il_term" value="" disabled></p>' +
  641.    '<p><label>Name: </label><input id="edit_il_name" value=""></p>' +
  642.    '</div>' +
  643.  
  644.    '<div style="position: relative; width: 80%; left: 10%; border-bottom: 3px solid; padding: 2px; text-align: center;"">Alerts</div>' +
  645.  
  646.    '<p>' +
  647.    '<label style="margin-right: 5px; display: inline-block; border-bottom: 1px solid;">Sound: ' +
  648.    '<select id="edit_il_sound">' +
  649.    '<option value="1">Sound 1</option>' +
  650.    '<option value="2">Sound 2</option>' +
  651.    '<option value="3">Sound 3</option>' +
  652.    '<option value="4">Sound 4</option>' +
  653.    '</select>' +
  654.    '</label>' +
  655.  
  656.    '<label style="margin-right: 5px; display: inline-block; border-bottom: 1px solid;">Desktop Notifications' +
  657.    '<input id="edit_il_noti_cb" type="checkbox">' +
  658.    '</label>' +
  659.  
  660.    '<label style="margin-right: 5px; display: inline-block; border-bottom: 1px solid;">Play Sound' +
  661.    '<input id="edit_il_sound_cb" type="checkbox">' +
  662.    '</label>' +
  663.  
  664.    '<label style="margin-right: 0px; display: inline-block; border-bottom: 1px solid;">Send Pushbullet' +
  665.    '<input id="edit_il_push_cb" type="checkbox">' +
  666.    '</label>' +
  667.    '</p>' +
  668.  
  669.    '<div>' +
  670.    '<button id="edit_il_save"   style="margin-right: 5px;">Save</button>' +
  671.    '<button id="edit_il_delete" style="margin-right: 5px;">Delete</button>' +
  672.    '<button id="edit_il_cancel" style="margin-right: 0px;">Cancel</button>' +
  673.    '</div>' +
  674.    '</div>' +
  675.  
  676.    // Advanced Settings
  677.    '<div id="sett" class="add" style="z-index: 100; position: fixed; width: 520px; top: 300px; left: 50%; margin: -250px; padding: 5px; text-align: center; display: none;">' +
  678.    '<div style="position: relative; width: 80%; left: 10%; border-bottom: 3px solid; padding: 2px; text-align: center;">Advanced Settings</div>' +
  679.  
  680.    '<div>' +
  681.    '<p><label>Pushbullet Token: </label><input id="push" value="' + config.push + '"></p>' +
  682.    '</div>' +
  683.    '<div style="position: relative; width: 80%; left: 10%; border-bottom: 3px solid; padding: 2px; text-align: center;">TurkerView</div>' +
  684.    '<div><p><label>TurkerView API Key: </label><input id="tv_api_key" value="' + ((config.tv_api_key == null || config.tv_api_key.length != 40) ? '' : config.tv_api_key) + '"></p></div>' +
  685.        '<div><p><label><input type="checkbox" name="disable_turkerview"' + (config.disable_tv ? 'checked' : '') + '> Disable TurkerView</label></p></div>' +
  686.    '<div><p>TurkerView is completely free (no obligation) for a month, we hope you\'ll <a href="" target="_blank">join us & try it</a>, but if not feel free to disable to stop notifications.</p></div>' +
  687.  
  688.     '<div style="position: relative; width: 80%; left: 10%; border-bottom: 3px solid; padding: 2px; text-align: center;">Theme</div>' +
  689.  
  690.     '<p>' +
  691.     '<label style="margin-right: 5px; display: inline-block; border-bottom: 1px solid;">Theme: ' +
  692.     '<select id="adv_theme" value="' + config.theme + '">' +
  693.     '<option value="default">Default (Light)</option>' +
  694.     '<option value="light">Pastel</option>' +
  695.     '<option value="dark">Dark</option>' +
  696.     '<option value="darker">Darker</option>' +
  697.     '<option value="custom">Custom</option>' +
  698.     '</select>' +
  699.     '</label>' +
  700.  
  701.     '<label style="margin-right: 0px; display: inline-block; border-bottom: 1px solid;">TO Theme: ' +
  702.     '<select id="to_theme" value="' + config.to_theme + '">' +
  703.     '<option value="1">Default</option>' +
  704.     '<option value="2">Column Only</option>' +
  705.     '<option value="3">Text Only</option>' +
  706.     '</select>' +
  707.     '</label>' +
  708.     '</p>' +
  709.  
  710.     '<p>' +
  711.     '<label style="width: 150px; margin-right: 5px; display: inline-block; border-bottom: 1px solid; text-align: left;">Main: #' +
  712.     '<input id="theme_main" style="width: 55px; float: right;" maxlength="6">' +
  713.     '</label>' +
  714.  
  715.     '<label style="width: 150px; margin-right: 5px; display: inline-block; border-bottom: 1px solid; text-align: left;">Primary: #' +
  716.     '<input id="theme_primary" style="width: 55px; float: right;" maxlength="6">' +
  717.     '</label>' +
  718.  
  719.     '<label style="width: 150px; margin-right: 0px; display: inline-block; border-bottom: 1px solid; text-align: left;">Secondary: #' +
  720.     '<input id="theme_secondary" style="width: 55px; float: right;" maxlength="6">' +
  721.     '</label>' +
  722.     '</p>' +
  723.  
  724.     '<p>' +
  725.     '<label style="width: 150px; margin-right: 5px; display: inline-block; border-bottom: 1px solid; text-align: left;">Text: #' +
  726.     '<input id="theme_text" style="width: 55px; float: right;" maxlength="6">' +
  727.     '</label>' +
  728.  
  729.     '<label style="width: 150px; margin-right: 5px; display: inline-block; border-bottom: 1px solid; text-align: left;">Link: #' +
  730.     '<input id="theme_link" style="width: 55px; float: right;" maxlength="6">' +
  731.     '</label>' +
  732.  
  733.     '<label style="width: 150px; margin-right: 0px; display: inline-block; border-bottom: 1px solid; text-align: left;">Visited: #' +
  734.     '<input id="theme_visited" style="width: 55px; float: right;" maxlength="6">' +
  735.     '</label>' +
  736.     '</p>' +
  737.  
  738.     '<div>' +
  739.     '<button id="sett_save"  style="margin-right: 5px;">Save</button>' +
  740.     '<button id="sett_close" style="margin-right: 0px;">Close</button>' +
  741.     '</div>' +
  742.     '</div>'
  743. );
  744.  
  745. // Click functions
  746. $('#scan_button').click(function () {
  747.     if ($(this).text() === 'Start') {
  748.         $(this).text('Stop');
  749.         _scan();
  750.     }
  751.     else {
  752.         $(this).text('Start');
  753.     }
  754. });
  755.  
  756. $('#sett_button').click(function () {
  757.     $('#sett').toggle();
  758. });
  759.  
  760. $('#conf_button').click(function () {
  761.     if ($(this).text() === 'Hide Config') {
  762.         $(this).text('Show Config');
  763.     }
  764.     else {
  765.         $(this).text('Hide Config');
  766.     }
  767.     $('#config').toggleClass('hidden');
  768. });
  769.  
  770. $('#logg_button').click(function () {
  771.     _hide_log_list( $(this).text() === 'Hide Logged HITs' ? true : false );
  772. });
  773.  
  774. $('#hits_button').click(function () {
  775.     _hide_hit_list( $(this).text() === 'Hide New HITs' ? true : false );
  776. });
  777.  
  778. $('#bloc_button').click(function () {
  779.     $('#bl_div').toggle();
  780. });
  781.  
  782. $('#bl_add').click(function () {
  783.     $('#bl').show();
  784. });
  785.  
  786. $('#bl_close').click(function () {
  787.     $('#bl_div').hide();
  788. });
  789.  
  790. $('#bl_import').click(function () {
  791.     _import_block();
  792. });
  793.  
  794. $('#bl_export').click(function () {
  795.     _export_block();
  796. });
  797.  
  798. $('#bl_add_save').click(function () {
  799.     var obj = {
  800.         term : $('#bl_term').val(),
  801.         name : $('#bl_name').val() === '' ? $('#bl_term').val() : $('#bl_name').val(),
  802.         gid : $('#bl_gid').val() === '' ? 'X' : $('#bl_gid').val()
  803.     };
  804.  
  805.     _add_block(obj);
  806.  
  807.     if ( obj.gid != 'X' ) {
  808.         $('.loggid_' + obj.gid ).remove();
  809.     }
  810.     else {
  811.         $('.logreqid_' + obj.term ).remove();
  812.     }
  813.  
  814.     $('#bl_term, #bl_name').val('');
  815.     $('#bl').hide();
  816. });
  817.  
  818. $('#bl_add_cancel').click(function () {
  819.     $('#bl_term, #bl_name').val('');
  820.     $('#bl').hide();
  821. });
  822.  
  823. $('#edit_bl_save').click(function () {
  824.     _update_block($(this).val());
  825.     $('#edit_bl_term, #edit_bl_name').val('');
  826.     $('#edit_bl').hide();
  827. });
  828.  
  829. $('#edit_bl_delete').click(function () {
  830.     _delete_block($(this).val());
  831.     $('#edit_bl_term, #edit_bl_name').val('');
  832.     $('#edit_bl').hide();
  833. });
  834.  
  835. $('#edit_bl_cancel').click(function () {
  836.     $('#edit_bl_term, #edit_bl_name').val('');
  837.     $('#edit_bl').hide();
  838. });
  839.  
  840. $('#incl_button').click(function () {
  841.     $('#il_div').toggle();
  842. });
  843.  
  844. $('#il_add').click(function () {
  845.     $('#il').show();
  846. });
  847.  
  848. $('#il_close').click(function () {
  849.     $('#il_div').hide();
  850. });
  851.  
  852. $('#il_import').click(function () {
  853.     _import_include();
  854. });
  855.  
  856. $('#il_export').click(function () {
  857.     _export_include();
  858. });
  859.  
  860. $('#il_add_save').click(function () {
  861.     var obj = {
  862.         term     : $('#il_term').val().trim(),
  863.         name     : $('#il_name').val().trim() === '' ? $('#il_term').val().trim() : $('#il_name').val().trim(),
  864.         sound    : $('#il_sound').val(),
  865.         noti_cb  : $('#il_noti_cb').prop('checked'),
  866.         sound_cb : $('#il_sound_cb').prop('checked'),
  867.         push_cb : $('#il_push_cb').prop('checked')
  868.     };
  869.  
  870.     _add_include(obj);
  871.  
  872.     $('#il_term, #il_name').val('');
  873.     $('#il').hide();
  874. });
  875.  
  876. $('#il_add_cancel').click(function () {
  877.     $('#il_term, #il_name').val('');
  878.     $('#il').hide();
  879. });
  880.  
  881. $('#edit_il_save').click(function () {
  882.     _update_include($(this).val());
  883.     $('#edit_il_term, #edit_il_name, #edit_il_sound').val('');
  884.     $('#edit_il_noti_cb, #edit_il_sound_cb, #edit_il_push_cb').prop('checked', false);
  885.     $('#edit_il').hide();
  886. });
  887.  
  888. $('#edit_il_delete').click(function () {
  889.     _delete_include($(this).val());
  890.     $('#edit_il_term, #edit_il_name, #edit_il_sound').val('');
  891.     $('#edit_il_noti_cb, #edit_il_sound_cb, #edit_il_push_cb').prop('checked', false);
  892.     $('#edit_il').hide();
  893. });
  894.  
  895. $('#edit_il_cancel').click(function () {
  896.     $('#edit_il_term, #edit_il_name, #edit_il_sound').val('');
  897.     $('#edit_il_noti_cb, #edit_il_sound_cb, #edit_il_push_cb').prop('checked', false);
  898.     $('#edit_il').hide();
  899. });
  900.  
  901. $('.on, .off').click(function () {
  902.     $(this).toggleClass('on off');
  903.     _save();
  904. });
  905.  
  906. $('#sett_save').click(function () {
  907.  
  908.     let original_key = config.tv_api_key;
  909.     let new_key = $('#tv_api_key').val();
  910.     let disable_tv = $('input[name=disable_turkerview]').is(':checked');
  911.     config.disable_tv = disable_tv;
  912.     _save('custom');
  913.     _theme();
  914.     if (original_key != new_key) {
  915.         $('#sett').prepend(`<p class="saved-settings-alert">Settings Saved! We'll reload Forker shortly.</p>`);
  916.        setTimeout(function() {
  917.            window.location.reload();
  918.        }, 1000)
  919.    } else $('#sett').prepend(`<p class="saved-settings-alert">Settings Saved!</p>`);
  920. });
  921.  
  922. $('#sett_close').click(function () {
  923.    $('#sett').find('.saved-settings-alert').remove();
  924.    $('#sett').hide();
  925. });
  926.  
  927. $('#time').click(function () {
  928.    $('.new').removeClass('new');
  929. });
  930.  
  931. // Delegated click functions
  932. $('body').on('click', '.blockit', function () {
  933.    _edit_block($(this).val());
  934. });
  935.  
  936. $('body').on('click', '.includeit', function () {
  937.    _edit_include($(this).val());
  938. });
  939.  
  940. $('body').on('click', '.rt', function () {
  941.    _block($(this).data('term'), $(this).data('name'), $(this).attr('id'));
  942. });
  943.  
  944. $('body').on('click', '.it', function () {
  945.    _includerid($(this).data('term'), $(this).data('name'), $(this).attr('id'));
  946. });
  947.  
  948. $('body').on('click', '.pc', function () {
  949.    _panda($(this).data('term'), $(this).data('reqname'), $(this).data('reqid'), $(this).data('title'), $(this).data('value'), $(this).data('name'), $(this))
  950. });
  951.  
  952. $('body').on('click', '.vb', function () {
  953.    _export_vb($(this).val());
  954. });
  955.  
  956. $('body').on('click', '.irc', function () {
  957.    _export_irc($(this).val());
  958. });
  959.  
  960. $('body').on('click', '.slk', function () {
  961.    _export_slk($(this).val());
  962. });
  963.  
  964. $('body').on('click', '.details', function () {
  965.    $(this).toggleClass('fa-plus-circle fa-minus-circle');
  966.    $('.info[value="' + $(this).val() + '"]').toggle();
  967. });
  968.  
  969. // Delegated mouseover functions
  970. $('body').on('mouseover', '.new', function () {
  971.    $(this).removeClass('new');
  972. });
  973.  
  974. // On change events
  975. $('#new_audio').change(function () {
  976.    _save();
  977.    _sound('new');
  978. });
  979.  
  980. $('#il_sound').change(function () {
  981.    _sound('il');
  982. });
  983.  
  984. $('#edit_il_sound').change(function () {
  985.    _sound('il_edit');
  986. });
  987.  
  988. $('#type, #size, #adv_theme, #to_theme, #c_theme, :checkbox').change(function () {
  989.    _save();
  990. });
  991.  
  992. $('#adv_theme, #to_theme, #c_theme').change(function () {
  993.    _theme();
  994. });
  995.  
  996. // On input events
  997. $('#delay, #min_rew, #min_avail, #min_to, #alert_delay').on('input', function () {
  998.    _save();
  999. });
  1000.  
  1001. function sanitize(strings, ...values) {
  1002.    console.log( strings );
  1003.    const dirty = strings.reduce((prev, next, i) => `${prev}${next}${values[i]} || ''}`, '');
  1004.    return DomPurify.sanitize(dirty);
  1005. }
  1006.  
  1007. function extend(obj, src) {
  1008.    for (var key in src) {
  1009.        if (src.hasOwnProperty(key)) obj[key] = src[key];
  1010.    }
  1011.    return obj;
  1012. }
  1013.  
  1014. function _scan () {
  1015.    var searchqualvar
  1016.    searchqualvar = (config.qual ? 'true' : 'false');
  1017.  
  1018.  
  1019.    if ($('#scan_button').text() === 'Stop') {
  1020.        var _url = url + $('#type').val() + $('#size').val() + minrew + $('#min_rew').val() + searchqual + searchqualvar;
  1021.        //console.log( _url );
  1022.        var _scanurl = _url + '&format=json';
  1023.  
  1024.        var date = new Date(), h = date.getHours(), m = date.getMinutes(), s = date.getSeconds(), ampm = h >= 12 ? 'pm' : 'am';
  1025.        h = h % 12; h = h ? h : 12; m = m < 10 ? '0' + m : m; s = s < 10 ? '0' + s : s;
  1026.        var timeis = [h, m, s, ampm];
  1027.        console.log( _scanurl );
  1028.        $.get(_scanurl, function (data) {
  1029.            _scrape_new(data, timeis);
  1030.        }).fail(function () {
  1031.            setTimeout(function () { _scan(); }, 2500);
  1032.        });
  1033.    }
  1034. }
  1035.  
  1036. function _scrape_new (data, timeis) {
  1037.    var keys = [], log_keys = [], to = [], logged_in = true;
  1038.    var new_requesters = [];
  1039.    var hits = data.results;
  1040.    //Set config for allowed tags
  1041.    var config = {
  1042.        ALLOWED_TAGS: ['p', '#text']
  1043.    };
  1044.  
  1045.    for (var i = 0; i < hits.length; i ++) {
  1046.        var hit = hits[i],
  1047.            req_name  = DOMPurify.sanitize(hit.requester_name,config).replace(/"/g, "&quot;" ),
  1048.            req_id    = DOMPurify.sanitize(hit.requester_id,config),
  1049.            req_link  = DOMPurify.sanitize(hit.requester_url.replace(/\.json/, ''),config),
  1050.            //req_link  = 'https://www.mturk.com/mturk/searchbar?selectedSearchType=hitgroups&requesterId=' + hit.requester_id,
  1051.             con_link  = DOMPurify.sanitize('https://www.mturk.com/mturk/contact?requesterId=' + hit.requester_id, config),
  1052.             group_id  = DOMPurify.sanitize(hit.hit_set_id,config),
  1053.             prev_link = DOMPurify.sanitize(hit.project_tasks_url.replace(/\.json/, ''),config),
  1054.             //prev_link = 'https://www.mturk.com/mturk/preview?groupId=' + hit.hit_set_id,
  1055.             pand_link = DOMPurify.sanitize(hit.accept_project_task_url.replace(/\.json/, ''),config),
  1056.             //pand_link = 'https://www.mturk.com/mturk/previewandaccept?groupId=' + hit.hit_set_id,
  1057.             title     = DOMPurify.sanitize(hit.title,config),
  1058.             safetitle = title.replace(/"/g, "&quot;" ),
  1059.             desc      = DOMPurify.sanitize(hit.description.replace(/"/g, "&quot;" ),config),
  1060.             time      = _convert_seconds(hit.assignment_duration_in_seconds),
  1061.             reward    = DOMPurify.sanitize('$' + hit.monetary_reward.amount_in_dollars.toFixed(2),config),
  1062.             avail     = DOMPurify.sanitize(hit.assignable_hits_count,config);
  1063.  
  1064.         var key = req_id + title + reward + group_id;
  1065.         keys.push(key);
  1066.  
  1067.         var qualif = 'None';
  1068.         var quals = hit.project_requirements;
  1069.  
  1070.  
  1071.         if (quals.length) {
  1072.  
  1073.             qualif = '';
  1074.             for (var j = 0; j < quals.length; j ++) {
  1075.                 var q_comp = quals[j].comparator + ' ';
  1076.                 var q_name = quals[j].qualification_type.name + ' ';
  1077.  
  1078.                 var q_valu = quals[j].qualification_values;
  1079.                 var q_values = '';
  1080.                 for (var k = 0; k < quals.length; k ++) {
  1081.                     if (quals[j].qualification_values[k]) {
  1082.                         q_values += quals[j].qualification_values[k];
  1083.                         q_values += k === quals.length ? ', ' : '';
  1084.                     }
  1085.                 }
  1086.                 if ( typeof quals[j].qualification_request_url !== 'undefined' ) {
  1087.                     q_values += '~!~' + quals[j].qualification_request_url;
  1088.                 }
  1089.                 qualif += (q_name + q_comp + q_values).trim() + '; ';
  1090.             }
  1091.             qualif = qualif.trim();
  1092.         }
  1093.  
  1094.         qualif = DOMPurify.sanitize(qualif,config);
  1095.  
  1096.         if (!hitlog[key]) {
  1097.             hitlog[key] = {
  1098.                 reqname  : req_name,
  1099.                 reqid    : req_id,
  1100.                 reqlink  : req_link,
  1101.                 conlink  : con_link,
  1102.                 groupid  : group_id,
  1103.                 prevlink : prev_link,
  1104.                 pandlink : pand_link,
  1105.                 title    : title,
  1106.                 safetitle: safetitle,
  1107.                 desc     : desc,
  1108.                 time     : time,
  1109.                 reward   : reward,
  1110.                 avail    : avail,
  1111.                 quals    : qualif,
  1112.                 key      : key,
  1113.                 tolink   : 'https://turkopticon.ucsd.edu/' + req_id,
  1114.                 to       : { comm : 'N/A', fair : 'N/A', fast : 'N/A', pay  : 'N/A' },
  1115.                 tvlink   : 'https://turkerview.com/requesters/' + req_id,
  1116.                 tv       : 'N/A'
  1117.             };
  1118.  
  1119.             if ( $.inArray( req_id, requesters ) == -1 ) {
  1120.                 new_requesters.push( req_id );
  1121.             }
  1122.  
  1123.             to.push([key, req_id]);
  1124.             log_keys.push(key);
  1125.         }
  1126.         else {
  1127.             hitlog[key].avail = avail;
  1128.         }
  1129.     }
  1130.  
  1131.     var tvPromise = _getTVReviews( new_requesters );
  1132.     var toPromise = _to(keys, log_keys, logged_in, to, timeis);
  1133.  
  1134.     $.when( tvPromise, toPromise).done( function( v1, v2 ) {
  1135.             _build(keys, log_keys, timeis);
  1136.         }
  1137.     );
  1138. }
  1139.  
  1140. let stopTV = false;
  1141. let tv_fail_rate = 0;
  1142. function _getTVReviews( reqlist ) {
  1143.     var def = $.Deferred();
  1144.     if ( reqlist.length > 0 && !config.disable_tv && !stopTV){
  1145.         fetch(`https://view.turkerview.com/v1/requesters/?requester_ids=${reqlist.join()}`, {
  1146.             method: 'GET',
  1147.             cache: 'no-cache',
  1148.             headers: ViewHeaders
  1149.         }).then(response => {
  1150.             if (!response.ok) throw response;
  1151.  
  1152.             return response.json();
  1153.         }).then(json => {
  1154.  
  1155.             newreviews = json.requesters;
  1156.             $.merge( requesters, reqlist );
  1157.             turkerview = extend( turkerview, newreviews);
  1158.             def.resolve(json);
  1159.  
  1160.         }).catch(ex => {
  1161.             tv_fail_rate++;
  1162.             if (ex.statusText == 'invalidUserAuthKey') {
  1163.                 stopTV = true;
  1164.                 $('#TVErrorMessage').find('p').html(`<span>Your TurkerView API Key is invalid. If you haven't added it yet check the "Advanced Settings" options tab!</span><p style="margin-bottom: 0.5rem; margin-top: 0.5rem;">You can claim your free API key (or support the site with a subscription!) from your <a href="https://turkerview.com/account/api/" target="_blank">TurkerView account API dashboard.</a></p>`);
  1165.                $('#TVErrorMessage').show();
  1166.            }
  1167.            else if (ex.statusText == 'dailyLimitExceeded' || ex.statusText == 'freeTrialExpired') {
  1168.                stopTV = true;
  1169.                $('#TVErrorMessage').find('p').html(`<span>Your TurkerView API Key has hit the free quota limit, please upgrade from your <a href="https://turkerview.com/account/api/" target="_blank">TurkerView account API dashboard</a>.</span>`);
  1170.                $('#TVErrorMessage').show();
  1171.            }
  1172.  
  1173.            def.resolve( 'Empty');
  1174.        });
  1175.    }
  1176.    else {
  1177.        def.resolve( 'Empty');
  1178.    }
  1179.  
  1180.    return def.promise();
  1181. }
  1182.  
  1183. function _getTVHourly( reqid ) {
  1184.    var tvHourly;
  1185.    if( reqid in turkerview ) {
  1186.        tvHourly = '$' + turkerview[reqid]['wages']['average']['wage'] + "/hr";
  1187.    }
  1188.    else {
  1189.        tvHourly = '-';
  1190.    }
  1191.  
  1192.    return tvHourly;
  1193. }
  1194.  
  1195. function _to (keys, log_keys, logged_in, to, timeis) {
  1196.    var def = $.Deferred();
  1197.    var timeout = true;
  1198.  
  1199.    var ids = [];
  1200.  
  1201.    if (logged_in && to.length && config.to) {
  1202.        for (var i = 0; i < to.length; i++) {
  1203.            ids.push(to[i][1]);
  1204.        }
  1205.  
  1206.        $.ajax( {
  1207.            url: 'https://turkopticon.ucsd.edu/api/multi-attrs.php?ids=' + ids,
  1208.             success: function (data) {
  1209.                 try {
  1210.                     var to_data = JSON.parse(data);
  1211.                 }
  1212.                 catch(err) {
  1213.                     console.log ( "Fork off TO you're drunk" );
  1214.                     // malformed javascript, treat it lke TO timed out.
  1215.                     def.resolve( 'Done');
  1216.                 }
  1217.  
  1218.                 for (i = 0; i < to.length; i++) {
  1219.                     if (!to_data[to[i][1]].length && typeof to_data[to[i][1]].attrs != 'undefined') {
  1220.                         hitlog[to[i][0]].to = to_data[to[i][1]].attrs;
  1221.                     }
  1222.                 }
  1223.  
  1224.                 timeout = false;
  1225.             },
  1226.             timeout: 400
  1227.         }).always(function () {
  1228.             def.resolve( 'Done' );
  1229.         });
  1230.     }
  1231.     else {
  1232.         def.resolve( 'TO Off' );
  1233.     }
  1234.     return def.promise();
  1235.  
  1236. }
  1237.  
  1238. function _build (keys, log_keys, timeis) {
  1239.     var hit_html = '', log_html = '';
  1240.  
  1241.     for (var i = 0; i < keys.length; i++) {
  1242.         var hit = hitlog[keys[i]], blocked = _check_block(hit), included = _check_include(hit), remove = false, classes, tvHourly, tvScore = false;
  1243.  
  1244.         hit.tv = _getTVHourly( hit.reqid );
  1245.  
  1246.         if ( hit.tv.substring(0,3) == 'N/A' || hit.tv.substring(0,3) == '<sp' ) {
  1247.             tvScore = false;
  1248.         }
  1249.         else {
  1250.             tvScore = true;
  1251.         }
  1252.  
  1253.         rowcolor = tvScore == true ? _color_tv(hit) : 'toNone';
  1254.  
  1255.         classes = rowcolor;
  1256.  
  1257.         if (Number(config.avail) > Number(hit.avail) || Number(config.mto) > Number(hit.to.pay)) {
  1258.             classes += ' hidden';
  1259.             remove = true;
  1260.         }
  1261.  
  1262.         if (blocked) {
  1263.             classes += config.bl ? ' bl_hidden' : ' bl';
  1264.             remove = true;
  1265.         }
  1266.  
  1267.         if (included) {
  1268.             classes += ' il';
  1269.             _included(included, hit);
  1270.         }
  1271.         else {
  1272.             classes += config.nl ? ' nl_hidden' : ' nl';
  1273.         }
  1274.  
  1275.         hit_html +=
  1276.             '<div class="cont" style="margin-bottom: 2px;">' +
  1277.             '<div class="' + classes + ' " style="overflow: hidden; white-space: nowrap; margin-bottom: 2px;">' +
  1278.  
  1279.             '<div style="float: left; width: calc(100% - 330px);">' +
  1280.  
  1281.             '<span style="width: 25%; float: left; display:inline-block; overflow: hidden;">' +
  1282.             '<button data-term="' + hit.reqid + '" data-name="' + hit.reqname + '" class="rt">R</button>' +
  1283.             '<button id="'+ hit.groupid + '" data-term="' + hit.safetitle + '" data-name="' + hit.safetitle + '" class="rt">T</button>' +
  1284.             '<button data-term="' + hit.reqid + '" data-name="' + hit.reqname + '" class="it">I</button>' +
  1285.             '<a href="' + hit.reqlink + '">' + hit.reqname + '</a>' +
  1286.             '</span>' +
  1287.  
  1288.             '<span style="width: 75%; float: right; display:inline-block; overflow: hidden;">' +
  1289.             '<button value="' + hit.key + '" class="vb">vB</button>' +
  1290.             '<button value="' + hit.key + '" class="irc">IRC</button>' +
  1291.             '<button value="' + hit.key + '" class="slk">SLK</button>' +
  1292.             '<a href="' + hit.prevlink + '">' + hit.safetitle + '</a>' +
  1293.             '</span>' +
  1294.  
  1295.             '</div>' +
  1296.  
  1297.             '<div style="float: right;">' +
  1298.  
  1299.             '<span style="width: 60px; display:inline-block; text-align: center;">' +
  1300.             hit.avail +
  1301.             '</span>' +
  1302.  
  1303.             '<span style="width: 60px; display:inline-block; text-align: center;">' +
  1304.             '<a href="' + hit.pandlink + '">' + hit.reward + '</a>' +
  1305.             '</span>' +
  1306.  
  1307.             '<span class="to" style="width: 60px; display:inline-block; text-align: center;">' +
  1308.             '<a href="' + hit.tvlink + '">' + hit.tv + '</a>' +
  1309.             '</span>' +
  1310.  
  1311.             '<span class="to" style="width: 60px; display:inline-block; text-align: center;">' +
  1312.             '<a href="' + hit.tolink + '">' + hit.to.pay + '</a>' +
  1313.             '</span>' +
  1314.  
  1315.             '<span style="width: 60px; display:inline-block; text-align: center;">' +
  1316.             '<button data-term="' + hit.groupid + '" data-reqid="' + hit.reqid + '" data-reqname="' + hit.reqname.replace(/"/g, "&quot;" ) + '" data-title="' + hit.safetitle + '"  data-value="' + hit.reward.replace(/\$/g, '') + '" data-name="panda" class="pc">P</button>' +
  1317.             '<button data-term="' + hit.groupid + '" data-reqid="' + hit.reqid + '" data-reqname="' + hit.reqname.replace(/"/g, "&quot;" ) + '" data-title="' + hit.safetitle + '"  data-value="' + hit.reward.replace(/\$/g, '') + '" data-name="once" class="pc">O</button>' +
  1318.             '</span>' +
  1319.  
  1320.             '</div>' +
  1321.             '</div>' +
  1322.             '</div>'
  1323.         ;
  1324.  
  1325.         if (remove) {
  1326.             var index = log_keys.indexOf(keys[i]);
  1327.  
  1328.             if (index > -1) {
  1329.                 log_keys.splice(index, 1);
  1330.             }
  1331.         }
  1332.     }
  1333.  
  1334.     if (log_keys.length) {
  1335.         for (var j = 0; j < log_keys.length; j ++) {
  1336.             var hit_log = hitlog[log_keys[j]], included_log = _check_include(hit_log), rowcolor, classes_log, tvHourly;
  1337.  
  1338.             rowcolor = hit_log.tv != 'N/A' ? _color_tv(hit_log) : 'toNone';
  1339.  
  1340.             classes_log = rowcolor;
  1341.  
  1342.             if (included_log) {
  1343.                 classes_log += ' il';
  1344.             }
  1345.             else {
  1346.                 classes_log += config.nl ? ' nl_hidden' : ' nl';
  1347.             }
  1348.  
  1349.             var quals = hit_log.quals.split(';');
  1350.             var qualif = '';
  1351.  
  1352.             for (var k = 0; k < quals.length; k ++) {
  1353.                 if (quals[k] !== '') {
  1354.                     if( quals[k].indexOf('~!~') != -1 ) {
  1355.                         var temp = quals[k].split('~!~');
  1356.                         quals[k] = temp[0];
  1357.                         if (temp[1].indexOf('request') == -1 ) {
  1358.                             //Qual test
  1359.                             quals[k] += '<form action="' +temp[1] +'" method="get" style=" display:inline!important;"><button>Take Test</button></form>';
  1360.                         }
  1361.                         else {
  1362.                             //Request Qual
  1363.                             //quals[k] += '<form action="' +temp[1] +'" method="post"><button>Request</button></form>';
  1364.                         }
  1365.                     }
  1366.                     qualif += '<li style="padding: 2px;">' + quals[k] + '</li>';
  1367.                 }
  1368.             }
  1369.  
  1370.  
  1371.  
  1372.             log_html +=
  1373.                 '<div class="cont loggid_' + hit_log.groupid +' logreqid_' + hit_log.reqid + '" style="margin-bottom: 2px;">' +
  1374.                 '<div class="' + classes_log + '" style="overflow: hidden; white-space: nowrap;">' +
  1375.  
  1376.                 '<div style="float: left;">' +
  1377.                 '<span style="width: 80px; display:inline-block;">' +
  1378.                 '<button class="fa fa-plus-circle fa-2 details" aria-hidden="true" value="' + hit_log.key + '" style="background-color: transparent; border: 0px; padding: 1px;"></button>' +
  1379.                 timeis[0] + ':' + timeis[1]  + timeis[3] + '</span>' +
  1380.                 '</div>' +
  1381.  
  1382.                 '<div style="float: left; width: calc(100% - 410px);">' +
  1383.  
  1384.                 '<span style="width: 25%; float: left; display:inline-block; overflow: hidden;">' +
  1385.                 '<button data-term="' + hit_log.reqid + '" data-name="' + hit_log.reqname + '" class="rt">R</button>' +
  1386.                 '<button id="'+ hit_log.groupid + '" data-term="' + hit_log.safetitle + '" data-name="' + hit_log.safetitle + '" class="rt">T</button>' +
  1387.                 '<button data-term="' + hit_log.reqid + '" data-name="' + hit_log.reqname + '" class="it">I</button>' +
  1388.                 '<a href="' + hit_log.reqlink + '">' + hit_log.reqname + '</a>' +
  1389.                 '</span>' +
  1390.  
  1391.                 '<span style="width: 75%; float: right; display:inline-block; overflow: hidden;">' +
  1392.                 '<button value="' + hit_log.key + '" class="vb">vB</button>' +
  1393.                 '<button value="' + hit_log.key + '" class="irc">IRC</button>' +
  1394.                 '<button value="' + hit_log.key + '" class="slk">SLK</button>' +
  1395.                 '<a href="' + hit_log.prevlink + '">' + hit_log.safetitle + '</a>' +
  1396.                 '</span>' +
  1397.  
  1398.                 '</div>' +
  1399.  
  1400.                 '<div style="float: right;">' +
  1401.  
  1402.                 '<span style="width: 60px; display:inline-block; text-align: center;">' +
  1403.                 hit_log.avail +
  1404.                 '</span>' +
  1405.  
  1406.                 '<span style="width: 60px; display: inline-block; text-align: center;">' +
  1407.                 '<a href="' + hit_log.pandlink + '">' + hit_log.reward + '</a>' +
  1408.                 '</span>' +
  1409.  
  1410.                 '<span class="to" style="width: 60px; display:inline-block; text-align: center;">' +
  1411.                 '<a href="' + hit_log.tvlink + '">' + hit_log.tv + '</a>' +
  1412.                 '</span>' +
  1413.  
  1414.  
  1415.                 '<span class="to" style="width: 60px; display:inline-block; text-align: center;">' +
  1416.                 '<a href="' + hit_log.tolink + '">' + hit_log.to.pay + '</a>' +
  1417.                 '</span>' +
  1418.  
  1419.                 '<span style="width: 60px; display:inline-block; text-align: center;">' +
  1420.                 '<button data-term="' + hit_log.groupid + '" data-reqid="' + hit_log.reqid + '" data-reqname="' + hit_log.reqname.replace(/"/g, "&quot;" ) + '" data-title="' + hit_log.safetitle + '"  data-value="' + hit_log.reward.replace(/\$/g, '') + '" data-name="panda" class="pc">P</button>' +
  1421.                 '<button data-term="' + hit_log.groupid + '" data-reqid="' + hit_log.reqid + '" data-reqname="' + hit_log.reqname.replace(/"/g, "&quot;" ) + '" data-title="' + hit_log.safetitle + '"  data-value="' + hit_log.reward.replace(/\$/g, '') + '" data-name="once" class="pc">O</button>' +
  1422.                 '</span>' +
  1423.  
  1424.                 '</div>' +
  1425.                 '</div>'+
  1426.  
  1427.                 '<div class="info ' + rowcolor + '" value="' + hit_log.key + '" style="overflow: hidden; display: none; font-size: 11px;">' +
  1428.  
  1429.                 '<div style="border-bottom: 1px solid #000000;"></div>' +
  1430.  
  1431.                 '<span style="width: 33%; float: left; display:inline-block; padding: 5px;">' +
  1432.                 '<span style="text-decoration: underline;">Description</span>' +
  1433.                 '<div style="padding: 2px;">' + hit_log.desc +'</div>' +
  1434.                 '<span style="text-decoration: underline;">Time</span>' +
  1435.                 '<div style="padding: 2px;">' + hit_log.time +'</div>' +
  1436.                 '</span>' +
  1437.  
  1438.                 '<span style="width: 33%; float: left; display:inline-block; padding: 5px;">' +
  1439.                 '<span style="text-decoration: underline;">Qualifications</span>' +
  1440.                 qualif +
  1441.                 '</span>' +
  1442.  
  1443.                 '<span style="width: calc(34% - 30px); float: right; display:inline-block; padding: 5px;">' +
  1444.                 '<span style="text-decoration: underline;">Turkopticon</span>' +
  1445.                 '<br>' +
  1446.                 '<span style="width: 70px; display:inline-block; padding: 2px;">Pay  : ' + hit_log.to.pay +'</span>' +
  1447.                 '<span style="width: 70px; display:inline-block; padding: 2px;">Fair : ' + hit_log.to.fair +'</span>' +
  1448.                 '<br>' +
  1449.                 '<span style="width: 70px; display:inline-block; padding: 2px;">Comm : ' + hit_log.to.comm +'</span>' +
  1450.                 '<span style="width: 70px; display:inline-block; padding: 2px;">Fast : ' + hit_log.to.fast +'</span>' +
  1451.                 '</span>' +
  1452.  
  1453.                 '</div>' +
  1454.                 '</div>';
  1455.  
  1456.             logged ++;
  1457.         }
  1458.         if (config.new) {
  1459.             _sound('new');
  1460.         }
  1461.     }
  1462.     $('#new_hits_').html(hit_html);
  1463.     $('#log_hits_').prepend(log_html);
  1464.  
  1465.     searches ++;
  1466.     var hits_data = '<span> ' + timeis[0] + ':' + timeis[1] + ':' + timeis[2] + timeis[3] + ' Scanned HITs: ' + keys.length + '</span><span style="float: right;">' + tv_fail_rate + '/' + searches + '</span>';
  1467.     var logged_hits_data = '<span style="float: right;">' + logged + '</span>';
  1468.  
  1469.     $('#hits_data').html(hits_data);
  1470.     $('#logged_hits_data').html(logged_hits_data);
  1471.  
  1472.     if ($('#scan_button').text() === 'Stop') {
  1473.         setTimeout(function () {
  1474.             _scan();
  1475.         }, $('#delay').val() * 1000);
  1476.     }
  1477. }
  1478.  
  1479. function _sound (sound) {
  1480.     if (sound === 'new')     { $('#audio_' + config.newaudio)           [0].play(); }
  1481.     if (sound === 'include') { $('#audio_' + config.newaudio)           [0].play(); }
  1482.     if (sound === 'il')      { $('#audio_' + $('#il_sound').val())      [0].play(); }
  1483.     if (sound === 'il_edit') { $('#audio_' + $('#edit_il_sound').val()) [0].play(); }
  1484. }
  1485.  
  1486. function _check_block (hit) {
  1487.     for (var key in blocklist) {
  1488.         var obj = blocklist[key];
  1489.         if (obj.term.toLowerCase() === hit.reqname.toLowerCase() || obj.term.toLowerCase() === hit.title.toLowerCase() || obj.term.toLowerCase() === hit.reqid.toLowerCase() || obj.term.toLowerCase() === hit.groupid.toLowerCase()) {
  1490.             return obj;
  1491.         }
  1492.     }
  1493. }
  1494.  
  1495. function _check_include (hit) {
  1496.     for (var key in includelist) {
  1497.         var obj = includelist[key];
  1498.         if (obj.term.toLowerCase() === hit.reqname.toLowerCase() || obj.term.toLowerCase() === hit.title.toLowerCase() || obj.term.toLowerCase() === hit.reqid.toLowerCase() || obj.term.toLowerCase() === hit.groupid.toLowerCase()) {
  1499.             return obj;
  1500.         }
  1501.     }
  1502. }
  1503.  
  1504. function _included (obj, hit) {
  1505.     var check = noti_delay.indexOf(hit.key) !== -1;
  1506.     var pushcheck = push_delay.indexOf(hit.key) !== -1;
  1507.  
  1508.     if (!check) {
  1509.         noti_delay.unshift(hit.key);
  1510.         setTimeout(function () { noti_delay.pop(); }, config.alert * 1000);
  1511.     }
  1512.     if (obj.noti_cb && !check) {
  1513.         Notification.requestPermission();
  1514.         var n = new Notification(hit.reqname + ' | ' + hit.reward, {
  1515.             icon : 'http://nopurpose.org/stuff/avatars/Lj21396.gif',
  1516.             body : hit.title,
  1517.         });
  1518.         setTimeout(n.close.bind(n), 5000);
  1519.  
  1520.         n.onclick = function(e) {
  1521.             e.preventDefault();
  1522.             window.open(hit.prevlink, '_blank');
  1523.         };
  1524.  
  1525.     }
  1526.     if (obj.sound_cb && !check) {
  1527.         if ( !config.tts ) {
  1528.             $('#audio_' + obj.sound)[0].play();
  1529.         }
  1530.         else {
  1531.             //Console.log("BOOM, TTS");
  1532.             slothbearsTTS(obj);
  1533.         }
  1534.     }
  1535.     if (obj.push_cb && !pushcheck && config.pb) {
  1536.         push_delay.unshift(hit.key);
  1537.         setTimeout(function () { push_delay.pop(); }, 900000);
  1538.  
  1539.         var push = {};
  1540.  
  1541.         push['type'] = 'note';
  1542.         push['title'] = 'HIT Finder';
  1543.         push['body'] = '[' + hit.reqname + ']\n[' + hit.safetitle + ']\n[' + hit.reward + ']\n[' + hit.prevlink + ']';
  1544.  
  1545.         $.ajax({
  1546.             type    : 'POST',
  1547.             headers : {'Authorization': 'Bearer ' + config.push},
  1548.             url     : 'https://api.pushbullet.com/v2/pushes',
  1549.             data    : push
  1550.         });
  1551.  
  1552.     }
  1553. }
  1554.  
  1555. function _color_tv(hit) {
  1556.     var tvHourly = hit.tv.replace(/\$/g, '').replace(/\/hr/g, '');
  1557.  
  1558.     if (tvHourly == '-') return 'toNone';
  1559.  
  1560.     if (config.theme == "light") {
  1561.         if (tvHourly >= 10.00) { return 'tvHigh'; }
  1562.         else if (tvHourly >= 7.25) { return 'tvFair'; }
  1563.         else { return 'tvLow'; }
  1564.     }
  1565.     else {
  1566.         if (tvHourly >= 10.00) { return 'toHigh'; }
  1567.         else if (tvHourly >= 7.25) { return 'toAverage'; }
  1568.         else { return 'toLow'; }
  1569.  
  1570.     }
  1571. }
  1572.  
  1573. function _color_to (hit) {
  1574.     var to = hit.to.pay;
  1575.  
  1576.     if (config.theme == "light") {
  1577.         if      (to > 4) { return 'tvHigh';    }
  1578.         else if (to > 2.5) { return 'tvFair';    }
  1579.         else if (to > 0) { return 'tvLow'; }
  1580.         else             { return 'tvNone';    }
  1581.     }
  1582.     else {
  1583.         if      (to > 4) { return 'toHigh';    }
  1584.         else if (to > 3) { return 'toGood';    }
  1585.         else if (to > 2.25) { return 'toAverage'; }
  1586.         else if (to > 1.25) { return 'toLow';     }
  1587.         else if (to > 0) { return 'toPoor';    }
  1588.         else             { return 'toNone';    }
  1589.  
  1590.     }
  1591. }
  1592.  
  1593. function _convert_seconds (seconds) {
  1594.     seconds = Number(seconds);
  1595.     var h = Math.floor(seconds / 3600);
  1596.     var m = Math.floor(seconds % 3600 / 60);
  1597.     var s = Math.floor(seconds % 3600 % 60);
  1598.     var time = '';
  1599.     if (h > 0) { time += h + ' hour(s) ';    }
  1600.     if (m > 0) { time += m + ' minutes(s) '; }
  1601.     if (s > 0) { time += s + ' seconds(s)';  }
  1602.     return time;
  1603. }
  1604.  
  1605. function _block (term, name, gid) {
  1606.     $('#bl_term') .val(term);
  1607.     $('#bl_name') .val(name);
  1608.     $('#bl_gid').val(gid);
  1609.     $('#bl')      .show();
  1610. }
  1611.  
  1612. function _includerid( term, name, gid ) {
  1613.     $('#il_term') .val(term);
  1614.     $('#il_name') .val(name);
  1615.     $('#il')      .show();
  1616. }
  1617.  
  1618. function _panda(term, reqname, reqid, title, value, name, button) {
  1619.     var hitData = {
  1620.         hitTitle: title,
  1621.         requesterName: reqname,
  1622.         requesterId: reqid,
  1623.         hitValue: value
  1624.     }
  1625.  
  1626.     var once = name == "panda" ? false : true;
  1627.  
  1628.     PandaCrazy.online().then (
  1629.         function(successResp) {
  1630.             PandaCrazy.addJob( term, once, hitData );
  1631.             button.addClass("clicked")
  1632.         },
  1633.         function(failedResp) {
  1634.             alert( "Panda Crazy doesn't appear to be running. Please double check if it's being run on the same browser profile and try again");
  1635.         }
  1636.     );
  1637.     //console.log ( running );
  1638. }
  1639.  
  1640.  
  1641. function _add_block (obj) {
  1642.     if (!blocklist[obj.term]) {
  1643.         blocklist[obj.term] = obj;
  1644.         _init_lists();
  1645.     }
  1646. }
  1647.  
  1648. function _edit_block (term) {
  1649.     var obj = blocklist[term];
  1650.     $('#edit_bl_term')   .val(obj.term) .text(obj.term);
  1651.     $('#edit_bl_name')   .val(obj.name);
  1652.     $('#edit_bl_save')   .val(obj.term);
  1653.     $('#edit_bl_delete') .val(obj.term);
  1654.     $('#edit_bl')        .show();
  1655. }
  1656.  
  1657. function _update_block (block) {
  1658.     var obj = blocklist[block];
  1659.     obj.name = $('#edit_bl_name').val();
  1660.     _init_lists();
  1661. }
  1662.  
  1663. function _delete_block (block) {
  1664.     delete blocklist[block];
  1665.     _init_lists();
  1666. }
  1667.  
  1668. function _add_include (obj) {
  1669.     if (!includelist[obj.term]) {
  1670.         includelist[obj.term] = obj;
  1671.         _init_lists();
  1672.     }
  1673. }
  1674.  
  1675. function _edit_include (term) {
  1676.     var obj = includelist[term];
  1677.  
  1678.     $('#edit_il_term')  .val(obj.term) .text(obj.term);
  1679.     $('#edit_il_name')  .val(obj.name);
  1680.     $('#edit_il_sound') .val(obj.sound);
  1681.  
  1682.     $('#edit_il_noti_cb')  .prop('checked', obj.noti_cb);
  1683.     $('#edit_il_sound_cb') .prop('checked', obj.sound_cb);
  1684.     $('#edit_il_push_cb') .prop('checked', obj.push_cb);
  1685.  
  1686.     $('#edit_il_save')   .val(obj.term);
  1687.     $('#edit_il_delete') .val(obj.term);
  1688.  
  1689.     $('#edit_il').show();
  1690. }
  1691.  
  1692. function _update_include (term) {
  1693.     var obj = includelist[term];
  1694.     obj.name     = $('#edit_il_name')  .val().trim();
  1695.     obj.sound    = $('#edit_il_sound') .val().trim();
  1696.     obj.noti_cb  = $('#edit_il_noti_cb')  .prop('checked');
  1697.     obj.sound_cb = $('#edit_il_sound_cb') .prop('checked');
  1698.     obj.push_cb = $('#edit_il_push_cb') .prop('checked');
  1699.     _init_lists();
  1700. }
  1701.  
  1702. function _delete_include (term) {
  1703.     delete includelist[term];
  1704.     _init_lists();
  1705. }
  1706.  
  1707. function _hide_hit_list( hide ) {
  1708.     if (hide) {
  1709.         $("#hits_button").text('Show New HITs');
  1710.         $('#hits_table').hide();
  1711.         $('#hits_hidden').show();
  1712.         config.h_hidden = '1';
  1713.     }
  1714.     else {
  1715.         $("#hits_button").text('Hide New HITs');
  1716.         $('#hits_table').show();
  1717.         $('#hits_hidden').hide();
  1718.         config.h_hidden = '0';
  1719.     }
  1720.  
  1721.     _save( 'showhide' );
  1722. }
  1723.  
  1724. function _hide_log_list( hide ) {
  1725.     if (hide) {
  1726.         $("#logg_button").text('Show Logged HITs');
  1727.         $('#log_table').hide();
  1728.         $('#log_hidden').show();
  1729.         config.l_hidden = '1';
  1730.     }
  1731.     else {
  1732.         $("#logg_button").text('Hide Logged HITs');
  1733.         $('#log_table').show();
  1734.         $('#log_hidden').hide();
  1735.         config.l_hidden = '0';
  1736.     }
  1737.  
  1738.     _save( 'showhide' );
  1739. }
  1740.  
  1741. function _init_lists () {
  1742.     var bl_sort = [], il_sort = [], bl_html = '', il_html = '';
  1743.  
  1744.     for (var bl_key in blocklist) {
  1745.         bl_sort.push([bl_key, blocklist[bl_key].name]);
  1746.     }
  1747.  
  1748.     bl_sort.sort(function (a, b) {
  1749.         if (a[1].toLowerCase() < b[1].toLowerCase()) return -1;
  1750.         if (a[1].toLowerCase() > b[1].toLowerCase()) return 1;
  1751.         return 0;
  1752.     });
  1753.  
  1754.     for (var i = 0; i < bl_sort.length; i ++) {
  1755.         var bl_obj = blocklist[bl_sort[i][0]];
  1756.         bl_html += '<button class="blockit" style="margin: 2px;" value="' + bl_obj.term + '" title="' + bl_obj.term + '">' + bl_obj.name + '</button>';
  1757.     }
  1758.  
  1759.     for (var il_key in includelist) {
  1760.         il_sort.push([il_key, includelist[il_key].name]);
  1761.     }
  1762.  
  1763.     il_sort.sort(function (a, b) {
  1764.         if (a[1].toLowerCase() < b[1].toLowerCase()) return -1;
  1765.         if (a[1].toLowerCase() > b[1].toLowerCase()) return 1;
  1766.         return 0;
  1767.     });
  1768.  
  1769.     for (var j = 0; j < il_sort.length; j ++) {
  1770.         var il_obj = includelist[il_sort[j][0]];
  1771.         il_html += '<button class="includeit" style="margin: 2px;" value="' + il_obj.term + '" title="' + il_obj.term + '">' + il_obj.name + '</button>';
  1772.     }
  1773.  
  1774.     $('#bl_items') .html(bl_html);
  1775.     $('#il_items') .html(il_html);
  1776.     _save('init');
  1777. }
  1778.  
  1779. function _import_block () {
  1780.     var import_bl  = prompt(
  1781.         'Block List Import\n\n' +
  1782.         'You can import from HIT Finder or HIT Scraper.\n\n' +
  1783.         'This will not delete your current block list, only add to it.\n\n' +
  1784.         'Please enter your block list here.',
  1785.         ''
  1786.     );
  1787.  
  1788.     if (import_bl) {
  1789.         var json = _json_validator(import_bl);
  1790.  
  1791.         if (json) {
  1792.             var _bl_obj = JSON.parse(import_bl);
  1793.             for (var key in _bl_obj) {
  1794.                 if (_bl_obj[key].hasOwnProperty('term') && _bl_obj[key].hasOwnProperty('name') && !_bl_obj[key].hasOwnProperty('sound')) {
  1795.                     if (!blocklist[key]) {
  1796.                         blocklist[key] = {
  1797.                             term : _bl_obj[key].term,
  1798.                             name : _bl_obj[key].name
  1799.                         };
  1800.                     }
  1801.                 }
  1802.                 else {
  1803.                     alert('An error occured while importing.\n\n Please check if you have a valid import and try again.');
  1804.                     break;
  1805.                 }
  1806.             }
  1807.             _init_lists();
  1808.         }
  1809.         else if (import_bl.match(/^/)) {
  1810.             var _bl_arr = import_bl.trim().split('^');
  1811.             for (var i = 0; i < _bl_arr.length; i ++) {
  1812.                 if (!blocklist[_bl_arr[i]]) {
  1813.                     blocklist[_bl_arr[i]] = {
  1814.                         term : _bl_arr[i],
  1815.                         name : _bl_arr[i]
  1816.                     };
  1817.                 }
  1818.             }
  1819.             _init_lists();
  1820.         }
  1821.     }
  1822.     else {
  1823.         alert('An error occured while importing.\n\n Please check if you have a valid import and try again.');
  1824.     }
  1825. }
  1826.  
  1827. function _export_block () {
  1828.     GM_setClipboard(localStorage.getItem('_finder_bl'));
  1829.     alert('Your block list has been copied to your clipboard.');
  1830. }
  1831.  
  1832. function _import_include () {
  1833.     var import_il  = prompt(
  1834.         'Include List Import\n\n' +
  1835.         'You can import from HIT Finder or HIT Scraper.\n\n' +
  1836.         'This will not delete your current include list, only add to it.\n\n' +
  1837.         'Please enter your include list here.',
  1838.         ''
  1839.     );
  1840.  
  1841.     if (import_il) {
  1842.         var json = _json_validator(import_il);
  1843.  
  1844.         if (json) {
  1845.             var _il_obj = JSON.parse(import_il);
  1846.  
  1847.             for (var key in _il_obj) {
  1848.                 if (_il_obj[key].hasOwnProperty('term') && _il_obj[key].hasOwnProperty('name') && _il_obj[key].hasOwnProperty('sound')) {
  1849.                     if (!includelist[key]) {
  1850.                         includelist[key] = {
  1851.                             term     : _il_obj[key].term,
  1852.                             name     : _il_obj[key].name,
  1853.                             sound    : _il_obj[key].sound,
  1854.                             noti_cb  : _il_obj[key].noti_cb,
  1855.                             sound_cb : _il_obj[key].sound_cb,
  1856.                             push_cb  : _il_obj[key].push_cb
  1857.                         };
  1858.                     }
  1859.                 }
  1860.                 else {
  1861.                     alert('An error occured while importing.\n\n Please check that you have a valid import and try again.');
  1862.                     break;
  1863.                 }
  1864.             }
  1865.             _init_lists();
  1866.         }
  1867.         else if (import_il.match(/^/)) {
  1868.             var _il_arr = import_il.split('^');
  1869.  
  1870.             for (var i = 0; i < _il_arr.length; i ++) {
  1871.                 if (!includelist[_il_arr[i]]) {
  1872.                     includelist[_il_arr[i]] = {
  1873.                         term     : _il_arr[i],
  1874.                         name     : _il_arr[i],
  1875.                         sound    : '1',
  1876.                         noti_cb  : true,
  1877.                         sound_cb : true,
  1878.                         push_cb  : false
  1879.                     };
  1880.                 }
  1881.             }
  1882.             _init_lists();
  1883.         }
  1884.     }
  1885.     else {
  1886.         alert('An error occured while importing.\n\n Please check that you have a valid import and try again.');
  1887.     }
  1888. }
  1889.  
  1890. function _export_include () {
  1891.     GM_setClipboard(localStorage.getItem('_finder_il'));
  1892.     alert('Your include list has been copied to your clipboard.');
  1893. }
  1894.  
  1895. function _export_vb (key) {
  1896.     var hit = hitlog[key];
  1897.  
  1898.     var pay = hit.to.pay,  _pay = '#B30000';
  1899.     if      (pay > 3.99) { _pay = '#00B300'; }
  1900.     else if (pay > 2.99) { _pay = '#B3B300'; }
  1901.     else if (pay > 1.99) { _pay = '#B37400'; }
  1902.  
  1903.     var fair = hit.to.fair, _fair = '#B30000';
  1904.     if      (fair > 3.99) { _fair = '#00B300'; }
  1905.     else if (fair > 2.99) { _fair = '#B3B300'; }
  1906.     else if (fair > 1.99) { _fair = '#B37400'; }
  1907.  
  1908.     var comm = hit.to.comm, _comm = '#B30000';
  1909.     if      (comm > 3.99) { _comm = '#00B300'; }
  1910.     else if (comm > 2.99) { _comm = '#B3B300'; }
  1911.     else if (comm > 1.99) { _comm = '#B37400'; }
  1912.  
  1913.     var fast = hit.to.fast, _fast = '#B30000';
  1914.     if      (fast > 3.99) { _fast = '#00B300'; }
  1915.     else if (fast > 2.99) { _fast = '#B3B300'; }
  1916.     else if (fast > 1.99) { _fast = '#B37400'; }
  1917.  
  1918.     var tv = hit.tv.replace(/\$/g, ''), _tvhourly = '#B30000';
  1919.     if ( tv != 'N/A' ) {
  1920.         if ( tv  >= 10 ) { _tvhourly = '#00B300'; }
  1921.         else if ( tv >= 7.25 ) { _tvhourly = '#B3B300'; }
  1922.         tv = '$' + tv + '/hr';
  1923.     }
  1924.  
  1925.     var quals = hit.quals.split(';');
  1926.     var qualif = '';
  1927.  
  1928.     for (var k = 0; k < quals.length; k ++) {
  1929.         if (quals[k] !== '') {
  1930.             if( quals[k].indexOf('~!~') != -1 ) {
  1931.                 var temp = quals[k].split('~!~');
  1932.                 quals[k] = temp[0];
  1933.                 if (temp[1].indexOf('request') == -1 ) {
  1934.                     //Qual test
  1935.                     quals[k] += '[URL=' +temp[1] +']Take Test[/URL]';
  1936.                 }
  1937.                 else {
  1938.                     //Request Qual
  1939.                     //quals[k] += '<form action="' +temp[1] +'" method="post"><button>Request</button></form>';
  1940.                 }
  1941.             }
  1942.             qualif += quals[k] + ';';
  1943.         }
  1944.     }
  1945.  
  1946.     var exportcode = '[table][tr][td]'+
  1947.         '[b]Title:[/b] [URL=' + pandaurl + hit.prevlink + ']' + hit.safetitle + '[/URL] | [URL=' + pandaurl + hit.pandlink + ']PANDA[/URL]\n' +
  1948.         '[b]Requester:[/b] [URL=' + pandaurl + hit.reqlink + ']' + hit.reqname + '[/URL] [' + hit.reqid + '] ([URL=' + hit.tvlink +']Req TV[/URL]): [B][COLOR=' + _tvhourly + ']' + tv + '[/COLOR][/B]\n' +
  1949.         '([URL='+hit.tolink+']TO[/URL]):'+
  1950.         '[b] \[Pay: [COLOR=' + _pay + ']' + pay + '[/COLOR]\][/b]'+
  1951.         '[b] \[Fair: [COLOR=' + _fair + ']' + fair + '[/COLOR]\][/b]' +
  1952.         '[b] \[Comm: [COLOR=' + _comm +']' + comm + '[/COLOR]\][/b]' +
  1953.         '[b] \[Fast: [COLOR=' + _fast + ']' + fast + '[/COLOR]\][/b]\n' +
  1954.         '[b]Description:[/b] ' + hit.desc + '\n' +
  1955.         '[b]Time:[/b] ' + hit.time + '\n' +
  1956.         '[b]HITs Available:[/b] ' + hit.avail + '\n' +
  1957.         '[b]Reward:[/b] [COLOR=green][b] ' + hit.reward + '[/b][/COLOR]\n' +
  1958.         '[b]Qualifications:[/b] ' + qualif + '\n' +
  1959.         '[/td][/tr][/table]';
  1960.  
  1961.     GM_setClipboard(exportcode);
  1962.     alert('Forum export has been copied to your clipboard.');
  1963. }
  1964.  
  1965. function _export_irc (key) {
  1966.     var hit = hitlog[key];
  1967.  
  1968.     $.get('https://ns4t.net/yourls-api.php?action=bulkshortener&title=MTurk&signature=39f6cf4959&urls[]=https://worker.mturk.com' + hit.prevlink + '&urls[]=https://worker.mturk.com' + hit.pandlink, function (data) {
  1969.         var urls = data.split(';'),
  1970.             preview = urls[0],
  1971.             panda   = urls[1];
  1972.  
  1973.         var exportcode = 'Req: ' + hit.reqname + ' ■ Title: ' + hit.safetitle + ' ■ Reward: ' + hit.reward;
  1974.         exportcode += preview !== panda ? ' ■ Prev: ' + preview + ' ■ PandA: '+ panda : ' ■ Search: ' + preview;
  1975.         exportcode += ' ■ TO: (Pay: ' + hit.to.pay + ') (Fair: ' + hit.to.fair + ') (Comm: ' + hit.to.comm + ') (Fast: ' + hit.to.fast + ')';
  1976.  
  1977.         GM_setClipboard(exportcode);
  1978.         alert('IRC export has been copied to your clipboard.');
  1979.  
  1980.     }).fail(function () {
  1981.         alert('Failed to shorten links.');
  1982.     });
  1983. }
  1984.  
  1985. function _json_validator (data) {
  1986.     try {
  1987.         JSON.parse(data);
  1988.         return true;
  1989.     }
  1990.     catch (e) {
  1991.         return false;
  1992.     }
  1993. }
  1994.  
  1995. function _export_slk (key) {
  1996.   var hit = hitlog[key];
  1997.   var quals = hit.quals.split(';');
  1998.   var qualif = '';
  1999.  
  2000.     for (var k = 0; k < quals.length; k ++) {
  2001.         if (quals[k] !== '') {
  2002.             if( quals[k].indexOf('~!~') != -1 ) {
  2003.                 var temp = quals[k].split('~!~');
  2004.                 quals[k] = temp[0];
  2005.                 if (temp[1].indexOf('request') == -1 ) {
  2006.                     //Qual test
  2007.                     quals[k] += '\nQual Test: https://worker.mturk.com' +temp[1] + '\n' ;
  2008.                 }
  2009.                 else {
  2010.                     //Request Qual
  2011.                     //quals[k] += '<form action="' +temp[1] +'" method="post"><button>Request</button></form>';
  2012.                 }
  2013.             }
  2014.           qualif += quals[k] + ';';
  2015.         }
  2016.       }
  2017.  
  2018.      var exportcode = 'Title: ' + hit.safetitle + ' • ' + pandaurl + hit.prevlink.replace(/\?ref=w_pl_prvw/, '') + ' • ' + pandaurl + hit.pandlink.replace(/\?ref=w_pl_prvw/, '') + '\n' +
  2019.         'Requester: ' + hit.reqname + ' • ' + pandaurl + hit.reqlink.replace(/\?ref=w_pl_prvw/, '') + '\n' +
  2020.         'TV: [Hrly: $' + hit.tv.replace(/\$/g, '') + '] [Fast:] [Comm:] [Fair:] [Reviews:] [ToS:] • ' + hit.tvlink + '\n' +
  2021.         'TO: [Pay: ' + hit.to.pay + '] [Fast: ' + hit.to.fast + '] [Comm: ' + hit.to.comm + '] [Fair: ' + hit.to.fair + '] • ' + hit.tolink + '\n' +
  2022.         'Reward: $' + hit.reward.replace(/\$/g, '') + '\n' +
  2023.         'Duration: ' + hit.time + '\n' +
  2024.         'Available: ' + hit.avail + '\n' +
  2025.         'Description: ' + hit.desc + '\n' +
  2026.         'Requirements: ' + quals + '\n'
  2027.        ;
  2028.   GM_setClipboard(exportcode);
  2029.   alert('Slack export has been copied to your clipboard.');
  2030. }
  2031.  
  2032. function _save (type) {
  2033.     if (type !== 'init' && type !== 'custom') {
  2034.         config.delay    = $('#delay')       .val();
  2035.         config.rew      = $('#min_rew')     .val();
  2036.         config.avail    = $('#min_avail')   .val();
  2037.         config.mto      = $('#min_to')      .val();
  2038.         config.alert    = $('#alert_delay') .val();
  2039.         config.type     = $('#type')        .val();
  2040.         config.size     = $('#size')        .val();
  2041.         config.newaudio = $('#new_audio')   .val();
  2042.         config.theme    = $('#adv_theme')   .val();
  2043.         config.to_theme = $('#to_theme')    .val();
  2044.  
  2045.         config.new  = $('#new_sound') .prop('checked');
  2046.         config.pb   = $('#pb')        .prop('checked');
  2047.         config.tts  = $('#tts')       .prop('checked');
  2048.         config.to   = $('#to')        .prop('checked');
  2049.         config.qual = $('#qual')      .prop('checked');
  2050.         config.nl   = $('#nl_hide')   .prop('checked');
  2051.         config.bl   = $('#bl_hide')   .prop('checked');
  2052.         config.m    = $('#m_hide')    .prop('checked');
  2053.         console.log($('#push').val());
  2054.  
  2055.     }
  2056.     if (type === 'custom' && $('#adv_theme').val() === 'custom') {
  2057.         config.custom = {
  2058.             main      : $('#theme_main')      .val(),
  2059.             primary   : $('#theme_primary')   .val(),
  2060.             secondary : $('#theme_secondary') .val(),
  2061.             text      : $('#theme_text')      .val(),
  2062.             link      : $('#theme_link')      .val(),
  2063.             visited   : $('#theme_visited')   .val(),
  2064.             prop      : false
  2065.         };
  2066.         themes.custom = config.custom;
  2067.     }
  2068.     config.push     = $('#push').val();
  2069.     config.tv_api_key = $('#tv_api_key').val();
  2070.  
  2071.     localStorage.setItem('_finder', JSON.stringify(config));
  2072.     localStorage.setItem('_finder_bl', JSON.stringify(blocklist));
  2073.     localStorage.setItem('_finder_il', JSON.stringify(includelist));
  2074.  
  2075.     if (config.nl) { $('.nl').toggleClass('nl nl_hidden');        }
  2076.     else           { $('.nl_hidden').toggleClass('nl nl_hidden'); }
  2077.  
  2078.     if (config.bl) { $('.bl').toggleClass('bl bl_hidden');        }
  2079.     else           { $('.bl_hidden').toggleClass('bl bl_hidden'); }
  2080.  
  2081.     if (config.m) { $('.m').toggleClass('m m_hidden');        }
  2082.     else          { $('.m_hidden').toggleClass('m m_hidden'); }
  2083. }
  2084.  
  2085. function _theme () {
  2086.     var theme = themes[config.theme];
  2087.  
  2088.     $('#theme_main')      .val(theme.main)      .prop('disabled', theme.prop);
  2089.     $('#theme_primary')   .val(theme.primary)   .prop('disabled', theme.prop);
  2090.     $('#theme_secondary') .val(theme.secondary) .prop('disabled', theme.prop);
  2091.     $('#theme_text')      .val(theme.text)      .prop('disabled', theme.prop);
  2092.     $('#theme_link')      .val(theme.link)      .prop('disabled', theme.prop);
  2093.     $('#theme_visited')   .val(theme.visited)   .prop('disabled', theme.prop);
  2094.     _write_theme();
  2095. }
  2096.  
  2097. function _write_theme () {
  2098.     var css  = _to_theme(), theme = themes[config.theme];
  2099.  
  2100.     css +=
  2101.         'html {color: #' + theme.text + '; background-color: #' + theme.main + '; line-height: 1.5; font-family: "Roboto", sans-serif; font-size: 15px; font-weight: normal;}' +
  2102.         'body {margin: 0px;}' +
  2103.         '#menubar { background-color: #' + theme.menu + '; margin: 0px; padding: 3px; height:30px; color: #' + theme.menutext + ' }' +
  2104.         '#latest_hits { margin: 5px; }' +
  2105.         '#logged_hits { margin: 5px; }' +
  2106.         '#config { background-color: #' + theme.primary + '; border: 2px #' + theme.text + ' solid; padding: 2px;}' +
  2107.  
  2108.         '#bl_items, #il_items {background-color: #'+theme.main+'; height: calc(100% - 64px); overflow-y: scroll;}' +
  2109.         '#bl_div, #il_div {background-color: #'+theme.primary+'; border: 2px solid #'+theme.secondary+';}' +
  2110.  
  2111.         '.add {background-color: #'+theme.primary+'; border: 2px solid #'+theme.secondary+';}' +
  2112.  
  2113.         '.bl {border: 2px solid  #FF0000;}' +
  2114.         '.il {border: 2px solid  #009900;}' +
  2115.         '.hidden, .nl_hidden, .bl_hidden, .m_hidden {display: none;}' +
  2116.         'button:focus {outline: none !important;}';
  2117.  
  2118.     if (config.theme == 'light' ) {
  2119.         css +=
  2120.             '.tvHigh    {background-color: rgba(0,128,0,0.3); }' +
  2121.             '.tvFair    {background-color: rgba(255,165, 0, 0.3);}' +
  2122.             '.tvLow     {background-color: rgba(255,0,0,0.3); }' +
  2123.             '.tvNone    {background-color: rgba(128,128,128, 0.3); }'
  2124.     }
  2125.  
  2126.     $('#css').html(css);
  2127. }
  2128.  
  2129. function _to_theme () {
  2130.     var to, theme = themes[config.theme], color = '';
  2131.     console.log(config.to_theme);
  2132.     if (config.theme === 'default') {
  2133.         color = 'd9d9d9';
  2134.     }
  2135.     else {
  2136.         color = '262626';
  2137.     }
  2138.  
  2139.     switch (config.to_theme) {
  2140.         case '1':
  2141.             to =
  2142.                 'td {font-weight: bold;}' +
  2143.                 '.cont, .hit, .details {color: #000000;}' +
  2144.                 '.toHigh    {background-color: #33cc59;}' +
  2145.                 '.toGood    {background-color: #a6cc33;}' +
  2146.                 '.toAverage {background-color: #cccc33;}' +
  2147.                 '.toLow     {background-color: #cca633;}' +
  2148.                 '.toPoor    {background-color: #cc3333;}' +
  2149.                 '.toNone    {background-color: #cccccc;}' +
  2150.                 '.rt, .it, .pc   {width: 20px; height: 20px; background-color: transparent; margin: 1px;  border: 1px solid  #000000; font-size: 80%; padding: 1px;}' +
  2151.                 '.vb, .irc, .slk {width: 25px; height: 20px; background-color: transparent; margin: 1px;  border: 1px solid  #000000; font-size: 80%; padding: 1px;}' +
  2152.                 '.clicked   {background-color:grey;}';
  2153.             ;
  2154.             return to;
  2155.         case '2':
  2156.             to =
  2157.                 'a         {color: #'+theme.link+';}' +
  2158.                 'a:visited {color: #'+theme.visited+';}' +
  2159.                 'tbody td  {color: #'+theme.text+';}' +
  2160.                 '.to a {color: #000000;}' +
  2161.  
  2162.                 '.cont, .details {color: #'+theme.text+';}' +
  2163.                 '.toHigh    {background-color: #'+color+';}' +
  2164.                 '.toGood    {background-color: #'+color+';}' +
  2165.                 '.toAverage {background-color: #'+color+';}' +
  2166.                 '.toLow     {background-color: #'+color+';}' +
  2167.                 '.toPoor    {background-color: #'+color+';}' +
  2168.                 '.toNone    {background-color: #'+color+';}' +
  2169.  
  2170.                 '.toHigh    .to {background-color: #33cc59;}' +
  2171.                 '.toGood    .to {background-color: #a6cc33;}' +
  2172.                 '.toAverage .to {background-color: #cccc33;}' +
  2173.                 '.toLow     .to {background-color: #cca633;}' +
  2174.                 '.toPoor    .to {background-color: #cc3333;}' +
  2175.                 '.toNone    .to {background-color: #cccccc;}' +
  2176.                 '.rt, .pc   {width: 20px; height: 20px; color: #'+theme.text+'; background-color: transparent; margin: 1px;  border: 1px solid  #'+theme.text+'; font-size: 80%; padding: 1px;}' +
  2177.                 '.vb, .irc, .slk {width: 25px; height: 20px; color: #'+theme.text+'; background-color: transparent; margin: 1px;  border: 1px solid  #'+theme.text+'; font-size: 80%; padding: 1px;}' +
  2178.                 '.clicked   {background-color:grey;}';
  2179.             ;
  2180.             return to;
  2181.         case '3':
  2182.             to =
  2183.                 'a         {color: #'+theme.link+';}' +
  2184.                 'a:visited {color: #'+theme.visited+';}' +
  2185.                 'tbody td  {color: #'+theme.text+';}' +
  2186.  
  2187.                 '.cont, .details {color: #'+theme.text+';}' +
  2188.                 '.toHigh    {background-color: #'+color+';}' +
  2189.                 '.toGood    {background-color: #'+color+';}' +
  2190.                 '.toAverage {background-color: #'+color+';}' +
  2191.                 '.toLow     {background-color: #'+color+';}' +
  2192.                 '.toPoor    {background-color: #'+color+';}' +
  2193.                 '.toNone    {background-color: #'+color+';}' +
  2194.  
  2195.                 '.toHigh    .to a {color: #33cc59;}' +
  2196.                 '.toGood    .to a {color: #a6cc33;}' +
  2197.                 '.toAverage .to a {color: #cccc33;}' +
  2198.                 '.toLow     .to a {color: #cca633;}' +
  2199.                 '.toPoor    .to a {color: #cc3333;}' +
  2200.                 '.toNone    .to a {color: #cccccc;}' +
  2201.                 '.rt, .pc   {width: 20px; height: 20px; color: #'+theme.text+'; background-color: transparent; margin: 1px;  border: 1px solid  #'+theme.text+'; font-size: 80%; padding: 1px;}' +
  2202.                 '.vb, .irc, .slk {width: 25px; height: 20px; color: #'+theme.text+'; background-color: transparent; margin: 1px;  border: 1px solid  #'+theme.text+'; font-size: 80%; padding: 1px;}' +
  2203.                 '.clicked   {background-color:grey;}';
  2204.             ;
  2205.             return to;
  2206.     }
  2207. }
  2208.  
  2209. $('#type option[value="' + config.type + '"]')          .prop('selected', true);
  2210. $('#size option[value="' + config.size + '"]')          .prop('selected', true);
  2211. $('#new_audio option[value="' + config.newaudio + '"]') .prop('selected', true);
  2212. $('#adv_theme option[value="' + config.theme + '"]')    .prop('selected', true);
  2213. $('#to_theme option[value="' + config.to_theme + '"]')  .prop('selected', true);
  2214.  
  2215. _theme();
  2216. _init_lists();
  2217. _hide_hit_list( config.h_hidden == '1' ? true : false );
  2218. _hide_log_list( config.l_hidden == '1' ? true : false );
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement