WME Image Overlays

Makes it possible to add images as overlay on the Waze Map Editor

当前为 2017-05-01 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name WME Image Overlays
  3. // @author Tom 'Glodenox' Puttemans
  4. // @namespace http://www.tomputtemans.com/
  5. // @description Makes it possible to add images as overlay on the Waze Map Editor
  6. // @include /^https:\/\/(www|beta)\.waze\.com\/(?!user\/)(.{2,6}\/)?editor\/.*$/
  7. // @icon data:image/png;base64,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
  8. // @version 0.6
  9. // @grant none
  10. // ==/UserScript==
  11.  
  12. // Based on the OpenLayers.Layer.Image class
  13. OpenLayers.Layer.OverlayImage = OpenLayers.Class(OpenLayers.Layer, {
  14. isBaseLayer: false,
  15. url: null,
  16. extent: null,
  17. size: null,
  18. tile: null,
  19. rotation: null,
  20. initialize: function(name, url, extent, size, options) {
  21. options = options || {};
  22. this.url = url;
  23. this.extent = extent;
  24. this.maxExtent = extent;
  25. this.size = size;
  26. this.rotation = options.rotation || 0;
  27. OpenLayers.Layer.prototype.initialize.apply(this, [name, options]);
  28. },
  29. destroy: function() {
  30. if (this.tile) {
  31. this.removeTileMonitoringHooks(this.tile);
  32. this.tile.destroy();
  33. this.tile = null;
  34. }
  35. OpenLayers.Layer.prototype.destroy.apply(this, arguments);
  36. },
  37. setMap: function(map) {
  38. OpenLayers.Layer.prototype.setMap.apply(this, arguments);
  39. },
  40. moveTo:function(bounds, zoomChanged, dragging) {
  41. OpenLayers.Layer.prototype.moveTo.apply(this, arguments);
  42. var firstRendering = (this.tile == null);
  43. if (zoomChanged || firstRendering) {
  44. this.setTileSize();
  45. var ulPx = this.map.getLayerPxFromLonLat({
  46. lon: this.extent.left,
  47. lat: this.extent.top
  48. });
  49.  
  50. if (firstRendering) {
  51. this.tile = new OpenLayers.Tile.Image(this, ulPx, this.extent, null, this.tileSize);
  52. this.addTileMonitoringHooks(this.tile);
  53. } else {
  54. this.tile.size = this.tileSize.clone();
  55. this.tile.position = ulPx.clone();
  56. }
  57. this.tile.draw();
  58. if (firstRendering) {
  59. this.setRotation(this.rotation);
  60. }
  61. }
  62. },
  63. shift: function(x, y) {
  64. this.extent = this.extent.add(x, y);
  65. var ulPx = this.map.getLayerPxFromLonLat({
  66. lon: this.extent.left,
  67. lat: this.extent.top
  68. });
  69. this.tile.position = ulPx.clone();
  70. this.tile.draw();
  71. },
  72. scale: function(factor) {
  73. this.extent = this.extent.scale(factor);
  74. this.setTileSize();
  75. var ulPx = this.map.getLayerPxFromLonLat({
  76. lon: this.extent.left,
  77. lat: this.extent.top
  78. });
  79. this.tile.position = ulPx.clone();
  80. this.tile.size = this.tileSize.clone();
  81. this.tile.draw();
  82. },
  83. rotate: function(rotation) {
  84. this.setRotation(this.rotation + rotation);
  85. },
  86. setTileSize: function() {
  87. var tileWidth = this.extent.getWidth() / this.map.getResolution();
  88. var tileHeight = this.extent.getHeight() / this.map.getResolution();
  89. this.tileSize = new OpenLayers.Size(tileWidth, tileHeight);
  90. },
  91. addTileMonitoringHooks: function(tile) {
  92. tile.onLoadStart = function() {
  93. this.events.triggerEvent("loadstart");
  94. };
  95. tile.events.register("loadstart", this, tile.onLoadStart);
  96. tile.onLoadEnd = function() {
  97. this.events.triggerEvent("loadend");
  98. };
  99. tile.events.register("loadend", this, tile.onLoadEnd);
  100. tile.events.register("unload", this, tile.onLoadEnd);
  101. },
  102. removeTileMonitoringHooks: function(tile) {
  103. tile.unload();
  104. tile.events.un({
  105. "loadstart": tile.onLoadStart,
  106. "loadend": tile.onLoadEnd,
  107. "unload": tile.onLoadEnd,
  108. scope: this
  109. });
  110. },
  111. setUrl: function(newUrl) {
  112. this.url = newUrl;
  113. this.tile.draw();
  114. },
  115. getURL: function(bounds) {
  116. return this.url;
  117. },
  118. setRotation: function(rotation) {
  119. this.rotation = rotation;
  120. this.tile.imgDiv.style.transform = 'rotate(' + rotation + 'deg)';
  121. },
  122. CLASS_NAME: "OpenLayers.Layer.OverlayImage"
  123. });
  124.  
  125. function init(e) {
  126. if (e && e.user == null) {
  127. return;
  128. }
  129. if (document.getElementById('user-info') == null) {
  130. setTimeout(init, 500);
  131. log('user-info element not yet available, page still loading');
  132. return;
  133. }
  134. if (typeof Waze.loginManager === 'undefined') {
  135. setTimeout(init, 300);
  136. return;
  137. }
  138. if (!Waze.loginManager.hasUser()) {
  139. Waze.loginManager.events.register('login', null, init);
  140. Waze.loginManager.events.register('loginStatus', null, init);
  141. // Double check as event might have triggered already
  142. if (!Waze.loginManager.hasUser()) {
  143. return;
  144. }
  145. }
  146.  
  147. // Deal with events mode
  148. if (Waze.app.modeController) {
  149. Waze.app.modeController.model.bind('change:mode', function(model, modeId) {
  150. if (modeId == 0) {
  151. addTab(tab);
  152. }
  153. });
  154. }
  155.  
  156. var om_strings = {
  157. en: {
  158. tab_title: 'Image Overlays',
  159. add_image: 'Add image overlay',
  160. empty_list: 'No images added yet',
  161. name_missing: 'No name specified',
  162. hide_overlay: 'Hide overlay',
  163. opacity: 'Opacity',
  164. image_name: 'Name',
  165. image_edit: 'Edit image',
  166. image_remove: 'Remove image',
  167. import_image: 'Import image',
  168. import_image_description: 'You can now paste an image from your clipboard in the WME with Ctrl+V or select an image with the file input field below.',
  169. import_error: 'Could not import image, the image is probably too big to retrieve. If you used the clipboard, you may want to download the image and try the file input field above instead.',
  170. align_image: 'Align with map',
  171. align_image_description: "You can use the controls below to align the image overlay with the map. Use the 'Attach to map' button above to finish.",
  172. attach_image: 'Attach to map',
  173. cancel: 'Cancel'
  174. }
  175. };
  176. om_strings.en_GB = om_strings['en-US'] = om_strings.en;
  177. for (var i = 0; i < I18n.availableLocales.length; i++) {
  178. var locale = I18n.availableLocales[i];
  179. if (I18n.translations[locale]) {
  180. I18n.translations[locale].image_overlays = om_strings[locale];
  181. }
  182. }
  183.  
  184. var tab = addTab();
  185. var layer = null;
  186. var currentBlob = null;
  187. var emptyList = document.createElement('span');
  188. emptyList.style.fontStyle = 'italic';
  189. emptyList.appendChild(document.createTextNode(I18n.t('image_overlays.empty_list')));
  190. tab.appendChild(emptyList);
  191. var imagesList = document.createElement('div');
  192. imagesList.className = 'result-list';
  193. imagesList.style.marginBottom = '1em';
  194. tab.appendChild(imagesList);
  195. getIndexedDB(function(db) {
  196. db.transaction(['overlays'], 'readonly')
  197. .objectStore('overlays')
  198. .openCursor()
  199. .addEventListener('success', function(e) {
  200. var cursor = e.target.result;
  201. if (cursor) {
  202. addImageOverlay(cursor.value.name, cursor.key);
  203. cursor.continue();
  204. }
  205. });
  206. });
  207.  
  208. var panel = document.createElement('div');
  209. panel.className = 'hidden';
  210. panel.style.backgroundColor = '#f2f3f4';
  211. panel.style.padding = '15px';
  212.  
  213. var importError = document.createElement('p');
  214. importError.className = 'hidden text-danger';
  215. importError.textContent = I18n.t('image_overlays.import_error');
  216. var pasteListener = function(e) {
  217. var items = e.clipboardData.items;
  218. for (var i = 0; i < items.length; ++i) {
  219. if (items[i].kind == 'file' && items[i].type.indexOf('image/') !== -1) {
  220. var blob = items[i].getAsFile();
  221. if (blob) {
  222. importError.classList.add('hidden');
  223. displayAlignPage(blob);
  224. } else {
  225. importError.classList.remove('hidden');
  226. W.map.resize();
  227. }
  228. break;
  229. }
  230. }
  231. };
  232.  
  233. var cancelButton, importLabel, alignLabel, pinLabel;
  234. cancelButton = document.createElement('button');
  235. cancelButton.className = 'btn btn-default';
  236. cancelButton.style.position = 'absolute';
  237. cancelButton.style.right = '15px';
  238. cancelButton.style.fontSize = '14px';
  239. var cancelButtonIcon = document.createElement('i');
  240. cancelButtonIcon.className = 'fa fa-trash-o fa-fw';
  241. cancelButton.appendChild(cancelButtonIcon);
  242. cancelButton.appendChild(document.createTextNode(I18n.t('image_overlays.cancel')));
  243. cancelButton.addEventListener('click', function() {
  244. panel.classList.add('hidden');
  245. W.map.resize();
  246. removeLayer();
  247. });
  248. panel.appendChild(cancelButton);
  249. var breadcrumbs = document.createElement('div');
  250. breadcrumbs.className = 'text-center';
  251. breadcrumbs.style.marginBottom = '1.5em';
  252. var breadcrumbSeparator = document.createElement('i');
  253. breadcrumbSeparator.className = 'fa fa-fw fa-angle-right';
  254. importLabel = createBreadcrumb('download', I18n.t('image_overlays.import_image'), 'primary');
  255. breadcrumbs.appendChild(importLabel);
  256. breadcrumbs.appendChild(breadcrumbSeparator);
  257. alignLabel = createBreadcrumb('arrows-alt', I18n.t('image_overlays.align_image'), 'default');
  258. breadcrumbs.appendChild(alignLabel);
  259. breadcrumbs.appendChild(breadcrumbSeparator.cloneNode());
  260. pinLabel = createBreadcrumb('map-pin', I18n.t('image_overlays.attach_image'), 'default');
  261. breadcrumbs.appendChild(pinLabel);
  262. panel.appendChild(breadcrumbs);
  263. var description = document.createElement('p');
  264. description.className = 'text-center';
  265. panel.appendChild(description);
  266. var instructions = document.createElement('div');
  267. instructions.className = 'text-center';
  268. panel.appendChild(instructions);
  269. document.getElementById('map').insertBefore(panel, document.getElementById('map').firstChild);
  270.  
  271. var addImageOverlayButton = document.createElement('button');
  272. addImageOverlayButton.className = 'btn btn-primary';
  273. var addSpan = document.createElement('span');
  274. addSpan.className = 'fa fa-plus';
  275. addSpan.style.marginRight = '5px';
  276. addImageOverlayButton.appendChild(addSpan);
  277. addImageOverlayButton.appendChild(document.createTextNode(I18n.t('image_overlays.add_image')));
  278. addImageOverlayButton.addEventListener('click', displayImportPage);
  279. tab.appendChild(addImageOverlayButton);
  280. var hideOverlayButton = document.createElement('button');
  281. hideOverlayButton.className = 'btn btn-default hidden';
  282. hideOverlayButton.style.float = 'right';
  283. hideOverlayButton.textContent = I18n.t('image_overlays.hide_overlay');
  284. hideOverlayButton.addEventListener('click', removeLayer);
  285. tab.appendChild(hideOverlayButton);
  286.  
  287. var layerControls = document.createElement('div');
  288. layerControls.className = 'hidden clearfix';
  289. layerControls.style.marginTop = '8px';
  290. var opacityRange = document.createElement('input');
  291. opacityRange.type = 'range';
  292. opacityRange.min = 0;
  293. opacityRange.max = 50;
  294. opacityRange.value = 50;
  295. opacityRange.id = 'imageOverlaysOpacity';
  296. var opacityLabel = document.createElement('label');
  297. opacityLabel.textContent = I18n.t('image_overlays.opacity') + ': ';
  298. opacityLabel.htmlFor = opacityRange.id;
  299. layerControls.appendChild(opacityLabel);
  300. layerControls.appendChild(opacityRange);
  301. tab.appendChild(layerControls);
  302. var rangeListener = function() {
  303. if (layer) {
  304. layer.setOpacity(opacityRange.value / 50);
  305. }
  306. };
  307. opacityRange.addEventListener('input', rangeListener);
  308. opacityRange.addEventListener('change', rangeListener);
  309.  
  310. var versionBlock = document.createElement('p');
  311. versionBlock.style.fontSize = '0.9em';
  312. versionBlock.style.marginTop = '10px';
  313. var versionInfo = document.createElement('a');
  314. versionInfo.appendChild(document.createTextNode(GM_info.script.name + ' (v' + GM_info.script.version + ')'));
  315. versionInfo.href = 'https://www.waze.com/forum/viewforum.php?f=819';
  316. versionInfo.target = '_blank';
  317. versionBlock.appendChild(versionInfo);
  318. tab.appendChild(versionBlock);
  319.  
  320. function addImageOverlay(name, key) {
  321. emptyList.classList.add('hidden');
  322. var container = document.createElement('div');
  323. container.className = 'result session-available';
  324. var remove = document.createElement('button');
  325. remove.style.fontSize = '14px';
  326. remove.style.float = 'right';
  327. remove.style.fontWeight = 'normal';
  328. remove.style.marginTop = '-6px';
  329. remove.className = 'fa fa-trash-o';
  330. remove.addEventListener('click', function(e) {
  331. e.stopPropagation();
  332. getIndexedDB(function(db) {
  333. db.transaction(['overlays'], 'readwrite').objectStore('overlays').delete(key).addEventListener('success', function(e) {
  334. container.parentNode.removeChild(container);
  335. removeLayer();
  336. });
  337. });
  338. });
  339. container.appendChild(remove);
  340. var nameContainer = document.createElement('div');
  341. var rename = document.createElement('button');
  342. rename.style.fontSize = '14px';
  343. rename.style.float = 'right';
  344. rename.style.fontWeight = 'normal';
  345. rename.style.marginTop = '-6px';
  346. rename.style.marginLeft = '4px';
  347. rename.className = 'fa fa-pencil';
  348. rename.addEventListener('click', function(e) {
  349. e.stopPropagation();
  350. getIndexedDB(function(db) {
  351. var objectStore = db.transaction(['overlays'], 'readwrite').objectStore('overlays');
  352. objectStore.get(key).addEventListener('success', function(e) {
  353. var overlay = e.target.result;
  354. var response = prompt('Please enter a new name for this image overlay', overlay.name);
  355. if (response && response.length > 0) {
  356. overlay.name = response;
  357. objectStore.put(overlay, key).addEventListener('success', function() {
  358. nameContainer.textContent = overlay.name;
  359. nameContainer.style.fontStyle = '';
  360. });
  361. }
  362. });
  363. });
  364. });
  365. container.appendChild(rename);
  366. if (name && name.length > 0) {
  367. nameContainer.textContent = name;
  368. } else {
  369. nameContainer.style.fontStyle = 'italic';
  370. nameContainer.textContent = I18n.t('image_overlays.name_missing');
  371. }
  372. container.appendChild(nameContainer);
  373. container.addEventListener('click', function() {
  374. getIndexedDB(function(db) {
  375. db.transaction(['overlays'], 'readonly').objectStore('overlays').get(key).addEventListener('success', function(e) {
  376. var overlay = e.target.result;
  377. overlay.extent = new OL.Bounds(overlay.extent);
  378. displayImageOverlay(overlay);
  379. });
  380. });
  381. });
  382. imagesList.appendChild(container);
  383. }
  384.  
  385. function displayImportPage() {
  386. document.addEventListener('paste', pasteListener);
  387. importLabel.className = 'label label-primary';
  388. alignLabel.className = 'label label-default';
  389. pinLabel.style.cursor = 'default';
  390. pinLabel.removeEventListener('click', pinToMap);
  391. removeLayer();
  392. description.textContent = I18n.t('image_overlays.import_image_description');
  393. var addImageInput = document.createElement('input');
  394. addImageInput.type = 'file';
  395. addImageInput.accepts = 'image/*';
  396. addImageInput.className = 'center-block';
  397. addImageInput.addEventListener('change', function() {
  398. displayAlignPage(addImageInput.files[0]);
  399. });
  400. instructions.textContent = '';
  401. instructions.appendChild(addImageInput);
  402. instructions.appendChild(importError);
  403. panel.classList.remove('hidden');
  404. W.map.resize();
  405. }
  406.  
  407. function displayAlignPage(blob) {
  408. currentBlob = blob;
  409. document.removeEventListener('paste', pasteListener);
  410. importLabel.className = 'label label-success';
  411. alignLabel.className = 'label label-primary';
  412.  
  413. displayImageOverlay({
  414. 'blob': blob,
  415. 'extent': W.map.getExtent(),
  416. 'rotation': 0
  417. }, true);
  418.  
  419. pinLabel.style.cursor = 'pointer';
  420. pinLabel.addEventListener('click', pinToMap);
  421.  
  422. description.textContent = I18n.t('image_overlays.align_image_description');
  423. instructions.textContent = '';
  424. instructions.appendChild(createControlButton('rotate-left', function() {
  425. layer.rotate(-45);
  426. }, '45°'));
  427. instructions.appendChild(createControlButton('rotate-left', function() {
  428. layer.rotate(-0.5);
  429. }));
  430. instructions.appendChild(createControlButton('arrow-up', function() {
  431. layer.shift(0, 10 * W.map.getResolution());
  432. }));
  433. instructions.appendChild(createControlButton('rotate-right', function() {
  434. layer.rotate(0.5);
  435. }));
  436. instructions.appendChild(createControlButton('rotate-right', function() {
  437. layer.rotate(45);
  438. }, '45°'));
  439. instructions.appendChild(document.createElement('br'));
  440. instructions.appendChild(createControlButton('arrow-left', function() {
  441. layer.shift(-10 * W.map.getResolution(), 0);
  442. }));
  443. instructions.appendChild(createControlButton('crosshairs', function() {
  444. var layerCenter = layer.extent.getCenterLonLat();
  445. layer.shift(W.map.getCenter().lon - layerCenter.lon, W.map.getCenter().lat - layerCenter.lat);
  446. }));
  447. instructions.appendChild(createControlButton('arrow-right', function() {
  448. layer.shift(10 * W.map.getResolution(), 0);
  449. }));
  450. instructions.appendChild(document.createElement('br'));
  451. instructions.appendChild(createControlButton('minus', function() {
  452. layer.scale(1 - (0.05 * W.map.getResolution()));
  453. }));
  454. instructions.appendChild(createControlButton('arrow-down', function() {
  455. layer.shift(0, -10 * W.map.getResolution());
  456. }));
  457. instructions.appendChild(createControlButton('plus', function() {
  458. layer.scale(1 + (0.05 * W.map.getResolution()));
  459. }));
  460.  
  461. W.map.resize();
  462. }
  463.  
  464. function pinToMap() {
  465. getIndexedDB(function(db) {
  466. var obj = {
  467. 'blob': currentBlob,
  468. 'name': currentBlob.name,
  469. 'extent': layer.extent.toArray(),
  470. 'rotation': layer.rotation
  471. };
  472. var req = db.transaction(['overlays'], 'readwrite').objectStore('overlays').add(obj);
  473. req.addEventListener('success', function(e) {
  474. panel.className = 'hidden';
  475. W.map.resize();
  476. addImageOverlay(obj.name, e.target.result);
  477. });
  478. });
  479. }
  480.  
  481. function displayImageOverlay(overlay, rescale) {
  482. var url = window.URL.createObjectURL(overlay.blob);
  483. var img = document.createElement('img');
  484. img.addEventListener('load', function() {
  485. removeLayer();
  486. if (rescale) {
  487. var mapExtentAspectRatio = overlay.extent.getWidth() / overlay.extent.getHeight();
  488. var imageAspectRatio = img.naturalWidth / img.naturalHeight;
  489. if (mapExtentAspectRatio > imageAspectRatio) {
  490. var widthDiff = overlay.extent.getWidth() - (overlay.extent.getHeight() * imageAspectRatio);
  491. overlay.extent = new OL.Bounds([overlay.extent.left + widthDiff/2 , overlay.extent.bottom, overlay.extent.right - widthDiff/2, overlay.extent.top]);
  492. } else {
  493. var heightDiff = overlay.extent.getHeight() - (overlay.extent.getWidth() / imageAspectRatio);
  494. overlay.extent = new OL.Bounds([overlay.extent.left, overlay.extent.bottom + heightDiff/2, overlay.extent.right, overlay.extent.top - heightDiff/2]);
  495. }
  496. overlay.extent = overlay.extent.scale(0.8);
  497. }
  498. layer = new OL.Layer.OverlayImage('Image Overlay', url, overlay.extent, new OL.Size(img.naturalWidth, img.naturalHeight), { 'rotation': overlay.rotation });
  499. Waze.map.addLayer(layer);
  500. layer.setOpacity(opacityRange.value / 50);
  501. layerControls.classList.remove('hidden');
  502. hideOverlayButton.classList.remove('hidden');
  503. var aerialImageryIndex = Waze.map.getLayerIndex(Waze.map.getLayerByUniqueName('satellite_imagery'));
  504. Waze.map.setLayerIndex(layer, aerialImageryIndex+1);
  505. W.map.zoomToExtent(overlay.extent);
  506. console.log('layer', layer);
  507. });
  508. img.src = url;
  509. }
  510.  
  511. function removeLayer() {
  512. if (layer) {
  513. W.map.removeLayer(layer);
  514. layer = null;
  515. layerControls.classList.add('hidden');
  516. hideOverlayButton.classList.add('hidden');
  517. }
  518. }
  519. }
  520.  
  521. // Create a tab and possibly receive a previous tab to restore (usually in case of a mode change)
  522. function addTab(recoveredTab) {
  523. var userInfo = document.getElementById('user-info'),
  524. tabHandles = userInfo.querySelector('.nav-tabs'),
  525. tabs = userInfo.querySelector('.tab-content'),
  526. tabHandle = document.createElement('li'),
  527. tab = document.createElement('div');
  528. tabHandle.innerHTML = '<a href="#sidepanel-imageoverlays" data-toggle="tab" title="' + I18n.t('image_overlays.tab_title') + '"><span class="fa fa-picture-o"></span></a>';
  529. if (recoveredTab) {
  530. tab = recoveredTab;
  531. } else {
  532. tab.id = 'sidepanel-imageoverlays';
  533. tab.className = 'tab-pane';
  534. }
  535. tabHandles.appendChild(tabHandle);
  536. $(tabHandle.childNodes[0]).tooltip();
  537. tabs.appendChild(tab);
  538. return tab;
  539. }
  540.  
  541. function createBreadcrumb(icon, text, status) {
  542. var label = document.createElement('span');
  543. label.className = 'label label-' + status;
  544. label.style.fontSize = '1.2em';
  545. label.style.cursor = 'default';
  546. var i = document.createElement('i');
  547. i.className = 'fa fa-fw fa-' + icon;
  548. label.appendChild(i);
  549. label.appendChild(document.createTextNode(' ' + text));
  550. return label;
  551. }
  552.  
  553. function createControlButton(icon, callback, text) {
  554. var controlButton = document.createElement('button');
  555. var controlButtonIcon = document.createElement('i');
  556. controlButtonIcon.className = 'fa fa-fw fa-' + icon;
  557. controlButton.appendChild(controlButtonIcon);
  558. if (text) {
  559. controlButton.appendChild(document.createTextNode(' ' + text));
  560. }
  561. controlButton.addEventListener('click', callback);
  562. return controlButton;
  563. }
  564.  
  565. function getIndexedDB(callback) {
  566. var req = indexedDB.open('ImageOverlays', 1);
  567. req.addEventListener('upgradeneeded', function(e) {
  568. e.target.result.createObjectStore('overlays', { autoIncrement: true });
  569. });
  570. req.addEventListener('error', log);
  571. req.addEventListener('success', function(e) {
  572. callback(e.target.result);
  573. });
  574. }
  575.  
  576. function log(message) {
  577. if (typeof message === 'string') {
  578. console.log('%c' + GM_info.script.name + ' (v' + GM_info.script.version + '): %c' + message, 'color:black', 'color:#d97e00');
  579. } else {
  580. console.log('%c' + GM_info.script.name + ' (v' + GM_info.script.version + ')', 'color:black', message);
  581. }
  582. }
  583.  
  584. init();