Waze Edit Count Monitor

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

当前为 2023-04-25 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Waze Edit Count Monitor
  3. // @namespace https://greasyfork.org/en/users/45389-mapomatic
  4. // @version 2023.04.25.002
  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. // ==/UserScript==
  15.  
  16. /* global W */
  17. /* global toastr */
  18.  
  19. // This function is injected into the page to allow it to run in the page's context.
  20. function wecmInjected() {
  21. 'use strict';
  22.  
  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, purCount, 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; margin-left: 4px;' });
  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. if ($('#toolbar > div > div.secondary-toolbar > div.secondary-toolbar-actions > div.secondary-toolbar-actions-edit').length) {
  66. // Production WME, as of 4/25/2023
  67. $('#toolbar > div > div.secondary-toolbar > div.secondary-toolbar-actions > div.secondary-toolbar-actions-edit').after(_$outputElemContainer);
  68. } else {
  69. // Beta WME, as of 4/25/2023
  70. $('#toolbar > div > div.secondary-toolbar > div:nth-child(1)').after(_$outputElemContainer);
  71. }
  72. _$outputElem.tooltip({
  73. placement: 'auto top',
  74. delay: { show: 100, hide: 100 },
  75. html: true,
  76. template: '<div class="tooltip" role="tooltip" style="opacity:0.95"><div class="tooltip-arrow"></div>'
  77. + '<div class="my-tooltip-header" style="display:block;"><b></b></div>'
  78. + '<div class="my-tooltip-body tooltip-inner" style="display:block; !important; min-width: fit-content"></div></div>'
  79. });
  80. }
  81.  
  82. // log('edit count = ' + editCount + ', UR count = ' + urCount.count);
  83. if (_lastEditCount !== editCount || _lastURCount.count !== urCount.count || _lastMPCount.count !== mpCount.count) {
  84. _savesWithoutIncrease = 0;
  85. } else if (!noIncrement) {
  86. _savesWithoutIncrease++;
  87. }
  88.  
  89. let textColor;
  90. let bgColor;
  91. let tooltipTextColor;
  92. if (_savesWithoutIncrease < 5) {
  93. textColor = '#354148';
  94. bgColor = 'white';
  95. tooltipTextColor = 'black';
  96. } else if (_savesWithoutIncrease < 10) {
  97. textColor = '#354148';
  98. bgColor = 'yellow';
  99. tooltipTextColor = 'black';
  100. } else {
  101. textColor = 'white';
  102. bgColor = 'red';
  103. tooltipTextColor = 'white';
  104. }
  105. _$outputElemContainer.css('background-color', bgColor);
  106. _$outputElem.css('color', textColor).html(editCount);
  107. const urCountText = `<div style="margin-top:8px;padding:3px;">URs&nbsp;Closed:&nbsp;${urCount.count.toLocaleString()}&nbsp;&nbsp;(since&nbsp;${
  108. (new Date(urCount.since)).toLocaleDateString()})</div>`;
  109. const purCountText = `<div style="margin-top:0px;padding:0px 3px;">PURs&nbsp;Closed:&nbsp;${purCount.count.toLocaleString()}&nbsp;&nbsp;(since&nbsp;${(
  110. new Date(purCount.since)).toLocaleDateString()})</div>`;
  111. const mpCountText = `<div style="margin-top:0px;padding:0px 3px;">MPs&nbsp;Closed:&nbsp;${mpCount.count.toLocaleString()}&nbsp;&nbsp;(since&nbsp;${(
  112. new Date(mpCount.since)).toLocaleDateString()})</div>`;
  113. let warningText = '';
  114. if (_savesWithoutIncrease) {
  115. warningText = `<div style="border-radius:8px;padding:3px;margin-top:8px;margin-bottom:5px;color:${
  116. tooltipTextColor};background-color:${bgColor};">${_savesWithoutIncrease} ${
  117. (_savesWithoutIncrease > 1) ? 'consecutive saves' : 'save'} without an increase. ${
  118. (_savesWithoutIncrease >= 5) ? '(Are you throttled?)' : ''}</div>`;
  119. }
  120. _$outputElem.attr('data-original-title', TOOLTIP_TEXT + urCountText + purCountText + mpCountText + warningText);
  121. _lastEditCount = editCount;
  122. _lastURCount = urCount;
  123. _lastMPCount = mpCount;
  124. }
  125.  
  126. function receiveMessage(event) {
  127. let msg;
  128. try {
  129. msg = JSON.parse(event.data);
  130. } catch (err) {
  131. // Do nothing
  132. }
  133.  
  134. if (msg && msg[0] === 'wecmUpdateUi') {
  135. const editCount = msg[1][0];
  136. const urCount = msg[1][1];
  137. const purCount = msg[1][2];
  138. const mpCount = msg[1][3];
  139. updateEditCount(editCount, urCount, purCount, mpCount);
  140. }
  141. }
  142.  
  143. function errorHandler(callback) {
  144. try {
  145. callback();
  146. } catch (ex) {
  147. console.error('Edit Count Monitor:', ex);
  148. }
  149. }
  150.  
  151. async function init() {
  152. _userName = W.loginManager.user.userName;
  153. // Listen for events from sandboxed code.
  154. window.addEventListener('message', receiveMessage);
  155. // Listen for Save events.
  156.  
  157. $('head').append(
  158. $('<link/>', {
  159. rel: 'stylesheet',
  160. type: 'text/css',
  161. href: 'https://cdnjs.cloudflare.com/ajax/libs/toastr.js/2.1.4/toastr.min.css'
  162. }),
  163. $('<style type="text/css">#toast-container {position: absolute;} #toast-container > div {opacity: 0.95;} .toast-top-center {top: 30px;}</style>')
  164. );
  165. await $.getScript(TOASTR_URL);
  166. toastr.options = {
  167. target: '#map',
  168. timeOut: 9999999999,
  169. positionClass: 'toast-top-right',
  170. closeOnHover: false,
  171. closeDuration: 0,
  172. showDuration: 0,
  173. closeButton: true
  174. // preventDuplicates: true
  175. };
  176. W.model.actionManager.events.register('afterclearactions', null, () => errorHandler(checkEditCount));
  177.  
  178. // Update the edit count first time.
  179. checkEditCount();
  180. log('Initialized.');
  181. }
  182.  
  183. function bootstrap() {
  184. if (W && W.loginManager && W.loginManager.events && W.loginManager.events.register && W.map && W.loginManager.user) {
  185. log('Initializing...');
  186. init();
  187. } else {
  188. log('Bootstrap failed. Trying again...');
  189. setTimeout(bootstrap, 1000);
  190. }
  191. }
  192.  
  193. bootstrap();
  194. }
  195.  
  196. // Code that is NOT injected into the page.
  197. // Note that jQuery may or may not be available, so don't rely on it in this part of the script.
  198.  
  199. function getEditCountFromProfile(profile) {
  200. 'use strict';
  201.  
  202. const { editingActivity } = profile;
  203. return editingActivity[editingActivity.length - 1];
  204. }
  205.  
  206. function getEditCountByTypeFromProfile(profile, type) {
  207. 'use strict';
  208.  
  209. const edits = profile.editsByType.find(editsEntry => editsEntry.key === type);
  210. return edits ? edits.value : -1;
  211. }
  212.  
  213. // Handle messages from the page.
  214. function receivePageMessage(event) {
  215. 'use strict';
  216.  
  217. let msg;
  218. try {
  219. msg = JSON.parse(event.data);
  220. } catch (err) {
  221. // Ignore errors
  222. }
  223.  
  224. if (msg && msg[0] === 'wecmGetCounts') {
  225. const userName = msg[1];
  226. GM_xmlhttpRequest({
  227. method: 'GET',
  228. url: `https://www.waze.com/Descartes/app/UserProfile/Profile?username=${userName}`,
  229. onload: res => {
  230. const profile = JSON.parse(res.responseText);
  231. window.postMessage(JSON.stringify(['wecmUpdateUi', [
  232. getEditCountFromProfile(profile),
  233. getEditCountByTypeFromProfile(profile, 'mapUpdateRequest'),
  234. getEditCountByTypeFromProfile(profile, 'venueUpdateRequest'),
  235. getEditCountByTypeFromProfile(profile, 'machineMapProblem')
  236. ]]), '*');
  237. }
  238. });
  239. }
  240. }
  241.  
  242. GM_addElement('script', {
  243. textContent: `${wecmInjected.toString()} \nwecmInjected();`
  244. });
  245.  
  246. // Listen for events coming from the page script.
  247. window.addEventListener('message', receivePageMessage);