Skip to content

Commit

Permalink
Merged branch 'Feature-User-Authentication' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
ignat980 committed May 2, 2016
2 parents e3a38d6 + 382868c commit fc94a10
Show file tree
Hide file tree
Showing 6 changed files with 239 additions and 25 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@ This is an extension for [YouTube](https://www.youtube.com). It is meant to be l
Since [Magic Actions](https://chrome.google.com/webstore/detail/magic-actions-for-youtube/abjcfabbhafbcdfjoecdgepllmpfceif) may stop working, I wanted to start this project.

###Current features:
* Displays Total Playlist Length for public playlists with less than 250 videos
* Displays Total Playlist Length for public and private playlists

###Working on:
* View total playlist length for private playlists
* Caching playlist requests

###Planned:
Expand All @@ -18,3 +17,4 @@ Since [Magic Actions](https://chrome.google.com/webstore/detail/magic-actions-fo
* Enlarge photo on mouseover
* Hotkeys Customization (Play/Pause, Skip, Volume, Speed, etc.)
* Easy Playlist Creation
* Watch Playlist in reverse order
17 changes: 17 additions & 0 deletions js/background.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
var saved_token
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
console.log("Recieved a message from", sender.tab ?
"a content script: " + sender.tab.url :
"the extension");
console.log(request);
console.log("The token is", saved_token);
if (request.name === 'getAuthToken'){
sendResponse(saved_token)
} else if (request.name === 'setupToken') {
chrome.identity.getAuthToken({'interactive': false}, token => {
saved_token = token
console.log("Token set");
})
}
});
157 changes: 157 additions & 0 deletions js/getPageTokens.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
// Copyright (c) 2016 Ignat Remizov. All rights reserved.
;(function(root, undefined) {

/*#### HELPER METHODS ####*/
/**
* Adds formatting to strings, used like "Hello {0}!".format("World")
*
* @source https://stackoverflow.com/a/4673436/3923022 and https://stackoverflow.com/a/18234317/3923022
*/
function addFormatStringFunction() {
if (!String.prototype.format) {
String.prototype.format = function() {
var args = typeof arguments[0] === 'object' ? arguments[0] : arguments;
return this.replace(/{(\d?[A-Za-z]?)}/gi, (match, arg) =>
typeof args[arg] != 'undefined' ? args[arg] : match)
};
}
}

/**
* Runs a for loop asynchronously, call the funtion passed to your loop when you want the loop to run again
*
* @param: {object} o - an object that has
* a 'length' property for how many times to iterate,
* a 'loop' property which is the iteration body,
* a 'callback' property that gets called at the end of the for loop
* @source https://stackoverflow.com/a/7654602/3923022
*/
var AsyncLooper = function(o) {
this.i = -1;
this.length = o.i
this.o = o
this.loop();//start iterating
}

AsyncLooper.prototype.loop = function() {
this.i++;
if(this.i === this.length) {
if (this.o.callback) {
this.o.callback();
};
return;
};
this.o.loop(this.i);
};

/**
* @param {string} url - The URL to get from
* @param {function(Response)} callback - Called when the GET json request finishes
* @param {function(Error)} errorCallback - Called when the request fails or returns an error
*/
function asyncJsonGET(url, callback, errorCallback) {
var x = new XMLHttpRequest();
x.open("GET", url);
// x.setRequestHeader("If-None-Match","etag")
x.responseType = 'json';
x.onload = function() {
if (x.status === 400 || x.status === 404) {
errorCallback(x);
} else if (!x.response) {
errorCallback("No response.");
} else if (x.response.error) {
errorCallback(x.response.error);
} else {
callback(x.response);
};
};
x.onerror = function() {
errorCallback('Network error.');
};
x.send(null);
};

/**
* Read a local json text file.
*
* @param {string} file - The filepath to the json resourse
* @param {function(string)} callback - called when the json file has been read.
* @source - http://stackoverflow.com/a/34579496/3923022
*/

function readJsonFile(file, callback) {
var raw_file = new XMLHttpRequest();
raw_file.overrideMimeType("application/json");
raw_file.open("GET", file, true);
raw_file.onreadystatechange = function() {
if (raw_file.readyState === 4 && raw_file.status == "200") {
callback(raw_file.responseText);
};
};
raw_file.send(null);
};


/*#### MAIN METHODS ####*/


/**
* Calls the Youtube API to read the length of the current playlist
*
* @param {string} pl_id - The ID for the playlist to get
* @param {string} key - The Youtube data v3 api key
* @param {function(string)} callback - called when the length of a Youtube Playlist is parsed
*/
function getPageTokens(pl_id, key, callback) {
console.log("Getting tokens for playlist");
var playlist_api_url = "https://www.googleapis.com/youtube/v3/playlistItems" +
"?part=contentDetails&maxResults=50&playlistId={0}" +
"&fields=nextPageToken%2CprevPageToken{1}&key=" + key;
var token; // Next page token
var pageTokens = []; // Used to store all page tokens
var looper = new AsyncLooper({
// i changes after the first request because no idea about total videos before
'i': 1,
'loop': (i) => {
// Call /playlistItems for the first 50 items
var first_time = (i === 0);
asyncJsonGET(playlist_api_url.format(pl_id, (first_time ? "%2CpageInfo%2FtotalResults" : "")) + (token ? "&pageToken=" + token : ""), res => {
console.log("Next 50 Playlist Items:", res);
token = res.nextPageToken;
pageTokens.push(token);
// If this is the first pass, set the total results and total index to iterate over
if (first_time) {
totalResults = res.pageInfo.totalResults;
looper.length = Math.ceil(totalResults/50);//(totalResults < 250 ? Math.ceil(totalResults/50) : 5); //Number of requests to make
console.log("Looping " + looper.length + " times");
};
looper.loop();
}, err => {
console.error(err);
}); //Close Playlist items call
},
'callback': () => {
console.log("Finished requesting");
console.log(pageTokens);
}
}); //Close Async loop
};

/**
* Run on script load
*/
console.log('Script running');

addFormatStringFunction() //Add .format() method to strings
var keys_URL = chrome.extension.getURL("keys.json");
//Read the private keys file, the key is used in the request to get playlist length data
readJsonFile(keys_URL, json => {
var keys = JSON.parse(json);
//This regex gets the playlist id
var list_regex = /(?:https?:\/\/)www\.youtube\.com\/(?:(?:playlist)|(?:watch))\?.*?(?:list=([A-z\d-]+)).*/;
var url = document.location.href;
var list_id = url.match(list_regex)[1];
console.log("Playlist id:",list_id);
getPageTokens(list_id, keys["YTDataAPIKey"]);
});
})(this);
60 changes: 41 additions & 19 deletions js/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,16 +136,17 @@ function formatDuration(duration, format_string) {
var length;
if (format_string === "long") {
format = [
duration.years() === 1 ? "Year" : "Years",
duration.months() === 1 ? "Month" : "Months",
duration.days() === 1 ? "Day" : "Days",
duration.hours() === 1 ? "Hour" : "Hours",
duration.minutes() === 1 ? "Minute" : "Minutes",
duration.seconds() === 1 ? "Second" : "Seconds"
];
length = duration.format("M [" + format[0] + "] d [" + format[1] +
"] h [" + format[2] + "] m [" + format[3] + " and] s [" + format[4] + "]");
length = duration.format("y [" + format[0] + "] M [" + format[1] + "] d [" + format[2] +
"] h [" + format[3] + "] m [" + format[4] + " and] s [" + format[5] + "]");
} else if (format_string === "short") {
length = duration.format("M[m] d[d] h:mm:ss");
length = duration.format("y[y] M[m] d[d] h:mm:ss");
} else {
length = duration.format(format_string);
};
Expand Down Expand Up @@ -287,14 +288,14 @@ function testingEtag(url, etag, callback) {
* @param {string} key - The Youtube data v3 api key
* @param {function(string)} callback - called when the length of a Youtube Playlist is parsed
*/
function getPlaylistLength(pl_id, key, callback) {
function getPlaylistLength(pl_id, key, callback, oauth) {
console.log("Getting playlist length");
// TODO: cache etag to quickly return playlist length !important
// Api url to get video id's from playlistItems
var pl_api_url = "https://www.googleapis.com/youtube/v3/playlistItems"
var pl_api_query = "?part=contentDetails&maxResults=50"
var pl_api_params = "&fields=items%2FcontentDetails%2CnextPageToken%2CprevPageToken";
var pl_api_key = "&key=" + key;
var pl_api_key = "&key=" + key + (oauth ? "&access_token=" + oauth : "");
// Api url to get video durations given a bunch of video id's
var videos_api_url = "https://www.googleapis.com/youtube/v3/videos" +
"?part=contentDetails&id={0}&fields=items%2FcontentDetails%2Fduration&key=" + key;
Expand Down Expand Up @@ -332,7 +333,7 @@ function getPlaylistLength(pl_id, key, callback) {
// If this is the last page, sum all durations together and return to the callback
console.log("Page", async_i, "and total pages is", pages);
if (async_i === pages - 1) {
console.log("Finished Requesting on page", i);
console.log("Finished Requesting on page", async_i);
length = formatDuration(sumLengthsIntoDuration(durations),
document.location.pathname === "/playlist" ? "long" : "short");
callback(length);
Expand All @@ -347,13 +348,46 @@ function getPlaylistLength(pl_id, key, callback) {
} //Close for loop
}, err => {
console.error(err);
if (err.code === 403 && flag == undefined) {
flag = false
console.log("Used token at", new Date().getTime());
console.log("Token:", token);
getPlaylistLength(pl_id, key, callback, token);
};
}); //Close get to playlist total video count
};

var flag
/**
* Run on script load
*/
console.log('Script running');
chrome.runtime.sendMessage({name: 'setupToken'});
var token;
var timeout;
function assignToken(){
chrome.runtime.sendMessage({name: 'getAuthToken'}, function(res) {
token = res;
console.log("Assigning token on init:", token);
if (token == undefined) {
timeout = setTimeout(assignToken, 100);
} else {
clearTimeout(timeout);
//Read the private keys file, the key is used in the request to get playlist length data
readJsonFile(keys_URL, json => {
var keys = JSON.parse(json);
//This regex gets the playlist id
var list_regex = /(?:https?:\/\/)www\.youtube\.com\/(?:(?:playlist)|(?:watch))\?.*?(?:list=([A-z\d-]+)).*/;
var url = document.location.href;
var list_id = url.match(list_regex)[1];
console.log("Playlist id:",list_id);
getPlaylistLength(list_id, keys["YTDataAPIKey"],
renderLengthToDOM
, token);
});
}
});
};
timeout = setTimeout(assignToken, 100);
var spinner = document.createElement('span'); //An animated gif used to show the user that something is loading
spinner.setAttribute('class', 'yt-spinner-img yt-sprite');
spinner.setAttribute('id', 'pl-loader-gif');
Expand Down Expand Up @@ -388,17 +422,5 @@ readJsonFile(tokens_URL, json => {
console.log("Page Tokens read");
pageTokens = JSON.parse(json)["pageTokens"];
});
//Read the private keys file, the key is used in the request to get playlist length data
readJsonFile(keys_URL, json => {
var keys = JSON.parse(json);
//This regex gets the playlist id
var list_regex = /(?:https?:\/\/)www\.youtube\.com\/(?:(?:playlist)|(?:watch))\?.*?(?:list=([A-z\d-]+)).*/;
var url = document.location.href;
var list_id = url.match(list_regex)[1];
console.log("Playlist id:",list_id);
getPlaylistLength(list_id, keys["YTDataAPIKey"],
renderLengthToDOM
);
});

})(this);
24 changes: 21 additions & 3 deletions manifest.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
{
"author": "Ignat Remizov",
"manifest_version": 2,
"name": "Playlist Length",
"name": "YouTube Extension Suite",
"version": "0.0.1",
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0pj6Z8vYLAIUHpJxYYS2HYc1nV8XJD5crFiW9Ts1pILzkyxGtLj+bw3caBP0Ys94PRIgwxk9UXRNMKHZ63BvMYbhNIYiqpva4wqiAmjghBkOdQ2c3SmDax8IS7vDDv2k5uctsGTuujQ5D6Q+gfyH0ThYjGnxF/fBDWeMgmuR4cpeWqFbIJOeZ0Nca5gq98PQ/LSxvzYJFh7gVQpCM3Fo18PzttGGOG2p56YKqseC49k+iGt8IyBr5Ze0fJJMA0erK5jfNaOZTPUmUy7iWmM7M3WZws8ue6uklcrfoi+RjmVqMReP5JqzMEsBuKiVHJ3SuIuwlb/9tzxOsm0LHg4lGwIDAQAB",

"description": "This extesnion will list the length of time it takes to watch an entire youtube playlist",
"icons": { "16": "images/Icon16.png",
"48": "images/Icon48.png",
"128": "images/Icon128.png" },

"author": "Ignat Remizov",
"permissions": [
"identity",
"*://*.youtube.com/watch*&list*",
"*://*.youtube.com/playlist*"
],

"background": {
"scripts": ["js/background.js"],
"persistent": false
},

"content_scripts": [
{
Expand All @@ -22,5 +33,12 @@
"keys.json",
"pageTokens.json",
"js/*.js"
]
],

"oauth2": {
"client_id": "940024462417-5ais2trpuo7htdvonu4ulsmoia54fnsc.apps.googleusercontent.com",
"scopes": [
"https://www.googleapis.com/auth/youtube.readonly"
]
}
}
2 changes: 1 addition & 1 deletion pageTokens.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"pageTokens" : ["CDIQAQ", "CDIQAA", "CGQQAA", "CJYBEAA", "CMgBEAA", "CPoBEAA", "CKwCEAA", "CN4CEAA", "CJADEAA", "CMIDEAA", "CPQDEAA", "CKYEEAA", "CNgEEAA", "CIoFEAA", "CLwFEAA", "CO4FEAA", "CKAGEAA", "CNIGEAA", "CIQHEAA", "CLYHEAA", "COgHEAA", "CJoIEAA", "CMwIEAA", "CP4IEAA", "CLAJEAA", "COIJEAA", "CJQKEAA", "CMYKEAA", "CPgKEAA", "CKoLEAA", "CNwLEAA", "CI4MEAA", "CMAMEAA", "CPIMEAA", "CKQNEAA", "CNYNEAA", "CIgOEAA", "CLoOEAA", "COwOEAA", "CJ4PEAA", "CNAPEAA", "CIIQEAA", "CLQQEAA", "COYQEAA", "CJgREAA", "CMoREAA", "CPwREAA", "CK4SEAA", "COASEAA", "CJITEAA", "CMQTEAA", "CPYTEAA", "CKgUEAA", "CNoUEAA", "CIwVEAA", "CL4VEAA", "CPAVEAA", "CKIWEAA", "CNQWEAA", "CIYXEAA", "CLgXEAA", "COoXEAA", "CJwYEAA", "CM4YEAA", "CIAZEAA", "CLIZEAA"]
"pageTokens" : ["CDIQAQ", "CDIQAA", "CGQQAA", "CJYBEAA", "CMgBEAA", "CPoBEAA", "CKwCEAA", "CN4CEAA", "CJADEAA", "CMIDEAA", "CPQDEAA", "CKYEEAA", "CNgEEAA", "CIoFEAA", "CLwFEAA", "CO4FEAA", "CKAGEAA", "CNIGEAA", "CIQHEAA", "CLYHEAA", "COgHEAA", "CJoIEAA", "CMwIEAA", "CP4IEAA", "CLAJEAA", "COIJEAA", "CJQKEAA", "CMYKEAA", "CPgKEAA", "CKoLEAA", "CNwLEAA", "CI4MEAA", "CMAMEAA", "CPIMEAA", "CKQNEAA", "CNYNEAA", "CIgOEAA", "CLoOEAA", "COwOEAA", "CJ4PEAA", "CNAPEAA", "CIIQEAA", "CLQQEAA", "COYQEAA", "CJgREAA", "CMoREAA", "CPwREAA", "CK4SEAA", "COASEAA", "CJITEAA", "CMQTEAA", "CPYTEAA", "CKgUEAA", "CNoUEAA", "CIwVEAA", "CL4VEAA", "CPAVEAA", "CKIWEAA", "CNQWEAA", "CIYXEAA", "CLgXEAA", "COoXEAA", "CJwYEAA", "CM4YEAA", "CIAZEAA", "CLIZEAA", "COQZEAA", "CJYaEAA", "CMgaEAA", "CPoaEAA", "CKwbEAA", "CN4bEAA", "CJAcEAA", "CMIcEAA", "CPQcEAA", "CKYdEAA", "CNgdEAA", "CIoeEAA", "CLweEAA", "CO4eEAA", "CKAfEAA", "CNIfEAA", "CIQgEAA", "CLYgEAA", "COggEAA", "CJohEAA", "CMwhEAA", "CP4hEAA", "CLAiEAA", "COIiEAA", "CJQjEAA", "CMYjEAA", "CPgjEAA", "CKokEAA", "CNwkEAA", "CI4lEAA", "CMAlEAA", "CPIlEAA", "CKQmEAA", "CNYmEAA"]
}

0 comments on commit fc94a10

Please sign in to comment.