I am one of those people that don’t believe in paying $50 for cable. I think it is more because I am poor than cheap. Anyway, I have always loved streaming content from the internet to my tv. Netflix through my Xbox 360 is great and I couldn’t be any happier. I wasn’t much of a TV watcher, but after I became addicted to shows like Modern Family, The Middle, and Stargate Universe I started watching Hulu religiously. I love the fact that for $20 I can stream from Netflix and Hulu. Much better price tag than $50+ for cable or satellite. With both services, I have always wanted to perform a search on Netflix, and have it tell me if that TV show or Movie is on Hulu. The hard part is Hulu doesn’t have a public api for searching their video library. That is where Kynetx comes in!
I haven’t done a good job keeping up with the advancements in the KRL language, so when I sat down to make an application for the Developer Contest Saturday night, it was quite the project. Since Hulu doesn’t have a public api, the only thing I have to work with is the Hulu search page. Because of cross-domain security I couldn’t perform a simple ajax call within the jquery, so I used a datasource. A datasource is for json, not html, but it was a simple way to search Hulu without worrying about same origin policy. The search page was stored in a datasource, and all I had to do was make a javascript variable to put the datasource in so I could traverse the DOM with jquery.
Since searching Hulu doesn’t give specific information, like year released or anything there are some searches that don’t tell the whole truth. Each search result in Netflix is compared with each search result in Hulu. If there is a match, then it will insert the Hulu logo on the corresponding Netflix result. Hulu splits the season number and name of the show, whereas Netflix doesn’t, so any name with a colon “:” is shortened to contain only the text before the colon. So, Modern Family:Season 1 is compared as Modern Family. A good example is to search for “All Dogs Go to Heaven” in Netflix with this Kynetx app. Since all the results contain “All Dogs Go to Heaven” the hulu logo is put on all of the Netflix results. However, All Dogs Go to Heaven 2 is stream-able on Hulu. With the 2 at the end it is more unique, so it shows only one correct result on Netflix! (Try the app out searching for “Reba”, “30 Rock”, “Modern Family”. Those were some of what I used to test.)
Up to 3 Hulu results are shown per Netflix result. Hovering over the hulu logo will reveal the title of the matched video. Clicking on the hulu logo will take you directly to that hulu video, and it will start playing!
That is the nitty gritty of the program. I think the coolest thing is taking information from a source that doesn’t provide an api, and making a mashup possible! Talk about augmenting the web!
Download the Chrome Browser Extension
Download the Firefox Browser Extension
Give it a try and leave comments with suggestions and feedback. Don’t be too harsh though! I started working on this app late Saturday (Dec 4) and finished it early morning Sunday. (Less than 24 hrs.) Be sure to visit the Kynetx Development Center to find out how to create your own app!





















Beating Google
Google recently released an extension that would allow for users to specify ‘spam’ search results. Kynetx wants to take that a step further with an application that will do the same thing in Firefox and Internet Explorer as well as Chrome. The application had to be built in 24 hours. I always like a good challenge and learning something new, so I took a stab at it.
I wanted to keep the minimalistic idea that Google had with their extension, but make it more visible and useable. When you hover over a search result an option to remove the result is presented.
Internet Explorer Extension
Firefox Extension
Chrome Extension
Can’t restore a search result in IE.
Yahoo doesn’t seem to work as well.
Sometimes google doesn’t refresh the page and hide elements after they have been removed(disable instantSearch)
After spending 2 hours working on IE I gave up. I could have figured it out with more time, but the debug tools on IE suck… oh wait… there aren’t any! I think I am going to join an anti-develop-for-IE group.
ruleset a279x11 {
meta {
name "Hoogleing"
description >>
<<
author "David"
logging off
}
dispatch {
domain "google.com"
domain "bing.com"
domain "yahoo.com"
}
global {
//set the datasource for the google spreadsheet if the key is supplied in the app
datasource googleSheets >- "https://spreadsheets.google.com/"
css >>
.restoreResultKRL { visibility:visible; float:right; background-color: white; background-image: url(http://davidgodfrey.info/images/search/myCheck.png); background-repeat: no-repeat; border: medium none; padding-left: 20px; cursor: pointer; }
.removeResultKRL { visibility:hidden; float:right; background-color: white; background-image: url(http://davidgodfrey.info/images/search/myX.png); background-repeat: no-repeat; border: medium none; padding-left: 20px; cursor: pointer; }
.removeResultKRL,
.restoreResultKRL { background-color: #fffddd; padding: 5px 5px 5px 20px; background-position: 2px 4px; border: 1px solid #efefef; }
.SPAMremove:hover .restoreResultKRL,
.SPAMremove:hover .removeResultKRL { visibility:visible }
<<;
}
//This rule sets up the notify box at the top right of the screen
rule first_rule is active {
select using "google.com|bing.com/search|search.yahoo.com/search" setting()
pre {
message = >>
>span id="spamSearchCatcher"<0>/span< search results removed.>br /<>br /<
>button type="button" id="spanSeeRemoved" style="display:none; background: none; background-image: url(http://davidgodfrey.info/images/search/restoreA.png); background-repeat: no-repeat; border: medium none; padding-left: 20px; cursor: pointer; color: #68b4ef;"<See removed results>/button<>br /<>br /<
>button type="button" id="SPAMexternalGoogle" style="background: none; background-image: url(http://davidgodfrey.info/images/search/google.png); background-repeat: no-repeat; border: medium none; padding-left: 20px; cursor: pointer; color: #56c42e;"<Google Spreadsheet settings>/button<>br /<
>span id="showExternalGoogle" style="display:none;"<
>label<Google Spreadsheet Key>/label<>br /<>input style="margin: 5px 0 10px 0;" id="SPAMGoogleKey" type="text"<>/input<>br /<
>label<Google Form Key>/label<>br /<>input style="margin: 5px 0 10px 0;" id="SPAMGoogleFormKey" type="text"<>/input<>br /<
>button id="SPAMsubmitGoogleInfo" type="button"<Save>/button<
>/span<
>br /<
>span id="SPAMremoveNotice" style="display:none;"<It looks like you are using a Google Spreadsheet! To restore a result, you will have to remove the row from the Google Spreadsheet manually.>/span<
<<;
}
emit >|
$K('#spanSeeRemoved').live('click', function(){
$K('.SPAMremove').show();
//$K('#spanSeeRemoved').fadeOut();
});
$K('#SPAMexternalGoogle').live('click', function(){
$K('#showExternalGoogle').toggle();
});
|<
notify("", message) with sticky = true;
}
rule search_annotate_rule is active {
select using "google.com|bing.com/search|search.yahoo.com/search" setting()
pre {
//the variable that will hold the removed sites.
sites = ent:my_sites;
//get the google spreadsheet key and then get the csv
gKey = ent:my_gKey;
csvFromGoogleDoc = datasource:googleSheets("pub?hl=en&hl=en&key=" + gKey + "&single=true&gid=0&output=csv");
}
emit >|
//split the csv into a javascript array
if(csvFromGoogleDoc.length < 20){
var tempString = csvFromGoogleDoc.replace(/(\r\n|[\r\n])/g, ",");
var googleValues = tempString.split(',');
$K('#SPAMremoveNotice').show();
}else{
var googleValues = [0];
}
|<
every {
emit >|
function annotate_spamRemove(toAnnotate, wrapper, data) {
if ($K.inArray(data.domain, sites) != -1 || $K.inArray(data.domain, googleValues) != -1) {
toAnnotate.hide();
$K('#spamSearchCatcher').html(($K('#spamSearchCatcher').html()*1)+1);
$K('#spanSeeRemoved').show();
wrapper.append(">button type='button' domain='"+data.domain+"' class='restoreResultKRL'<restore result>/button<");
wrapper.show();
}else{
wrapper.append(">button type='button' domain='"+data.domain+"' class='removeResultKRL'<remove result>/button<");
wrapper.show();
}
toAnnotate.addClass('SPAMremove');
}
app = KOBJ.get_application("a279x11");
$K('.removeResultKRL').live('click', function(){
theDomain = $K(this).attr('domain');
$K(this).removeClass('removeResultKRL').addClass('restoreResultKRL').text('removed');
$K(this).closest('.SPAMremove').delay(1000).fadeOut('fast', function(){$K('.restoreResultKRL').text('restore result');});
app.raise_event("remove_result_from_search", {"myDomain":theDomain});
});
$K('.restoreResultKRL').live('click', function(){
theDomain = $K(this).attr('domain');
$K(this).addClass('removeResultKRL').removeClass('restoreResultKRL').text('remove result');
app.raise_event("restore_result_from_search", {"myDomain":theDomain});
});
$K('#SPAMsubmitGoogleInfo').live('click', function(){
$K('#SPAMsubmitGoogleInfo').text('Saved').attr('disabled', 'disabled');
googleKey = $K('#SPAMGoogleKey').val();
googleFormKey = $K('#SPAMGoogleFormKey').val();
app.raise_event("store_google_keys", {"Gkey":googleKey, "GFkey":googleFormKey});
});
|<;
annotate:annotate("SPAMremove") with annotator = >| annotate_spamRemove |<;
}
}
rule custom_event_for_removing_result {
select when web remove_result_from_search
pre {
gKey = ent:my_gKey;
gfKey = ent:my_gfKey;
sites = ent:my_sites;
domain = event:param("myDomain");
new_array = sites.union(domain);
}
emit >|
if(gKey.length < 20 && gfKey.length < 20){
app = KOBJ.get_application("a279x11");
app.raise_event("save_in_google", {"myDomain":domain });
}
|<
fired {
set ent:my_sites new_array;
}
}
rule custom_event_for_restoring_result {
select when web restore_result_from_search
pre {
sites = ent:my_sites;
domain = event:param("myDomain");
new_array = sites.difference(domain);
}
fired {
set ent:my_sites new_array;
}
}
rule custom_event_store_google_keys {
select when web store_google_keys
pre {
gKey = event:param("Gkey");
gfKey = event:param("GFkey");
}
fired {
set ent:my_gKey gKey;
set ent:my_gfKey gfKey;
}
}
rule custom_event_save_in_google {
select when web save_in_google
pre {
domain = event:param("myDomain");
gfKey = ent:my_gfKey;
}
{
http:post("https://spreadsheets.google.com/formResponse?hl=en&formkey=" + gfKey + "&ifq") with params = { "entry.0.single":domain, "submit":"Submit", "pageNumber":"0", "backupCache":"" };
}
}
}