Waze Edit Count Monitor

Displays your daily edit count in the WME toolbar. Warns if you might be throttled.

当前为 2022-11-16 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Waze Edit Count Monitor
  3. // @namespace https://greasyfork.org/en/users/45389-mapomatic
  4. // @version 2022.11.16.001
  5. // @description Displays your daily edit count in the WME toolbar. Warns if you might be throttled.
  6. // @author MapOMatic
  7. // @include /^https:\/\/(www|beta)\.waze\.com\/(?!user\/)(.{2,6}\/)?editor\/?.*$/
  8. // @require https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js
  9. // @license GNU GPLv3
  10. // @contributionURL https://github.com/WazeDev/Thank-The-Authors
  11. // @grant GM_xmlhttpRequest
  12. // @grant GM_addElement
  13. // @connect www.waze.com
  14.  
  15. // ==/UserScript==
  16.  
  17. /* global W */
  18. /* global toastr */
  19. /* global $ */
  20.  
  21. // This function is injected into the page to allow it to run in the page's context.
  22. function wecmInjected() {
  23. const TOASTR_URL = 'https://cdnjs.cloudflare.com/ajax/libs/toastr.js/2.1.4/toastr.min.js';
  24. const TOASTR_SETTINGS = {
  25. remindAtEditCount: 100,
  26. warnAtEditCount: 150,
  27. wasReminded: false,
  28. wasWarned: false
  29. };
  30. const TOOLTIP_TEXT = 'Your daily edit count from your profile. Click to open your profile.';
  31.  
  32. let _$outputElem = null;
  33. let _$outputElemContainer = null;
  34. let _lastEditCount = null;
  35. let _userName = null;
  36. let _savesWithoutIncrease = 0;
  37. let _lastURCount = null;
  38. let _lastMPCount = null;
  39.  
  40. function log(message) {
  41. console.log('Edit Count Monitor:', message);
  42. }
  43.  
  44. function checkEditCount() {
  45. window.postMessage(JSON.stringify(['wecmGetCounts', _userName]), '*');
  46. TOASTR_SETTINGS.wasReminded = false;
  47. TOASTR_SETTINGS.wasWarned = false;
  48. toastr.remove();
  49. }
  50.  
  51. function updateEditCount(editCount, urCount, mpCount, noIncrement) {
  52. // Add the counter div if it doesn't exist.
  53. if ($('#wecm-count').length === 0) {
  54. _$outputElemContainer = $('<div>', { class: 'toolbar-button', style: 'font-weight: bold; font-size: 16px; border-radius: 10px;' });
  55. const $innerDiv = $('<div>', { class: 'item-container', style: 'padding-left: 10px; padding-right: 10px; cursor: default;' });
  56. _$outputElem = $('<a>', {
  57. id: 'wecm-count',
  58. href: `https://www.waze.com/user/editor/${_userName.toLowerCase()}`,
  59. target: '_blank',
  60. style: 'text-decoration:none',
  61. 'data-original-title': TOOLTIP_TEXT
  62. });
  63. $innerDiv.append(_$outputElem);
  64. _$outputElemContainer.append($innerDiv);
  65. $('#edit-buttons .waze-icon-trash').before(_$outputElemContainer);
  66. _$outputElem.tooltip({
  67. placement: 'auto top',
  68. delay: { show: 100, hide: 100 },
  69. html: true,
  70. template: '<div class="tooltip" role="tooltip" style="opacity:0.95"><div class="tooltip-arrow"></div>'
  71. + '<div class="my-tooltip-header" style="display:block;"><b></b></div>'
  72. + '<div class="my-tooltip-body tooltip-inner" style="display:block; font-weight:600; !important"></div></div>'
  73. });
  74. }
  75.  
  76. // log('edit count = ' + editCount + ', UR count = ' + urCount.count);
  77. if (_lastEditCount !== editCount || _lastURCount.count !== urCount.count || _lastMPCount.count !== mpCount.count) {
  78. _savesWithoutIncrease = 0;
  79. } else if (!noIncrement) {
  80. _savesWithoutIncrease++;
  81. }
  82.  
  83. let textColor;
  84. let bgColor;
  85. let tooltipTextColor;
  86. if (_savesWithoutIncrease < 5) {
  87. textColor = '#354148';
  88. bgColor = 'white';
  89. tooltipTextColor = 'white';
  90. } else if (_savesWithoutIncrease < 10) {
  91. textColor = '#354148';
  92. bgColor = 'yellow';
  93. tooltipTextColor = 'black';
  94. } else {
  95. textColor = 'white';
  96. bgColor = 'red';
  97. tooltipTextColor = 'white';
  98. }
  99. _$outputElemContainer.css('background-color', bgColor);
  100. _$outputElem.css('color', textColor).html(editCount);
  101. const urCountText = `<div style="margin-top:8px;padding:3px;">UR's&nbsp;Closed:&nbsp;${urCount.count}&nbsp;&nbsp;(since&nbsp;${
  102. (new Date(urCount.since)).toLocaleDateString()})</div>`;
  103. const mpCountText = `<div style="margin-top:0px;padding:0px 3px;">MP's&nbsp;Closed:&nbsp;${mpCount.count}&nbsp;&nbsp;(since&nbsp;${(
  104. new Date(mpCount.since)).toLocaleDateString()})</div>`;
  105. const warningText = (_savesWithoutIncrease > 0) ? `<div style="border-radius:8px;padding:3px;margin-top:8px;margin-bottom:5px;color:${
  106. tooltipTextColor};background-color:${bgColor};">${_savesWithoutIncrease} consecutive saves without an increase. (Are you throttled?)</div>` : '';
  107. _$outputElem.attr('data-original-title', TOOLTIP_TEXT + urCountText + mpCountText + warningText);
  108. _lastEditCount = editCount;
  109. _lastURCount = urCount;
  110. _lastMPCount = mpCount;
  111. }
  112.  
  113. function receiveMessage(event) {
  114. let msg;
  115. try {
  116. msg = JSON.parse(event.data);
  117. } catch (err) {
  118. // Do nothing
  119. }
  120.  
  121. if (msg && msg[0] === 'wecmUpdateUi') {
  122. const editCount = msg[1][0];
  123. const urCount = msg[1][1];
  124. const mpCount = msg[1][2];
  125. updateEditCount(editCount, urCount, mpCount);
  126. }
  127. }
  128.  
  129. function errorHandler(callback) {
  130. try {
  131. callback();
  132. } catch (ex) {
  133. console.error('Edit Count Monitor:', ex);
  134. }
  135. }
  136.  
  137. async function init() {
  138. _userName = W.loginManager.user.userName;
  139. // Listen for events from sandboxed code.
  140. window.addEventListener('message', receiveMessage);
  141. // Listen for Save events.
  142.  
  143. $('head').append(
  144. $('<link/>', {
  145. rel: 'stylesheet',
  146. type: 'text/css',
  147. href: 'https://cdnjs.cloudflare.com/ajax/libs/toastr.js/2.1.4/toastr.min.css'
  148. }),
  149. $('<style type="text/css">#toast-container {position: absolute;} #toast-container > div {opacity: 0.95;} .toast-top-center {top: 30px;}</style>')
  150. );
  151. await $.getScript(TOASTR_URL);
  152. toastr.options = {
  153. target: '#map',
  154. timeOut: 9999999999,
  155. positionClass: 'toast-top-right',
  156. closeOnHover: false,
  157. closeDuration: 0,
  158. showDuration: 0,
  159. closeButton: true
  160. // preventDuplicates: true
  161. };
  162. W.model.actionManager.events.register('afterclearactions', null, () => errorHandler(checkEditCount));
  163.  
  164. // Update the edit count first time.
  165. checkEditCount();
  166. log('Initialized.');
  167. }
  168.  
  169. function bootstrap() {
  170. if (W && W.loginManager && W.loginManager.events && W.loginManager.events.register && W.map && W.loginManager.user) {
  171. log('Initializing...');
  172. init();
  173. } else {
  174. log('Bootstrap failed. Trying again...');
  175. setTimeout(bootstrap, 1000);
  176. }
  177. }
  178.  
  179. bootstrap();
  180. }
  181.  
  182.  
  183. // Code that is NOT injected into the page.
  184. // Note that jQuery may or may not be available, so don't rely on it in this part of the script.
  185.  
  186. function getEditorProfileFromSource(source) {
  187. const match = source.match(/gon.data=({.*?});gon.env=/i);
  188. return JSON.parse(match[1]);
  189. }
  190.  
  191. function getEditCountFromProfile(profile) {
  192. const editingActivity = profile.editingActivity;
  193. return editingActivity[editingActivity.length - 1];
  194. }
  195.  
  196. function getEditCountByTypeFromProfile(profile, type) {
  197. const edits = profile.editsByType.find(editsEntry => editsEntry.key === type);
  198. return edits ? edits.value : -1;
  199. }
  200.  
  201. // Handle messages from the page.
  202. function receivePageMessage(event) {
  203. let msg;
  204. try {
  205. msg = JSON.parse(event.data);
  206. } catch (err) {
  207. // Ignore errors
  208. }
  209.  
  210. if (msg && msg[0] === 'wecmGetCounts') {
  211. const userName = msg[1];
  212. GM_xmlhttpRequest({
  213. method: 'GET',
  214. url: `https://www.waze.com/user/editor/${userName}`,
  215. onload: res => {
  216. const profile = getEditorProfileFromSource(res.responseText);
  217. window.postMessage(JSON.stringify(['wecmUpdateUi', [
  218. getEditCountFromProfile(profile),
  219. getEditCountByTypeFromProfile(profile, 'mapUpdateRequest'),
  220. getEditCountByTypeFromProfile(profile, 'machineMapProblem')
  221. ]]), '*');
  222. }
  223. });
  224. }
  225. }
  226.  
  227. //const wecmInjectedScript = document.createElement('script');
  228. //wecmInjectedScript.textContent = `${wecmInjected.toString()} \nwecmInjected();`;
  229. //wecmInjectedScript.setAttribute('type', 'application/javascript');
  230. //document.body.appendChild(wecmInjectedScript);
  231.  
  232. let wecmInjectedScript = GM_addElement('script', {
  233. textContent: "" + wecmInjected.toString() + " \n" + "wecmInjected();"
  234. });
  235.  
  236. // Listen for events coming from the page script.
  237. window.addEventListener('message', receivePageMessage);