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", {});
}
}