Waze Edit Count Monitor

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

当前为 2023-01-29 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Waze Edit Count Monitor
  3. // @namespace https://greasyfork.org/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);