IITC plugin: Draw Tools Sync

Sync draw tools data between clients via Google Drive API.

当前为 2020-01-22 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @author Odrick
  3. // @name IITC plugin: Draw Tools Sync
  4. // @category Draw
  5. // @version 0.0.2
  6. // @description Sync draw tools data between clients via Google Drive API.
  7. // @id draw-tools-sync
  8. // @match https://intel.ingress.com/*
  9. // @grant none
  10. // @namespace https://greasyfork.org/users/410740
  11. // ==/UserScript==
  12.  
  13. function wrapper(plugin_info) {
  14.  
  15. if(typeof window.plugin !== 'function') window.plugin = function() {};
  16.  
  17. plugin_info.buildName = '0.0.1';
  18. plugin_info.dateTimeVersion = '2020-01-20-162932';
  19. plugin_info.pluginId = 'draw-tools-sync';
  20.  
  21. window.plugin.drawToolsSync = function() {};
  22.  
  23.  
  24. ////////////// Default storage - Google Drive ////////////////
  25. function GoogleDriveStorage(clientId, scope) {
  26. this.clientId = clientId;
  27. this.scope = scope;
  28.  
  29. this.authorized = false;
  30. }
  31.  
  32. GoogleDriveStorage.prototype.init = function(callback) {
  33. $.getScript('https://apis.google.com/js/api.js').done(function () {
  34. gapi.load('client:auth2', callback);
  35. });
  36. };
  37.  
  38. GoogleDriveStorage.prototype.authorize = function(redirect, callback) {
  39. this.authorized = false;
  40.  
  41. var self = this;
  42.  
  43. function handleAuthResult(authResult) {
  44. if(authResult && !authResult.error) {
  45. self.authorized = true;
  46. }
  47. else {
  48. self.authorized = false;
  49. var error = (authResult && authResult.error) ? authResult.error : 'not authorized';
  50. console.log(error);
  51. if(error === "idpiframe_initialization_failed") {
  52. console.log('You need enable 3rd-party cookies in your browser or allow [*.]google.com');
  53. }
  54. }
  55.  
  56. if(callback) callback(self.authorized);
  57. }
  58.  
  59. gapi.auth2.init({
  60. client_id: this.clientId,
  61. scope: this.scope,
  62. ux_mode: 'redirect',
  63. redirect_uri: 'https://intel.ingress.com'
  64. }).then(function() {
  65. var isSignedIn = gapi.auth2.getAuthInstance().isSignedIn.get();
  66.  
  67. if(isSignedIn) {
  68. self.authorized = true;
  69. if(callback) callback(self.authorized);
  70. }
  71. else {
  72. self.authorized = false;
  73.  
  74. if(redirect) {
  75. gapi.auth2.getAuthInstance().signIn().then(handleAuthResult);
  76. }
  77. else {
  78. if(callback) callback(self.authorized);
  79. }
  80. }
  81. }, handleAuthResult);
  82. };
  83.  
  84. GoogleDriveStorage.prototype.signOut = function(callback) {
  85. var auth2 = gapi.auth2.getAuthInstance();
  86. var self = this;
  87. auth2.signOut().then(function () {
  88. auth2.disconnect();
  89. self.authorized = false;
  90. if(callback) callback();
  91. });
  92. };
  93.  
  94. GoogleDriveStorage.prototype.getFilesList = function(callback) {
  95. gapi.client.load('drive', 'v3').then(function() {
  96. gapi.client.drive.files.list({
  97. spaces: 'appDataFolder',
  98. fields: 'files(id, name)',
  99. orderBy: 'modifiedTime desc'
  100. }).then(function(resp) {
  101. if(callback) callback(resp.result.files);
  102. });
  103. });
  104. };
  105.  
  106. GoogleDriveStorage.prototype.findFile = function(name, callback) {
  107. this.getFilesList(function(list) {
  108. var found = null;
  109.  
  110. for(var i=0; i<list.length; i++) {
  111. var file = list[i];
  112. if(file.name.toLowerCase() === name.toLowerCase()) found = file;
  113. }
  114.  
  115. if(callback) callback(found);
  116. });
  117. };
  118.  
  119. GoogleDriveStorage.prototype.createFile = function(name, callback) {
  120. this.findFile(name, function(file) {
  121. if(file) {
  122. if(callback) callback(file);
  123. return;
  124. }
  125.  
  126. gapi.client.load('drive', 'v3').then(function() {
  127. gapi.client.drive.files.create({
  128. resource: {
  129. name: name,
  130. mimeType: 'text/plain',
  131. parents: ['appDataFolder']
  132. },
  133. fields: 'id,name'
  134. }).then(function(resp) {
  135. if(callback) callback(resp.result);
  136. });
  137. });
  138. });
  139. };
  140.  
  141. GoogleDriveStorage.prototype.readFile = function(id, callback) {
  142. gapi.client.load('drive', 'v3').then(function() {
  143. gapi.client.drive.files.get({fileId: id, alt: 'media'}).then(function(resp) {
  144. if(callback) callback(resp);
  145. });
  146. });
  147. };
  148.  
  149. GoogleDriveStorage.prototype.saveFileById = function(id, content, callback) {
  150. gapi.client.load('drive', 'v3').then(function() {
  151. gapi.client.request({
  152. path: '/upload/drive/v3/files/' + id,
  153. method: 'PATCH',
  154. params: {uploadType: 'media'},
  155. body: content
  156. }).then(function(resp) {
  157. if(callback) callback(resp);
  158. });
  159. });
  160. };
  161.  
  162. GoogleDriveStorage.prototype.saveFileByName = function(name, content, callback) {
  163. var self = this;
  164.  
  165. self.findFile(name, function(file) {
  166. if(file) {
  167. self.saveFileById(file.id, content, callback);
  168. }
  169. else {
  170. self.createFile(name, function(file) {
  171. self.saveFileById(file.id, content, callback);
  172. });
  173. }
  174. });
  175. };
  176.  
  177. GoogleDriveStorage.prototype.deleteFile = function(id, callback) {
  178. gapi.client.load('drive', 'v3').then(function() {
  179. gapi.client.drive.files.delete({fileId: id}).then(function() {
  180. if(callback) callback();
  181. });
  182. });
  183. };
  184.  
  185. //////////////////////////////////////////////////////////////
  186.  
  187. var CLIENT_ID = '850036042257-lps5dks8a2274cmdtab87bpksh2mr3m6.apps.googleusercontent.com';
  188. var SCOPE = 'https://www.googleapis.com/auth/drive.appfolder';
  189.  
  190. var dataStorage = null;
  191. var ready = false;
  192. var dataFileExt = 'dtd';
  193.  
  194. var options = {};
  195.  
  196. function fixDataFileName(name) {
  197. var parts = name.split('.');
  198. if(parts.length < 2 || parts.pop() !== dataFileExt) {
  199. name = name + '.' + dataFileExt;
  200. }
  201. return name;
  202. }
  203.  
  204. function setupCSS() {
  205. var css = '#drawToolsSyncBox{display:none;position:absolute!important;z-index:5001;top:50px;left:60px;width:200px;height:250px;overflow:hidden;background:rgba(8,48,78,.9);border:1px solid #20a8b1;color:#ffce00;padding:8px;font-size:13px;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}#drawToolsSyncBox #drawToolsSyncTopBar{height:15px!important}#drawToolsSyncBox #drawToolsSyncTopBar *{height:14px!important}#drawToolsSyncBox .handle{width:89%;text-align:center;color:#fff;line-height:6px;cursor:move;float:right}#drawToolsSyncBox #drawToolsSyncTopBar .btn{display:block;width:10%;cursor:pointer;color:#20a8b1;font-weight:700;text-align:center;line-height:13px;font-size:18px;border:1px solid #20a8b1;float:left}#drawToolsSyncBox #drawToolsSyncTopBar .btn:hover{color:#ffce00;text-decoration:none}#drawToolsSyncBox #drawToolsSyncTitle{font-size:12px;padding-top:5px}#drawToolsSyncBox #drawToolsSyncList{clear:both;margin-top:8px;height:200px;overflow-x:hidden;overflow-y:auto;border-bottom:1px solid #20a8b1}#drawToolsSyncBox #drawToolsSyncLock{display:none;position:absolute!important;left:0;top:26px;width:100%;height:100%;text-align:center;background-color:rgba(0,0,0,.5)}#drawToolsSyncBox #drawToolsSyncLock svg{display:block;margin:auto;margin-top:80px}#drawToolsSyncBox #drawToolsSyncAuth{padding-top:24px}#drawToolsSyncBox #drawToolsSyncAuth a{display:block;color:#ffce00;border:1px solid #ffce00;padding:3px 0;margin:10px auto;width:80%;text-align:center;background:rgba(8,48,78,.9)}.drawToolsSyncItem{margin-top:2px;margin-bottom:2px;padding:4px;background:rgba(8,48,78,.75);cursor:pointer;color:#fff}.drawToolsSyncItem:hover{background:rgba(4,24,39,1)}#drawToolsSyncLoadPanel{padding-top:8px}#drawToolsSyncLoadPanel label{display:block;float:left;padding-top:2px;padding-left:5px}#drawToolsSyncLoadPanel input{display:block;float:left}#drawToolsSyncSaveName{width:100px;height:18px}#drawToolsSyncSavePanel{padding-top:6px;text-align:center}#drawToolsSyncSavePanel input{color:#fff}#drawToolsSyncSavePanel button{color:#ffce00;border:1px solid #ffce00;text-align:center;background:rgba(8,48,78,.9)}.drawToolsSyncDeleteButton{float:left;width:14px;height:14px;border:1px solid #f33;background:#000;color:#f33;text-align:center;font-weight:700;margin-right:8px}.drawToolsSyncDeleteButton:hover{background:#900}';
  206. $('<style>').prop('type', 'text/css').html(css).appendTo('head');
  207. //'<link rel="stylesheet" type="text/css" href="http://localhost/dt.css"/>').appendTo('head');
  208. }
  209.  
  210. function setupUI() {
  211. var content = '';
  212.  
  213. content += '<div id="drawToolsSyncBox">';
  214. content += ' <div id="drawToolsSyncTopBar">';
  215. content += ' <a id="drawToolsSyncMin" class="btn" onclick="window.plugin.drawToolsSync.hideBox();return false;" title="Minimize">-</a>';
  216. content += ' <div class="handle"><div id="drawToolsSyncTitle" class="ui-dialog-title ui-dialog-title-active">Status</div></div>';
  217. content += ' </div>';
  218. content += ' <div id="drawToolsSyncContent">';
  219. content += ' <div id="drawToolsSyncList"></div>';
  220. content += ' <div id="drawToolsSyncLoadPanel"><input type="checkbox" id="drawToolsSyncAutoClose" onchange="window.plugin.drawToolsSync.onAutoCloseChange()"/> <label for="drawToolsSyncAutoClose">Close after saving or loading</label> </div>';
  221. content += ' <div id="drawToolsSyncSavePanel">Name: <input type="text" id="drawToolsSyncSaveName"/> <button onclick="window.plugin.drawToolsSync.saveFile($(\'#drawToolsSyncSaveName\').val())">Save</button></div>';
  222. content += ' </div>';
  223. content += ' <div id="drawToolsSyncAuth"><a onclick="window.plugin.drawToolsSync.auth();return false;">Authorize</a></div>';
  224. content += ' <div id="drawToolsSyncLock"><svg width="50px" height="50px" viewBox="-103 -16 533 533.33333" xmlns="http://www.w3.org/2000/svg"><path fill="white" d="m293.167969 131.632812v-35.382812h5.222656c12.847656.035156 23.332031-10.269531 23.527344-23.113281v-49.988281c-.191407-12.851563-10.671875-23.164063-23.527344-23.148438h-280.449219c-12.855468-.015625-23.335937 10.296875-23.523437 23.148438v49.988281c.195312 12.84375 10.675781 23.148437 23.523437 23.113281h5.226563v35.382812c-.019531 16.757813 7.617187 32.605469 20.734375 43.03125l94.039062 75.183594-92.191406 71.511719c-13.496094 10.398437-21.386719 26.496094-21.332031 43.539063v38.851562h-6.476563c-12.847656-.035156-23.328125 10.269531-23.523437 23.113281v49.988281c.1875 12.851563 10.667969 23.164063 23.523437 23.148438h280.449219c12.855469.015625 23.335937-10.296875 23.527344-23.148438v-49.988281c-.195313-12.84375-10.679688-23.148437-23.527344-23.113281h-3.972656v-38.605469c-.078125-17.210937-8.140625-33.410156-21.824219-43.851562l-94.066406-71.542969 93.921875-75.089844c13.113281-10.425781 20.746093-26.277344 20.71875-43.027344zm-273.75-106.632812h277.5v46.25h-277.5zm277.5 450h-277.5v-46.25h15.34375c.71875 0 1.445312.203125 2.199219.203125.75 0 1.484374-.203125 2.203124-.203125h240.390626c.714843 0 1.449218.203125 2.199218.203125.753906 0 1.484375-.203125 2.199219-.203125h12.964844zm-27.5-109.855469v38.605469h-220v-38.851562c-.019531-9.3125 4.289062-18.105469 11.671875-23.785157l97.140625-75.351562 99.222656 75.425781c7.484375 5.703125 11.90625 14.550781 11.964844 23.957031zm-12.605469-210.007812-98.621094 78.859375-98.65625-78.859375c-7.179687-5.6875-11.367187-14.34375-11.367187-23.503907v-35.382812h220v35.382812c0 9.160157-4.179688 17.8125-11.355469 23.503907zm0 0"/></svg></div>';
  225. content += '</div>';
  226.  
  227. $('body').append(content);
  228. $('#drawToolsSyncBox').draggable({handle: '.handle', containment: 'window'});
  229.  
  230. if(window.useAndroidPanes()) {
  231. //Mobile mode
  232. android.addPane("plugin-draw-tools-sync-load", "DrawTools Load", "ic_action_star");
  233. android.addPane("plugin-draw-tools-sync-save", "DrawTools Save", "ic_action_save");
  234.  
  235. window.addHook('paneChanged', function(pane) {
  236. if(pane === 'plugin-draw-tools-sync-load') {
  237. showBox(0);
  238. }
  239.  
  240. if(pane === 'plugin-draw-tools-sync-save') {
  241. showBox(1);
  242. }
  243.  
  244. setTimeout(function() {
  245. window.show('map');
  246. }, 0);
  247. });
  248. }
  249. else {
  250. //Desktop mode
  251. $('#toolbox').append('<a onclick="window.plugin.drawToolsSync.showBox(0);return false;">DrawTools Load</a>');
  252. $('#toolbox').append('<a onclick="window.plugin.drawToolsSync.showBox(1);return false;">DrawTools Save</a>');
  253. }
  254. }
  255.  
  256. function refreshFilesList(mode) {
  257. showLock();
  258.  
  259. $("#drawToolsSyncList").html("");
  260.  
  261. window.plugin.drawToolsSync.getFilesList(function(list) {
  262. for(var i=0; i<list.length; i++) {
  263. var file = list[i];
  264. if(mode === 0) {
  265. $("#drawToolsSyncList").append("<div class='drawToolsSyncItem' onclick='window.plugin.drawToolsSync.loadFile(\"" + file.name + "\")'><b>" + file.name + "</b></div>");
  266. }
  267.  
  268. if(mode === 1) {
  269. $("#drawToolsSyncList").append("<div class='drawToolsSyncItem' onclick='window.plugin.drawToolsSync.saveFile(\"" + file.name + "\")'><div class='drawToolsSyncDeleteButton' onclick='window.plugin.drawToolsSync.deleteFile(\"" + file.name + "\"); event.cancelBubble=true;'>X</div><b>" + file.name + "</b></div>");
  270. }
  271. }
  272.  
  273. hideLock();
  274. });
  275. }
  276.  
  277. function showLock() {
  278. $("#drawToolsSyncLock").show();
  279. }
  280.  
  281. function hideLock() {
  282. $("#drawToolsSyncLock").hide();
  283. }
  284.  
  285. function showBox(mode) {
  286. if(!window.plugin.drawToolsSync.isReady()) return;
  287.  
  288. var title = "";
  289.  
  290. if(mode === 0) {
  291. title = "DrawTools Load";
  292. $("#drawToolsSyncLoadPanel").show();
  293. $("#drawToolsSyncSavePanel").hide();
  294. }
  295. else {
  296. title = "DrawTools Save";
  297. $("#drawToolsSyncLoadPanel").hide();
  298. $("#drawToolsSyncSavePanel").show();
  299. }
  300.  
  301. $("#drawToolsSyncBox").show();
  302. $("#drawToolsSyncTitle").html(title);
  303.  
  304. if(dataStorage.authorized) {
  305. $("#drawToolsSyncAuth").hide();
  306. $("#drawToolsSyncContent").show();
  307.  
  308. refreshFilesList(mode);
  309. }
  310. else {
  311. $("#drawToolsSyncAuth").show();
  312. $("#drawToolsSyncContent").hide();
  313. }
  314. }
  315.  
  316. function hideBox() {
  317. $("#drawToolsSyncBox").hide();
  318. }
  319.  
  320. function saveOptions() {
  321. localStorage['plugin-draw-tools-sync-options'] = JSON.stringify(options);
  322. }
  323.  
  324. function saveBoxPosition() {
  325. if($('#drawToolsSyncBox').css('display') === 'none') return;
  326.  
  327. options.boxPositionX = parseInt($('#drawToolsSyncBox').css('left'));
  328. options.boxPositionY = parseInt($('#drawToolsSyncBox').css('top'));
  329.  
  330. saveOptions();
  331. }
  332.  
  333. function setup() {
  334. if(!window.plugin.drawTools) return;
  335.  
  336. try {options = JSON.parse(localStorage['plugin-draw-tools-sync-options'])}
  337. catch(e) {}
  338.  
  339. setupCSS();
  340. setupUI();
  341.  
  342. $("#drawToolsSyncAutoClose").prop('checked', !!options.autoClose);
  343. $("#drawToolsSyncSaveName").val(options.lastSave || 'default');
  344.  
  345. if(options.boxPositionX !== undefined) $('#drawToolsSyncBox').css('left', options.boxPositionX + 'px');
  346. if(options.boxPositionY !== undefined) $('#drawToolsSyncBox').css('top', options.boxPositionY + 'px');
  347.  
  348. dataStorage = new GoogleDriveStorage(CLIENT_ID, SCOPE);
  349. window.plugin.drawToolsSync.dataStorage = dataStorage;
  350.  
  351. dataStorage.init(function() {
  352. dataStorage.authorize(false, function(authorized) {
  353. ready = true;
  354. });
  355. });
  356.  
  357. window.plugin.drawToolsSync.isReady = function() {
  358. return !!(window.plugin.drawTools && ready);
  359. };
  360.  
  361. window.plugin.drawToolsSync.auth = function() {
  362. dataStorage.authorize(true);
  363. };
  364.  
  365. window.plugin.drawToolsSync.getFilesList = function(callback) {
  366. dataStorage.getFilesList(function(res) {
  367. var list = [];
  368. for(var i=0; i<res.length; i++) {
  369. var file = res[i];
  370. var parts = file.name.split('.');
  371. if(parts.pop() === dataFileExt) {
  372. list.push({
  373. id: file.id,
  374. name: parts.join('.')
  375. });
  376. }
  377. }
  378. if(callback) callback(list);
  379. });
  380. };
  381.  
  382. window.plugin.drawToolsSync.saveFile = function(name, callback) {
  383. if(typeof name !== "string") return;
  384. name = name.trim();
  385.  
  386. if(name === '') {
  387. alert('Name cannot be empty');
  388. return;
  389. }
  390.  
  391. var data = localStorage['plugin-draw-tools-layer'];
  392. if(!data) {
  393. alert('Draw tools data is empty');
  394. return;
  395. }
  396.  
  397. showLock();
  398.  
  399. dataStorage.findFile(fixDataFileName(name), function(file) {
  400. if(file) {
  401. if(!confirm("Owerride " + name + " save?")) {
  402. hideLock();
  403. return;
  404. }
  405. }
  406.  
  407. $('#drawToolsSyncSaveName').val(name);
  408.  
  409. dataStorage.saveFileByName(fixDataFileName(name), data, function() {
  410. if(options.autoClose) hideBox();
  411. else refreshFilesList(1);
  412.  
  413. options.lastSave = name;
  414. saveOptions();
  415.  
  416. if(callback) callback();
  417. });
  418. });
  419. };
  420.  
  421. window.plugin.drawToolsSync.loadFile = function(name, callback) {
  422. showLock();
  423.  
  424. dataStorage.findFile(fixDataFileName(name), function(file) {
  425. if(!file) {
  426. alert("File " + name + " not found");
  427. hideLock();
  428. return;
  429. }
  430.  
  431. dataStorage.readFile(file.id, function(data) {
  432. if(data && data.result) {
  433. window.plugin.drawTools.drawnItems.clearLayers();
  434. window.plugin.drawTools.import(data.result);
  435. window.plugin.drawTools.save();
  436.  
  437. var bounds = window.plugin.drawTools.drawnItems.getBounds();
  438. window.map.setView({lat: (bounds._southWest.lat + bounds._northEast.lat)/2, lng: (bounds._southWest.lng + bounds._northEast.lng)/2}, window.map.getZoom());
  439.  
  440. if(options.autoClose) hideBox();
  441. }
  442. else {
  443. alert("Error while loading file " + name);
  444. }
  445.  
  446. hideLock();
  447.  
  448. if(callback) callback();
  449. });
  450. });
  451. };
  452.  
  453. window.plugin.drawToolsSync.deleteFile = function(name, callback) {
  454. if(!confirm("Realy delete " + name + " save?")) return;
  455.  
  456. showLock();
  457.  
  458. dataStorage.findFile(fixDataFileName(name), function(file) {
  459. if(!file) {
  460. alert("File " + name + " not found");
  461. return;
  462. }
  463.  
  464. dataStorage.deleteFile(file.id, function() {
  465. refreshFilesList(1);
  466. if(callback) callback();
  467. });
  468. });
  469. };
  470.  
  471. window.plugin.drawToolsSync.onAutoCloseChange = function() {
  472. options.autoClose = $("#drawToolsSyncAutoClose").prop('checked');
  473. saveOptions();
  474. };
  475.  
  476. window.plugin.drawToolsSync.showBox = showBox;
  477. window.plugin.drawToolsSync.hideBox = hideBox;
  478.  
  479. setInterval(saveBoxPosition, 1000);
  480. }
  481.  
  482.  
  483.  
  484. setup.priority = 'high';
  485.  
  486. setup.info = plugin_info; //add the script info data to the function as a property
  487. if(!window.bootPlugins) window.bootPlugins = [];
  488. window.bootPlugins.push(setup);
  489. // if IITC has already booted, immediately run the 'setup' function
  490. if(window.iitcLoaded && typeof setup === 'function') setup();
  491. } // wrapper end
  492. // inject code into site context
  493. var script = document.createElement('script');
  494. var info = {};
  495. if (typeof GM_info !== 'undefined' && GM_info && GM_info.script) info.script = { version: GM_info.script.version, name: GM_info.script.name, description: GM_info.script.description };
  496. script.appendChild(document.createTextNode('('+ wrapper +')('+JSON.stringify(info)+');'));
  497. (document.body || document.head || document.documentElement).appendChild(script);