NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name Autocomplete with Cache // @namespace TVMaze Autocomplete // @version 1.02 // @description Adds an autocomplete with local cache (that also acts an as optional search history) to TVMaze // @author gazza911 // @match http://www.tvmaze.com/* // @require https://code.jquery.com/ui/1.11.4/jquery-ui.js // @grant GM_getValue // @grant GM_setValue // @updateURL https://openuserjs.org/meta/gazza911/Autocomplete_with_Cache.meta.js // @license MIT // ==/UserScript== /* Instrcuctions / Read me This autocomplete uses the TVMaze API and will not be any more accurate than if you had searched for it; like the normal search, this is limited to the top 10 results. If a show is added/updated then you'll have to delete it from the cache in order to see the updated version - there's also a delay between the standard search and the API, this is out of my control as it is server side The cache is local meaning that no-one else (including me, the developer) will be able to see your search history - any data that might be collected by TVMaze when using the API is also out of my control Any images used are licensed so that they do not require a link-back and thus have been converted to base64 so that the images do not have to be stored anywhere To delete all settings/search history: 1. Change line #49 - which is normally reset(false); - to reset(true); 2. Reload any page on TVMaze once 3. Change it back to reset(false); To delete individual searches: 1. Select the search you want to delete from the dropdown 2. Click the grey 'X' to the right of the dropdown To hide/show search history: 1. Click the up/down arrow just below the search - when hidden, search results will still be stored for caching purposes but will not be displayed To change the cache time (how long each search result is stored) : 1. Change the value of 'longevity' (line #50) - this is the number of days, but any value including 0.5 for half a day would be accepted To see previous search results without typing it in again: 1. Select the show/person you want to see the search results for from the dropdown */ reset(false); var longevity = 2; var loadedAt = Date.now(); var cache = GM_getValue("tvmaze_Cache") || {}; if (Object.keys(cache).length === 0) { cache["shows"] = {}; cache["people"] = {}; GM_setValue("tvmaze_Cache", cache); } var accessTimes = GM_getValue("tvmaze_AccessTimes") || {}; if (Object.keys(accessTimes).length === 0) { accessTimes["shows"] = {}; accessTimes["people"] = {}; GM_setValue("tvmaze_AccessTimes", accessTimes); } var currentType = GM_getValue("tvmaze_Searchtype") || {}; if (currentType.length === undefined) { currentType = "shows"; GM_setValue("tvmaze_Searchtype", currentType); } var showRecentSearches = GM_getValue("tvmaze_ShowHistory") || {}; if (showRecentSearches["shows"] === undefined) { showRecentSearches["shows"] = true; GM_setValue("tvmaze_ShowHistory", showRecentSearches); } if (showRecentSearches["people"] === undefined) { showRecentSearches["people"] = true; GM_setValue("tvmaze_ShowHistory", showRecentSearches); } // Debug info - uncomment and view Google Chrome's console (F12) if you want to see how the data is stored /*console.log("Cache: " + Object.keys(cache["shows"]).length + "/" + Object.keys(cache["people"]).length); console.log(cache); console.log("Access: " + Object.keys(accessTimes["shows"]).length + "/" + Object.keys(accessTimes["people"]).length); console.log(accessTimes); console.log("Show"); console.log(showRecentSearches); console.log("Current Type: " + currentType);*/ function getHistory() { var count = 0; if (accessTimes["shows"].length > 0) sort(accessTimes["shows"]); if (accessTimes["people"].length > 0) sort(accessTimes["poeple"]); var history; var deleted = 0; var recentSearches = '<div id="recentHistory" style="margin-top:10px;display:none"><label for="recent" style="color:white; float:left; margin-right:11px">Your recent searches:</label><select id="recent" style="max-width:27.3rem; height:22px; padding:0.1rem;float:left;"><option value="" disabled selected>Click to see</option>'; for (var key in accessTimes[currentType]) { if (accessTimes[currentType].hasOwnProperty(key)) { if (loadedAt - accessTimes[currentType][key] > 1000 * 60 * 60 * 24 * longevity) // remove if over specifed amount of days { delete accessTimes[currentType][key]; delete cache[currentType][key]; deleted++; } else { history = '<option value="' + key + '">' + key + '</option>' + history; count++; } } } if (deleted > 0) { GM_setValue("tvmaze_AccessTimes", accessTimes); GM_setValue("tvmaze_Cache", cache); } recentSearches = recentSearches + history + '</select><img id="remove" title="This will also remove it from the cache" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAADQ0lEQVR4Xu2UXUwUVxSAz8zsLzu7q7ism4AF1qBJA2lCBBe1gOHPpAk/+wcsIBQ2qDVNjLHaNBZXjO9tt/ahBrWtiYpgLEnTlDTBhBcghEQfpBVE2TWNDw3s7szsWpyZ65m4xpQgg+lL0/RMvuTOnMn57r1zz8D/8e+jublpR6C9dbQ72PWbt9kbVBP4WtwfdHS1PWwN+Me9Ld49q/P0aoEM9LHKyoqa6urqnc78vIv+Fm/oTQKcgC/LZv+xrrbOWVtbt0/HMGFVARAqS5ZliMfjUFRUBLm5eWcUyVrF7Vm2a2VlZQzHJYBhGJBkWdiIoH9qapLX6/QvJYWFKMn9m8Tv9/iV4i6Xi+ESCTCZWJiYnJCIJJ+HjYSnxbO7O9jJXRz4lnz19Rfk8pUBcuLkcaJIlOKHP+oVlWdhJffdJaLcKyuCNYJaT2Jl2V9LS11sKiWAmbXA/dn7IAgCKdlVQvECB6zJDNPT09KfS0utQzeGbqoI1paYM0yjJSWllmQSJWYzUHjxPAcmloWZmRkptrQcGBwcHoT1Qk3S2X0wGb7wJQn1f076QqfJhW/CpPdwj6xsF6gErSagCJVv1Ov1siQBIQQTBJ6LIhgMRgpo6t1/JMAZtmZu3nS1oGAHvRxbBovFqgB4LJWTBXa7ffUR3rgAOzRg2WT9YbvTycTiMTAaMmB2dla+e+9eSqPRQCLBwTvbtoHNZltXQr+puNVi/T4vL5+JJ+Kg1xtgbn5OisUTbclnqf0oSmhRwnEc5OTkwJYtma8lavh8vgr8D4mh/j5y8tMT+GHPkA97ukSPp7Ed01pEV++u39vR2RYPne0jn50+RfrPhcihI73YJ56jqiugaPlodnY2diiHM9fB4uIj6enTPz4eHr49hmmHwsitkcXHkUhg/uE8R9MM8AIPDocDtDrdKVWBTOBJUhCUlyESiUrRaPSTn38avYOpLGTrK8bHxiO/P5gLLiws8BRFQSqVAnFFfABqUVVVtdXtaQw3eRt/qax6X9mWnch7yG6kHNmP7EV2IYXFxcUHGprqr7o9DddrasoLNtrJWsSI6NNo0zDpPEFE5DmygvyF8OnxWwWFaBADkoGYETY9NqSlFPyn4wXDv0nI59mhOQAAAABJRU5ErkJggg==" style="height:32px; padding-left:5px; padding-bottom:10px;float:left; border:1px solid #3f3f3f"></img></div>'; $("#recentHistory").remove(); $("#toggleRecent").remove(); $("toggleChange").remove(); if (count > 0) { $(recentSearches).insertAfter('form:first div:first'); $('<div id="toggleRecent" style="position:relative;top:37px;height:15px;width:100%;background-color:#595959"></div>').appendTo('form:first div:first'); $('<img id="toggleChange" style="position:absolute;left:50%;" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEwAACxMBAJqcGAAAAGJJREFUOI3tzUEKQFAUheFvTsk2JGzVhpQpS5E1MDF4qRdPJsqpO7n3/P/lz2sp0SO70c2PbhEua6wYLiQ5RiyozsfuQhLCTexDTHILjkmS4LNkwpwKh5LtmGQ4lLRP4Y9kB5U7Fwi6X3XpAAAAAElFTkSuQmCC"></img>').appendTo("#toggleRecent"); if (showRecentSearches[currentType] === true) { $("#toggleChange").css( { '-webkit-transform' : 'rotate(180deg)', 'transform' : 'rotate(180deg)' }); $("#recentHistory").toggle(); } $("#toggleRecent").click(function() { $("#recentHistory").slideToggle(); showRecentSearches[currentType] = !showRecentSearches[currentType]; GM_setValue("tvmaze_ShowHistory", showRecentSearches); setTimeout(flip,200); }); $("#recent").change(function() { $("#searchform-q").val($(this).val()); $("#searchform-q").focus(); $("#searchform-q").trigger("input"); }); $("#remove").mouseover(function(){ $(this).css("border", ""); $(this).css("cursor", "pointer"); }); $("#remove").mouseout(function(){ $(this).css("border", "1px solid #3f3f3f"); $(this).css("cursor", "default"); }); $("#remove").click(function() { var key = $("#recent").val(); delete accessTimes[currentType][key]; delete cache[currentType][key]; GM_setValue("tvmaze_Cache", cache); GM_setValue("tvmaze_AccessTimes", accessTimes); $("#recent option:selected").remove(); }); } } function flip() { if (showRecentSearches[currentType] === true) { $("#toggleChange").css( { '-webkit-transform' : 'rotate(180deg)', 'transform' : 'rotate(180deg)' }); } else { $("#toggleChange").css( { '-webkit-transform' : 'rotate(360deg)', 'transform' : 'rotate(360deg)' }); } } $('<div style="margin-top:10px;margin-left:20px;color:white">Autocomplete for<input type="radio" value="shows" name="type" style="margin-left:10px;"> Shows <input type="radio" value="people" name="type" style="margin-left:15px;"> People</div>').appendTo('form:first'); $('input:radio[value="' + currentType + '"]').prop("checked", true ); getHistory(); $('input:radio').change(function() { currentType = $("input:radio:checked").val(); GM_setValue("tvmaze_Searchtype", currentType); $("#searchform-q").focus(); $("#searchform-q").trigger("input"); getHistory(); }); $('head').append('<link rel="stylesheet" href="//code.jquery.com/ui/1.11.4/themes/smoothness/jquery-ui.css">'); $("#searchform-q").autocomplete({ source: function (request, response) { cache = GM_getValue("tvmaze_Cache") || {}; accessTimes = GM_getValue("tvmaze_AccessTimes") || {}; var term = request.term; accessTimes[currentType][term] = Date.now(); GM_setValue("tvmaze_AccessTimes", accessTimes); if (cache != undefined && term in cache[currentType] ) { response(cache[currentType][ term ]); console.log("in cache"); checkWidth(); return; } console.log("not in cache"); $.ajax( { url: "http://api.tvmaze.com/search/" + currentType + "?q=" + request.term, method: "GET", success: function (data) { input = request.term; cache[currentType][ term ] = transform(data); GM_setValue("tvmaze_Cache", cache); response(cache[currentType][ term ]); return; }, error: function () { response([]); }, complete: checkWidth() }); }, minLength: 4, delay:500, select: function( event, ui ) { window.location.assign(ui.item.link); } }) .autocomplete( "instance" )._renderItem = function( ul, item ) { if (item != undefined) { var element = $( '<li>' ); $(element).append( '<span class="ui-autocomplete-label">' + item.label + '</span>'); if (currentType == "shows") { var showDetails = '<span style="position:absolute; right:32px; top:8px; font-size:10px; color:grey;">'; if (item.year != null) { showDetails += item.year; } showDetails += '</span>'; if (item.country != null) showDetails += " <img style='position:absolute; right:10px; top:11px;' src='http://tvmazecdn.com/intvendor/flags/" + item.country.toLowerCase() + ".png'></img>"; $(showDetails).appendTo( element ); } $(element).appendTo( ul ); return element; } }; function checkWidth() { var menu = $(".ui-autocomplete").width(); var max = Math.max.apply(null, $(".ui-autocomplete-label").map(function () { return $(this).width(); }).get()); if (menu - max <= 80) $(".ui-autocomplete").width(max + 80 + "px"); } function transform(data, type) { var transformed = $.map(data, function (el) { if (currentType == "shows") { return { label: el.show.name, id: el.score, link: el.show.url, year: (el.show.premiered != null) ? el.show.premiered.split("-")[0] : null, country: (el.show.network != undefined) ? el.show.network.country.code : el.show.webChannel.country.code }; } else if (currentType == "people") { return { label: el.person.name, id: el.score, link: el.person.url }; } }); return transformed; } function sort(array){ Object.keys(array).sort( function(keyA, keyB) { return array[keyA] - array[keyB]; }); } function reset(input) { if (input == true) { GM_setValue("tvmaze_Cache", {}); GM_setValue("tvmaze_AccessTimes", {}); GM_setValue("tvmaze_ShowHistory", {}); GM_setValue("tvmaze_Searchtype", {}); } }