Google Drive Video Player for SyncVideo

Play Google Drive videos on SyncVideo

当前为 2018-04-17 提交的版本,查看 最新版本

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