2016-08-16 04:00:56 +00:00
|
|
|
// ==UserScript==
|
|
|
|
// @name Google Drive Video Player for {SITENAME}
|
|
|
|
// @namespace gdcytube
|
|
|
|
// @description Play Google Drive videos on {SITENAME}
|
|
|
|
// {INCLUDE_BLOCK}
|
|
|
|
// @grant unsafeWindow
|
|
|
|
// @grant GM_xmlhttpRequest
|
|
|
|
// @connect docs.google.com
|
|
|
|
// @run-at document-end
|
2016-08-20 17:59:20 +00:00
|
|
|
// @version 1.1.0
|
2016-08-16 04:00:56 +00:00
|
|
|
// ==/UserScript==
|
|
|
|
|
2016-08-20 17:59:20 +00:00
|
|
|
try {
|
2016-08-16 04:00:56 +00:00
|
|
|
function debug(message) {
|
|
|
|
if (!unsafeWindow.enableCyTubeGoogleDriveUserscriptDebug) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-08-20 17:59:20 +00:00
|
|
|
try {
|
|
|
|
unsafeWindow.console.log(message);
|
|
|
|
} catch (error) {
|
|
|
|
unsafeWindow.console.error(error);
|
|
|
|
}
|
2016-08-16 04:00:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var ITAG_QMAP = {
|
|
|
|
37: 1080,
|
|
|
|
46: 1080,
|
|
|
|
22: 720,
|
|
|
|
45: 720,
|
|
|
|
59: 480,
|
|
|
|
44: 480,
|
|
|
|
35: 480,
|
|
|
|
18: 360,
|
|
|
|
43: 360,
|
|
|
|
34: 360
|
|
|
|
};
|
|
|
|
|
|
|
|
var ITAG_CMAP = {
|
|
|
|
43: 'video/webm',
|
|
|
|
44: 'video/webm',
|
|
|
|
45: 'video/webm',
|
|
|
|
46: 'video/webm',
|
|
|
|
18: 'video/mp4',
|
|
|
|
22: 'video/mp4',
|
|
|
|
37: 'video/mp4',
|
|
|
|
59: 'video/mp4',
|
|
|
|
35: 'video/flv',
|
|
|
|
34: 'video/flv'
|
|
|
|
};
|
|
|
|
|
|
|
|
function getVideoInfo(id, cb) {
|
|
|
|
var url = 'https://docs.google.com/file/d/' + id + '/get_video_info';
|
|
|
|
debug('Fetching ' + url);
|
|
|
|
|
|
|
|
GM_xmlhttpRequest({
|
|
|
|
method: 'GET',
|
|
|
|
url: url,
|
|
|
|
onload: function (res) {
|
2016-08-20 17:59:20 +00:00
|
|
|
try {
|
|
|
|
debug('Got response ' + res.responseText);
|
|
|
|
var data = {};
|
|
|
|
var error;
|
|
|
|
res.responseText.split('&').forEach(function (kv) {
|
|
|
|
var pair = kv.split('=');
|
|
|
|
data[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]);
|
|
|
|
});
|
|
|
|
|
|
|
|
if (data.status === 'fail') {
|
|
|
|
error = new Error('Google Docs request failed: ' +
|
|
|
|
'metadata indicated status=fail');
|
|
|
|
error.response = res.responseText;
|
|
|
|
error.reason = 'RESPONSE_STATUS_FAIL';
|
|
|
|
return cb(error);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!data.fmt_stream_map) {
|
|
|
|
error = new Error('Google Docs request failed: ' +
|
|
|
|
'metadata lookup returned no valid links');
|
|
|
|
error.response = res.responseText;
|
|
|
|
error.reason = 'MISSING_LINKS';
|
|
|
|
return cb(error);
|
|
|
|
}
|
|
|
|
|
|
|
|
data.links = {};
|
|
|
|
data.fmt_stream_map.split(',').forEach(function (item) {
|
|
|
|
var pair = item.split('|');
|
|
|
|
data.links[pair[0]] = pair[1];
|
|
|
|
});
|
|
|
|
data.videoMap = mapLinks(data.links);
|
|
|
|
|
|
|
|
cb(null, data);
|
|
|
|
} catch (error) {
|
|
|
|
unsafeWindow.console.error(error);
|
2016-08-16 04:00:56 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
onerror: function () {
|
|
|
|
var error = new Error('Google Docs request failed: ' +
|
|
|
|
'metadata lookup HTTP request failed');
|
|
|
|
error.reason = 'HTTP_ONERROR';
|
|
|
|
return cb(error);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function mapLinks(links) {
|
|
|
|
var videos = {
|
|
|
|
1080: [],
|
|
|
|
720: [],
|
|
|
|
480: [],
|
|
|
|
360: []
|
|
|
|
};
|
|
|
|
|
|
|
|
Object.keys(links).forEach(function (itag) {
|
|
|
|
itag = parseInt(itag, 10);
|
|
|
|
if (!ITAG_QMAP.hasOwnProperty(itag)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
videos[ITAG_QMAP[itag]].push({
|
|
|
|
itag: itag,
|
|
|
|
contentType: ITAG_CMAP[itag],
|
|
|
|
link: links[itag]
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
return videos;
|
|
|
|
}
|
|
|
|
|
2016-08-20 17:59:20 +00:00
|
|
|
/*
|
|
|
|
* Greasemonkey 2.0 has this wonderful sandbox that attempts
|
|
|
|
* to prevent script developers from shooting themselves in
|
|
|
|
* the foot by removing the trigger from the gun, i.e. it's
|
|
|
|
* impossible to cross the boundary between the browser JS VM
|
|
|
|
* and the privileged sandbox that can run GM_xmlhttpRequest().
|
|
|
|
*
|
|
|
|
* So in this case, we have to resort to polling a special
|
|
|
|
* variable to see if getGoogleDriveMetadata needs to be called
|
|
|
|
* and deliver the result into another special variable that is
|
|
|
|
* being polled on the browser side.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Browser side function -- sets gdUserscript.pollID to the
|
|
|
|
* ID of the Drive video to be queried and polls
|
|
|
|
* gdUserscript.pollResult for the result.
|
|
|
|
*/
|
|
|
|
function getGoogleDriveMetadata_GM(id, callback) {
|
|
|
|
debug('Setting GD poll ID to ' + id);
|
|
|
|
unsafeWindow.gdUserscript.pollID = id;
|
|
|
|
var tries = 0;
|
|
|
|
var i = setInterval(function () {
|
|
|
|
if (unsafeWindow.gdUserscript.pollResult) {
|
|
|
|
debug('Got result');
|
|
|
|
clearInterval(i);
|
|
|
|
var result = unsafeWindow.gdUserscript.pollResult;
|
|
|
|
unsafeWindow.gdUserscript.pollResult = null;
|
|
|
|
callback(result.error, result.result);
|
|
|
|
} else if (++tries > 100) {
|
|
|
|
// Took longer than 10 seconds, give up
|
|
|
|
clearInterval(i);
|
|
|
|
}
|
|
|
|
}, 100);
|
2016-08-16 04:00:56 +00:00
|
|
|
}
|
|
|
|
|
2016-08-20 17:59:20 +00:00
|
|
|
/*
|
|
|
|
* Sandbox side function -- polls gdUserscript.pollID for
|
|
|
|
* the ID of a Drive video to be queried, looks up the
|
|
|
|
* metadata, and stores it in gdUserscript.pollResult
|
|
|
|
*/
|
|
|
|
function setupGDPoll() {
|
|
|
|
unsafeWindow.gdUserscript = cloneInto({}, unsafeWindow);
|
|
|
|
var pollInterval = setInterval(function () {
|
|
|
|
if (unsafeWindow.gdUserscript.pollID) {
|
|
|
|
var id = unsafeWindow.gdUserscript.pollID;
|
|
|
|
unsafeWindow.gdUserscript.pollID = null;
|
|
|
|
debug('Polled and got ' + id);
|
|
|
|
getVideoInfo(id, function (error, data) {
|
|
|
|
unsafeWindow.gdUserscript.pollResult = cloneInto({
|
|
|
|
error: error,
|
|
|
|
result: data
|
|
|
|
}, unsafeWindow);
|
|
|
|
});
|
2016-08-16 04:00:56 +00:00
|
|
|
}
|
2016-08-20 17:59:20 +00:00
|
|
|
}, 1000);
|
|
|
|
}
|
2016-08-16 04:00:56 +00:00
|
|
|
|
2016-08-20 17:59:20 +00:00
|
|
|
function isRunningTampermonkey() {
|
|
|
|
try {
|
|
|
|
return GM_info.scriptHandler === 'Tampermonkey';
|
|
|
|
} catch (error) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isRunningTampermonkey()) {
|
|
|
|
unsafeWindow.getGoogleDriveMetadata = getVideoInfo;
|
|
|
|
} else {
|
|
|
|
debug('Using non-TM polling workaround');
|
|
|
|
unsafeWindow.getGoogleDriveMetadata = exportFunction(
|
|
|
|
getGoogleDriveMetadata_GM, unsafeWindow);
|
|
|
|
setupGDPoll();
|
|
|
|
}
|
2016-08-16 04:00:56 +00:00
|
|
|
|
|
|
|
unsafeWindow.console.log('Initialized userscript Google Drive player');
|
2016-08-16 04:09:43 +00:00
|
|
|
unsafeWindow.hasDriveUserscript = true;
|
2016-08-20 17:59:20 +00:00
|
|
|
} catch (error) {
|
|
|
|
unsafeWindow.console.error(error);
|
|
|
|
}
|