SHOW:
|
|
- or go back to the newest paste.
| 1 | // ==UserScript== | |
| 2 | - | // @name Google Drive Video Player for cytube.xyz |
| 2 | + | // @name Google Drive Video Player for CyTube |
| 3 | // @namespace gdcytube | |
| 4 | - | // @description Play Google Drive videos on cytube.xyz |
| 4 | + | // @description Play Google Drive videos on CyTube |
| 5 | - | // @match http://cytu.be/r/* |
| 5 | + | // @include https://cytu.be/r/* |
| 6 | - | // @match https://cytu.be/r/* |
| 6 | + | // @include https://www.cytu.be/r/* |
| 7 | - | // @match http://cytube.xyz/r/* |
| 7 | + | |
| 8 | - | // @match https://cytube.xyz/r/* |
| 8 | + | |
| 9 | // @grant GM.xmlHttpRequest | |
| 10 | // @connect docs.google.com | |
| 11 | // @run-at document-end | |
| 12 | // @version 1.7.0 | |
| 13 | // ==/UserScript== | |
| 14 | ||
| 15 | try {
| |
| 16 | function debug(message) {
| |
| 17 | try {
| |
| 18 | unsafeWindow.console.log('[Drive]', message);
| |
| 19 | } catch (error) {
| |
| 20 | unsafeWindow.console.error(error); | |
| 21 | } | |
| 22 | } | |
| 23 | ||
| 24 | function httpRequest(opts) {
| |
| 25 | if (typeof GM_xmlhttpRequest === 'undefined') {
| |
| 26 | // Assume GM4.0 | |
| 27 | debug('Using GM4.0 GM.xmlHttpRequest');
| |
| 28 | GM.xmlHttpRequest(opts); | |
| 29 | } else {
| |
| 30 | debug('Using old-style GM_xmlhttpRequest');
| |
| 31 | GM_xmlhttpRequest(opts); | |
| 32 | } | |
| 33 | } | |
| 34 | ||
| 35 | var ITAG_QMAP = {
| |
| 36 | 37: 1080, | |
| 37 | 46: 1080, | |
| 38 | 22: 720, | |
| 39 | 45: 720, | |
| 40 | 59: 480, | |
| 41 | 44: 480, | |
| 42 | 35: 480, | |
| 43 | 18: 360, | |
| 44 | 43: 360, | |
| 45 | 34: 360 | |
| 46 | }; | |
| 47 | ||
| 48 | var ITAG_CMAP = {
| |
| 49 | 43: 'video/webm', | |
| 50 | 44: 'video/webm', | |
| 51 | 45: 'video/webm', | |
| 52 | 46: 'video/webm', | |
| 53 | 18: 'video/mp4', | |
| 54 | 22: 'video/mp4', | |
| 55 | 37: 'video/mp4', | |
| 56 | 59: 'video/mp4', | |
| 57 | 35: 'video/flv', | |
| 58 | 34: 'video/flv' | |
| 59 | }; | |
| 60 | ||
| 61 | function getVideoInfo(id, cb) {
| |
| 62 | var url = 'https://docs.google.com/get_video_info?authuser=' | |
| 63 | + '&docid=' + id | |
| 64 | + '&sle=true' | |
| 65 | + '&hl=en'; | |
| 66 | debug('Fetching ' + url);
| |
| 67 | ||
| 68 | httpRequest({
| |
| 69 | method: 'GET', | |
| 70 | url: url, | |
| 71 | onload: function (res) {
| |
| 72 | try {
| |
| 73 | debug('Got response ' + res.responseText);
| |
| 74 | ||
| 75 | if (res.status !== 200) {
| |
| 76 | debug('Response status not 200: ' + res.status);
| |
| 77 | return cb( | |
| 78 | 'Google Drive request failed: HTTP ' + res.status | |
| 79 | ); | |
| 80 | } | |
| 81 | ||
| 82 | var data = {};
| |
| 83 | var error; | |
| 84 | // Google Santa sometimes eats login cookies and gets mad if there aren't any. | |
| 85 | if(/accounts\.google\.com\/ServiceLogin/.test(res.responseText)){
| |
| 86 | error = 'Google Docs request failed: ' + | |
| 87 | 'This video requires you be logged into a Google account. ' + | |
| 88 | 'Open your Gmail in another tab and then refresh video.'; | |
| 89 | return cb(error); | |
| 90 | } | |
| 91 | ||
| 92 | res.responseText.split('&').forEach(function (kv) {
| |
| 93 | var pair = kv.split('=');
| |
| 94 | data[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]); | |
| 95 | }); | |
| 96 | ||
| 97 | if (data.status === 'fail') {
| |
| 98 | error = 'Google Drive request failed: ' + | |
| 99 | unescape(data.reason).replace(/\+/g, ' '); | |
| 100 | return cb(error); | |
| 101 | } | |
| 102 | ||
| 103 | if (!data.fmt_stream_map) {
| |
| 104 | error = ( | |
| 105 | 'Google has removed the video streams associated' + | |
| 106 | ' with this item. It can no longer be played.' | |
| 107 | ); | |
| 108 | ||
| 109 | return cb(error); | |
| 110 | } | |
| 111 | ||
| 112 | data.links = {};
| |
| 113 | data.fmt_stream_map.split(',').forEach(function (item) {
| |
| 114 | var pair = item.split('|');
| |
| 115 | data.links[pair[0]] = pair[1]; | |
| 116 | }); | |
| 117 | data.videoMap = mapLinks(data.links); | |
| 118 | ||
| 119 | cb(null, data); | |
| 120 | } catch (error) {
| |
| 121 | unsafeWindow.console.error(error); | |
| 122 | } | |
| 123 | }, | |
| 124 | ||
| 125 | onerror: function () {
| |
| 126 | var error = 'Google Drive request failed: ' + | |
| 127 | 'metadata lookup HTTP request failed'; | |
| 128 | error.reason = 'HTTP_ONERROR'; | |
| 129 | return cb(error); | |
| 130 | } | |
| 131 | }); | |
| 132 | } | |
| 133 | ||
| 134 | function mapLinks(links) {
| |
| 135 | var videos = {
| |
| 136 | 1080: [], | |
| 137 | 720: [], | |
| 138 | 480: [], | |
| 139 | 360: [] | |
| 140 | }; | |
| 141 | ||
| 142 | Object.keys(links).forEach(function (itag) {
| |
| 143 | itag = parseInt(itag, 10); | |
| 144 | if (!ITAG_QMAP.hasOwnProperty(itag)) {
| |
| 145 | return; | |
| 146 | } | |
| 147 | ||
| 148 | videos[ITAG_QMAP[itag]].push({
| |
| 149 | itag: itag, | |
| 150 | contentType: ITAG_CMAP[itag], | |
| 151 | link: links[itag] | |
| 152 | }); | |
| 153 | }); | |
| 154 | ||
| 155 | return videos; | |
| 156 | } | |
| 157 | ||
| 158 | /* | |
| 159 | * Greasemonkey 2.0 has this wonderful sandbox that attempts | |
| 160 | * to prevent script developers from shooting themselves in | |
| 161 | * the foot by removing the trigger from the gun, i.e. it's | |
| 162 | * impossible to cross the boundary between the browser JS VM | |
| 163 | * and the privileged sandbox that can run GM_xmlhttpRequest(). | |
| 164 | * | |
| 165 | * So in this case, we have to resort to polling a special | |
| 166 | * variable to see if getGoogleDriveMetadata needs to be called | |
| 167 | * and deliver the result into another special variable that is | |
| 168 | * being polled on the browser side. | |
| 169 | */ | |
| 170 | ||
| 171 | /* | |
| 172 | * Browser side function -- sets gdUserscript.pollID to the | |
| 173 | * ID of the Drive video to be queried and polls | |
| 174 | * gdUserscript.pollResult for the result. | |
| 175 | */ | |
| 176 | function getGoogleDriveMetadata_GM(id, callback) {
| |
| 177 | debug('Setting GD poll ID to ' + id);
| |
| 178 | unsafeWindow.gdUserscript.pollID = id; | |
| 179 | var tries = 0; | |
| 180 | var i = setInterval(function () {
| |
| 181 | if (unsafeWindow.gdUserscript.pollResult) {
| |
| 182 | debug('Got result');
| |
| 183 | clearInterval(i); | |
| 184 | var result = unsafeWindow.gdUserscript.pollResult; | |
| 185 | unsafeWindow.gdUserscript.pollResult = null; | |
| 186 | callback(result.error, result.result); | |
| 187 | } else if (++tries > 100) {
| |
| 188 | // Took longer than 10 seconds, give up | |
| 189 | clearInterval(i); | |
| 190 | } | |
| 191 | }, 100); | |
| 192 | } | |
| 193 | ||
| 194 | /* | |
| 195 | * Sandbox side function -- polls gdUserscript.pollID for | |
| 196 | * the ID of a Drive video to be queried, looks up the | |
| 197 | * metadata, and stores it in gdUserscript.pollResult | |
| 198 | */ | |
| 199 | function setupGDPoll() {
| |
| 200 | unsafeWindow.gdUserscript = cloneInto({}, unsafeWindow);
| |
| 201 | var pollInterval = setInterval(function () {
| |
| 202 | if (unsafeWindow.gdUserscript.pollID) {
| |
| 203 | var id = unsafeWindow.gdUserscript.pollID; | |
| 204 | unsafeWindow.gdUserscript.pollID = null; | |
| 205 | debug('Polled and got ' + id);
| |
| 206 | getVideoInfo(id, function (error, data) {
| |
| 207 | unsafeWindow.gdUserscript.pollResult = cloneInto({
| |
| 208 | error: error, | |
| 209 | result: data | |
| 210 | }, unsafeWindow); | |
| 211 | }); | |
| 212 | } | |
| 213 | }, 1000); | |
| 214 | } | |
| 215 | ||
| 216 | var TM_COMPATIBLES = [ | |
| 217 | 'Tampermonkey', | |
| 218 | 'Violentmonkey' // https://github.com/calzoneman/sync/issues/713 | |
| 219 | ]; | |
| 220 | ||
| 221 | function isTampermonkeyCompatible() {
| |
| 222 | try {
| |
| 223 | return TM_COMPATIBLES.indexOf(GM_info.scriptHandler) >= 0; | |
| 224 | } catch (error) {
| |
| 225 | return false; | |
| 226 | } | |
| 227 | } | |
| 228 | ||
| 229 | if (isTampermonkeyCompatible()) {
| |
| 230 | unsafeWindow.getGoogleDriveMetadata = getVideoInfo; | |
| 231 | } else {
| |
| 232 | debug('Using non-TM polling workaround');
| |
| 233 | unsafeWindow.getGoogleDriveMetadata = exportFunction( | |
| 234 | getGoogleDriveMetadata_GM, unsafeWindow); | |
| 235 | setupGDPoll(); | |
| 236 | } | |
| 237 | ||
| 238 | unsafeWindow.console.log('Initialized userscript Google Drive player');
| |
| 239 | unsafeWindow.hasDriveUserscript = true; | |
| 240 | // Checked against GS_VERSION from data.js | |
| 241 | unsafeWindow.driveUserscriptVersion = '1.7'; | |
| 242 | } catch (error) {
| |
| 243 | unsafeWindow.console.error(error); | |
| 244 | } | |
| 245 | ||
| 246 |