init commit
This commit is contained in:
parent
ae639426d0
commit
7a491681cc
257 changed files with 95524 additions and 80 deletions
243
gdrive-userscript/cytube-google-drive.user.js
Normal file
243
gdrive-userscript/cytube-google-drive.user.js
Normal file
|
|
@ -0,0 +1,243 @@
|
|||
// ==UserScript==
|
||||
// @name Google Drive Video Player for {SITENAME}
|
||||
// @namespace gdcytube
|
||||
// @description Play Google Drive videos on {SITENAME}
|
||||
// {INCLUDE_BLOCK}
|
||||
// @grant unsafeWindow
|
||||
// @grant GM_xmlhttpRequest
|
||||
// @grant GM.xmlHttpRequest
|
||||
// @connect docs.google.com
|
||||
// @run-at document-end
|
||||
// @version 1.7.0
|
||||
// ==/UserScript==
|
||||
|
||||
try {
|
||||
function debug(message) {
|
||||
try {
|
||||
unsafeWindow.console.log('[Drive]', message);
|
||||
} catch (error) {
|
||||
unsafeWindow.console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
function httpRequest(opts) {
|
||||
if (typeof GM_xmlhttpRequest === 'undefined') {
|
||||
// Assume GM4.0
|
||||
debug('Using GM4.0 GM.xmlHttpRequest');
|
||||
GM.xmlHttpRequest(opts);
|
||||
} else {
|
||||
debug('Using old-style GM_xmlhttpRequest');
|
||||
GM_xmlhttpRequest(opts);
|
||||
}
|
||||
}
|
||||
|
||||
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/get_video_info?authuser='
|
||||
+ '&docid=' + id
|
||||
+ '&sle=true'
|
||||
+ '&hl=en';
|
||||
debug('Fetching ' + url);
|
||||
|
||||
httpRequest({
|
||||
method: 'GET',
|
||||
url: url,
|
||||
onload: function (res) {
|
||||
try {
|
||||
debug('Got response ' + res.responseText);
|
||||
|
||||
if (res.status !== 200) {
|
||||
debug('Response status not 200: ' + res.status);
|
||||
return cb(
|
||||
'Google Drive request failed: HTTP ' + res.status
|
||||
);
|
||||
}
|
||||
|
||||
var data = {};
|
||||
var error;
|
||||
// Google Santa sometimes eats login cookies and gets mad if there aren't any.
|
||||
if(/accounts\.google\.com\/ServiceLogin/.test(res.responseText)){
|
||||
error = 'Google Docs request failed: ' +
|
||||
'This video requires you be logged into a Google account. ' +
|
||||
'Open your Gmail in another tab and then refresh video.';
|
||||
return cb(error);
|
||||
}
|
||||
|
||||
res.responseText.split('&').forEach(function (kv) {
|
||||
var pair = kv.split('=');
|
||||
data[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]);
|
||||
});
|
||||
|
||||
if (data.status === 'fail') {
|
||||
error = 'Google Drive request failed: ' +
|
||||
unescape(data.reason).replace(/\+/g, ' ');
|
||||
return cb(error);
|
||||
}
|
||||
|
||||
if (!data.fmt_stream_map) {
|
||||
error = (
|
||||
'Google has removed the video streams associated' +
|
||||
' with this item. It can no longer be played.'
|
||||
);
|
||||
|
||||
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);
|
||||
}
|
||||
},
|
||||
|
||||
onerror: function () {
|
||||
var error = 'Google Drive 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;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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);
|
||||
});
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
var TM_COMPATIBLES = [
|
||||
'Tampermonkey',
|
||||
'Violentmonkey' // https://github.com/calzoneman/sync/issues/713
|
||||
];
|
||||
|
||||
function isTampermonkeyCompatible() {
|
||||
try {
|
||||
return TM_COMPATIBLES.indexOf(GM_info.scriptHandler) >= 0;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (isTampermonkeyCompatible()) {
|
||||
unsafeWindow.getGoogleDriveMetadata = getVideoInfo;
|
||||
} else {
|
||||
debug('Using non-TM polling workaround');
|
||||
unsafeWindow.getGoogleDriveMetadata = exportFunction(
|
||||
getGoogleDriveMetadata_GM, unsafeWindow);
|
||||
setupGDPoll();
|
||||
}
|
||||
|
||||
unsafeWindow.console.log('Initialized userscript Google Drive player');
|
||||
unsafeWindow.hasDriveUserscript = true;
|
||||
// Checked against GS_VERSION from data.js
|
||||
unsafeWindow.driveUserscriptVersion = '1.7';
|
||||
} catch (error) {
|
||||
unsafeWindow.console.error(error);
|
||||
}
|
||||
37
gdrive-userscript/generate-userscript.js
Normal file
37
gdrive-userscript/generate-userscript.js
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
|
||||
var sitename = process.argv[2];
|
||||
var includes = process.argv.slice(3).map(function (include) {
|
||||
return '// @include ' + include;
|
||||
}).join('\n');
|
||||
|
||||
var lines = String(fs.readFileSync(
|
||||
path.resolve(__dirname, 'cytube-google-drive.user.js'))).split('\n');
|
||||
|
||||
var userscriptOutput = '';
|
||||
var metaOutput = '';
|
||||
lines.forEach(function (line) {
|
||||
if (line.match(/\{INCLUDE_BLOCK\}/)) {
|
||||
userscriptOutput += includes + '\n';
|
||||
} else if (line.match(/\{SITENAME\}/)) {
|
||||
line = line.replace(/\{SITENAME\}/, sitename) + '\n';
|
||||
userscriptOutput += line;
|
||||
metaOutput += line;
|
||||
} else {
|
||||
if (line.match(/==\/?UserScript|@name|@version/)) {
|
||||
metaOutput += line + '\n';
|
||||
}
|
||||
|
||||
userscriptOutput += line + '\n';
|
||||
}
|
||||
});
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(__dirname, '..', 'www', 'js', 'cytube-google-drive.user.js'),
|
||||
userscriptOutput
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(__dirname, '..', 'www', 'js', 'cytube-google-drive.meta.js'),
|
||||
metaOutput
|
||||
);
|
||||
Loading…
Add table
Add a link
Reference in a new issue