Greasy Fork is available in English.

Waze Edit Count Monitor

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

Version vom 29.01.2023. Aktuellste Version

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