Waze Edit Count Monitor

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

当前为 2024-08-11 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Waze Edit Count Monitor
  3. // @namespace https://greasyfork.org/en/users/45389-mapomatic
  4. // @version 2024.08.11.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. // @require https://greasyfork.org/scripts/24851-wazewrap/code/WazeWrap.js
  10. // @license GNU GPLv3
  11. // @contributionURL https://github.com/WazeDev/Thank-The-Authors
  12. // @grant GM_xmlhttpRequest
  13. // @grant GM_addElement
  14. // @grant GM_addStyle
  15. // @connect www.waze.com
  16. // @connect greasyfork.org
  17. // ==/UserScript==
  18.  
  19. /* global W */
  20. /* global toastr */
  21. /* global WazeWrap */
  22.  
  23. (function main() {
  24. 'use strict';
  25.  
  26. const SCRIPT_NAME = GM_info.script.name;
  27. const SCRIPT_VERSION = GM_info.script.version;
  28. const DOWNLOAD_URL = 'https://greasyfork.org/scripts/40313-waze-edit-count-monitor/code/Waze%20Edit%20Count%20Monitor.user.js';
  29.  
  30. // This function is injected into the page to allow it to run in the page's context.
  31. function wecmInjected() {
  32. const TOASTR_URL = 'https://cdnjs.cloudflare.com/ajax/libs/toastr.js/2.1.4/toastr.min.js';
  33. const TOASTR_SETTINGS = {
  34. remindAtEditCount: 100,
  35. warnAtEditCount: 150,
  36. wasReminded: false,
  37. wasWarned: false
  38. };
  39. const TOOLTIP_TEXT = 'Your daily edit count from your profile. Click to open your profile.';
  40.  
  41. let _$outputElem = null;
  42. let _$outputElemContainer = null;
  43. let _lastEditCount = null;
  44. let _userName = null;
  45. let _savesWithoutIncrease = 0;
  46. let _lastURCount = null;
  47. let _lastMPCount = null;
  48.  
  49. function log(message) {
  50. console.log('Edit Count Monitor:', message);
  51. }
  52.  
  53. function checkEditCount() {
  54. window.postMessage(JSON.stringify(['wecmGetCounts', _userName]), '*');
  55. TOASTR_SETTINGS.wasReminded = false;
  56. TOASTR_SETTINGS.wasWarned = false;
  57. toastr.remove();
  58. }
  59.  
  60. function updateEditCount(editCount, urCount, purCount, mpCount, noIncrement) {
  61. // Add the counter div if it doesn't exist.
  62. if ($('#wecm-count').length === 0) {
  63. _$outputElemContainer = $('<div>', { class: 'toolbar-button', style: 'font-weight: bold; font-size: 16px; border-radius: 10px; margin-left: 4px;' });
  64. const $innerDiv = $('<div>', { class: 'item-container', style: 'padding-left: 10px; padding-right: 10px; cursor: default;' });
  65. _$outputElem = $('<a>', {
  66. id: 'wecm-count',
  67. href: `https://www.waze.com/user/editor/${_userName.toLowerCase()}`,
  68. target: '_blank',
  69. style: 'text-decoration:none',
  70. 'data-original-title': TOOLTIP_TEXT
  71. });
  72. $innerDiv.append(_$outputElem);
  73. _$outputElemContainer.append($innerDiv);
  74. if ($('#toolbar > div > div.secondary-toolbar > div.secondary-toolbar-actions > div.secondary-toolbar-actions-edit').length) {
  75. // Production WME, as of 4/25/2023
  76. $('#toolbar > div > div.secondary-toolbar > div.secondary-toolbar-actions > div.secondary-toolbar-actions-edit').after(_$outputElemContainer);
  77. } else {
  78. // Beta WME, as of 4/25/2023
  79. $('#toolbar > div > div.secondary-toolbar > div:nth-child(1)').after(_$outputElemContainer);
  80. }
  81. _$outputElem.tooltip({
  82. placement: 'auto top',
  83. delay: { show: 100, hide: 100 },
  84. html: true,
  85. template: '<div class="tooltip wecm-tooltip" role="tooltip"><div class="tooltip-arrow"></div>'
  86. + '<div class="wecm-tooltip-header"><b></b></div>'
  87. + '<div class="wecm-tooltip-body tooltip-inner""></div></div>'
  88. });
  89. }
  90.  
  91. // log('edit count = ' + editCount + ', UR count = ' + urCount.count);
  92. if (_lastEditCount !== editCount || _lastURCount.count !== urCount.count || _lastMPCount.count !== mpCount.count) {
  93. _savesWithoutIncrease = 0;
  94. } else if (!noIncrement) {
  95. _savesWithoutIncrease++;
  96. }
  97.  
  98. let textColor;
  99. let bgColor;
  100. let tooltipTextColor;
  101. if (_savesWithoutIncrease < 5) {
  102. textColor = '#354148';
  103. bgColor = 'white';
  104. tooltipTextColor = 'black';
  105. } else if (_savesWithoutIncrease < 10) {
  106. textColor = '#354148';
  107. bgColor = 'yellow';
  108. tooltipTextColor = 'black';
  109. } else {
  110. textColor = 'white';
  111. bgColor = 'red';
  112. tooltipTextColor = 'white';
  113. }
  114. _$outputElemContainer.css('background-color', bgColor);
  115. _$outputElem.css('color', textColor).html(editCount);
  116. const urCountText = `<div style="margin-top:8px;padding:3px;">URs&nbsp;Closed:&nbsp;${urCount.count.toLocaleString()}&nbsp;&nbsp;(since&nbsp;${
  117. (new Date(urCount.since)).toLocaleDateString()})</div>`;
  118. const purCountText = `<div style="margin-top:0px;padding:0px 3px;">PURs&nbsp;Closed:&nbsp;${
  119. purCount.count.toLocaleString()}&nbsp;&nbsp;(since&nbsp;${(
  120. new Date(purCount.since)).toLocaleDateString()})</div>`;
  121. const mpCountText = `<div style="margin-top:0px;padding:0px 3px;">MPs&nbsp;Closed:&nbsp;${mpCount.count.toLocaleString()}&nbsp;&nbsp;(since&nbsp;${(
  122. new Date(mpCount.since)).toLocaleDateString()})</div>`;
  123. let warningText = '';
  124. if (_savesWithoutIncrease) {
  125. warningText = `<div style="border-radius:8px;padding:3px;margin-top:8px;margin-bottom:5px;color:${
  126. tooltipTextColor};background-color:${bgColor};">${_savesWithoutIncrease} ${
  127. (_savesWithoutIncrease > 1) ? 'consecutive saves' : 'save'} without an increase. ${
  128. (_savesWithoutIncrease >= 5) ? '(Are you throttled?)' : ''}</div>`;
  129. }
  130. _$outputElem.attr('data-original-title', TOOLTIP_TEXT + urCountText + purCountText + mpCountText + warningText);
  131. _lastEditCount = editCount;
  132. _lastURCount = urCount;
  133. _lastMPCount = mpCount;
  134. }
  135.  
  136. function receiveMessage(event) {
  137. let msg;
  138. try {
  139. msg = JSON.parse(event.data);
  140. } catch (err) {
  141. // Do nothing
  142. }
  143.  
  144. if (msg && msg[0] === 'wecmUpdateUi') {
  145. const editCount = msg[1][0];
  146. const urCount = msg[1][1];
  147. const purCount = msg[1][2];
  148. const mpCount = msg[1][3];
  149. updateEditCount(editCount, urCount, purCount, mpCount);
  150. }
  151. }
  152.  
  153. function errorHandler(callback) {
  154. try {
  155. callback();
  156. } catch (ex) {
  157. console.error('Edit Count Monitor:', ex);
  158. }
  159. }
  160.  
  161. let _ignoreNextEditCountCheck = false;
  162. async function init() {
  163. _userName = W.loginManager.user.getUsername();
  164. // Listen for events from sandboxed code.
  165. window.addEventListener('message', receiveMessage);
  166. // Listen for Save events.
  167.  
  168. $('head').append(
  169. $('<link/>', {
  170. rel: 'stylesheet',
  171. type: 'text/css',
  172. href: 'https://cdnjs.cloudflare.com/ajax/libs/toastr.js/2.1.4/toastr.min.css'
  173. }),
  174. $('<style type="text/css">#toast-container {position: absolute;} #toast-container > div {opacity: 0.95;} .toast-top-center {top: 30px;}</style>')
  175. );
  176. await $.getScript(TOASTR_URL);
  177. toastr.options = {
  178. target: '#map',
  179. timeOut: 9999999999,
  180. positionClass: 'toast-top-right',
  181. closeOnHover: false,
  182. closeDuration: 0,
  183. showDuration: 0,
  184. closeButton: true
  185. // preventDuplicates: true
  186. };
  187. W.editingMediator.on('change:editingHouseNumbers', () => { _ignoreNextEditCountCheck = true; });
  188. W.model.actionManager.events.register('afterclearactions', null, () => setTimeout(() => {
  189. if (!_ignoreNextEditCountCheck) {
  190. errorHandler(checkEditCount);
  191. } else {
  192. _ignoreNextEditCountCheck = false;
  193. }
  194. }, 100));
  195.  
  196. // Update the edit count first time.
  197. checkEditCount();
  198. log('Initialized.');
  199. }
  200.  
  201. function bootstrap() {
  202. if (W && W.loginManager && W.loginManager.events && W.loginManager.events.register && W.map && W.loginManager.user) {
  203. log('Initializing...');
  204. init();
  205. } else {
  206. log('Bootstrap failed. Trying again...');
  207. setTimeout(bootstrap, 1000);
  208. }
  209. }
  210.  
  211. bootstrap();
  212. }
  213.  
  214. // Code that is NOT injected into the page.
  215. // Note that jQuery may or may not be available, so don't rely on it in this part of the script.
  216.  
  217. function getEditCountFromProfile(profile) {
  218. const { editingActivity } = profile;
  219. return editingActivity[editingActivity.length - 1];
  220. }
  221.  
  222. function getEditCountByTypeFromProfile(profile, type) {
  223. const edits = profile.editsByType.find(editsEntry => editsEntry.key === type);
  224. return edits ? edits.value : -1;
  225. }
  226.  
  227. // Handle messages from the page.
  228. function receivePageMessage(event) {
  229. let msg;
  230. try {
  231. msg = JSON.parse(event.data);
  232. } catch (err) {
  233. // Ignore errors
  234. }
  235.  
  236. if (msg && msg[0] === 'wecmGetCounts') {
  237. const userName = msg[1];
  238. GM_xmlhttpRequest({
  239. method: 'GET',
  240. url: `https://www.waze.com/Descartes/app/UserProfile/Profile?username=${userName}`,
  241. onload: res => {
  242. const profile = JSON.parse(res.responseText);
  243. window.postMessage(JSON.stringify(['wecmUpdateUi', [
  244. getEditCountFromProfile(profile),
  245. getEditCountByTypeFromProfile(profile, 'mapUpdateRequest'),
  246. getEditCountByTypeFromProfile(profile, 'venueUpdateRequest'),
  247. getEditCountByTypeFromProfile(profile, 'machineMapProblem')
  248. ]]), '*');
  249. }
  250. });
  251. }
  252. }
  253.  
  254. function waitForWazeWrap() {
  255. return new Promise(resolve => {
  256. function loopCheck(tries = 0) {
  257. if (WazeWrap.Ready) {
  258. resolve();
  259. } else if (tries < 1000) {
  260. setTimeout(loopCheck, 200, ++tries);
  261. }
  262. }
  263. loopCheck();
  264. });
  265. }
  266.  
  267. async function loadScriptUpdateMonitor() {
  268. let updateMonitor;
  269. await waitForWazeWrap();
  270. try {
  271. updateMonitor = new WazeWrap.Alerts.ScriptUpdateMonitor(SCRIPT_NAME, SCRIPT_VERSION, DOWNLOAD_URL, GM_xmlhttpRequest);
  272. updateMonitor.start();
  273. } catch (ex) {
  274. // Report, but don't stop if ScriptUpdateMonitor fails.
  275. console.error(`${SCRIPT_NAME}:`, ex);
  276. }
  277. }
  278.  
  279. function injectScript() {
  280. GM_addStyle('.wecm-tooltip-body { max-width: 230px; }');
  281. GM_addElement('script', {
  282. textContent: `${wecmInjected.toString()} \nwecmInjected();`
  283. });
  284.  
  285. // Listen for events coming from the page script.
  286. window.addEventListener('message', receivePageMessage);
  287. }
  288.  
  289. function mainInit() {
  290. injectScript();
  291. loadScriptUpdateMonitor();
  292. }
  293.  
  294. mainInit();
  295. })();