View difference between Paste ID: pDuRHynr and QEB56myq
SHOW: | | - or go back to the newest paste.
1
// ==UserScript==
2
// @name Google Drive Video Player for CyTube
3
// @namespace gdcytube
4
// @description Play Google Drive videos on CyTube
5
// @include https://cytu.be/r/*
6-
// @include http://lolcow.tv/r/*
6+
7-
// @include https://lolcow.tv/r/*
7+
8
// @grant GM_xmlhttpRequest
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
}