Add log signature

Automatically adds your signature when creating logs on geocaching.com. With configurable content and desired log types.

  1. // ==UserScript==
  2. // @name Add log signature
  3. // @version 0.0.1
  4. // @namespace https://github.com/todeit02/geocaching-com_add_log_signature
  5. // @description Automatically adds your signature when creating logs on geocaching.com. With configurable content and desired log types.
  6. // @grant GM_registerMenuCommand
  7. // @require https://openuserjs.org/src/libs/sizzle/GM_config.js
  8. // @grant GM_getValue
  9. // @grant GM_setValue
  10. // @grant GM_notification
  11. // @grant GM.getValue
  12. // @grant GM.setValue
  13. // @grant GM.notification
  14. // @include /^https:\/\/www\.geocaching\.com\/seek\/log\.aspx.*$/
  15. // @include /^https:\/\/www\.geocaching\.com\/play\/geocache\/.+\/log$/
  16. // @run-at document-end
  17. // @connect *
  18. // ==/UserScript==
  19.  
  20.  
  21. "use strict";
  22.  
  23. const OLD_LOG_PAGE_PATHNAME = "/seek/log.aspx"
  24. const NEW_LOG_PAGE_PATHNAME_REGEX = /\/play\/geocache\/.+\/log/;
  25.  
  26. const LOG_TYPES = [
  27. {
  28. id: 2,
  29. name: "Found it"
  30. },
  31. {
  32. id: 3,
  33. name: "Didn't find it"
  34. },
  35. {
  36. id: 4,
  37. name: "Write note"
  38. },
  39. {
  40. id: 7,
  41. name: "Needs Archived"
  42. },
  43. {
  44. id: 9,
  45. name: "Will attend"
  46. },
  47. {
  48. id: 45,
  49. name: "Needs Maintenance"
  50. }
  51. ];
  52. const LOG_TYPE_CONFIG_FIELD_PREFIX = "logType";
  53.  
  54. (async () =>
  55. {
  56. const isNewLogPage = NEW_LOG_PAGE_PATHNAME_REGEX.test(window.location.pathname);
  57. const isOldLogPage = (window.location.pathname === OLD_LOG_PAGE_PATHNAME);
  58. if(!isNewLogPage && !isOldLogPage) throw new Error("Unrecognized log page.");
  59.  
  60. let logInputElement = null;
  61. let logTypeSelect = null;
  62. if(isNewLogPage)
  63. {
  64. logInputElement = document.querySelector("#LogText");
  65. logTypeSelect = await waitForNewLogTypeSelect();
  66. }
  67. else if(isOldLogPage)
  68. {
  69. logInputElement = document.querySelector("#ctl00_ContentBody_LogBookPanel1_uxLogInfo");
  70. logTypeSelect = document.querySelector("#ctl00_ContentBody_LogBookPanel1_ddLogType");
  71. }
  72.  
  73. const configPanel = await initializeConfigurability(LOG_TYPES);
  74. GM_registerMenuCommand("Configuration", () =>
  75. {
  76. configPanel.open();
  77. });
  78.  
  79. if(!configPanel.get("configHasBeenOpen"))
  80. {
  81. showMissingConfigurationNotification(configPanel);
  82. return;
  83. }
  84.  
  85. const signature = configPanel.get("signature");
  86. const onlyInNewLogs = configPanel.get("onlyInNewLogs");
  87.  
  88. const logInput = LogInput(logInputElement, signature);
  89.  
  90. logTypeSelect.addEventListener("change", handleLogTypeChange);
  91.  
  92. const selectedLogTypeId = parseInt(logTypeSelect.value);
  93. if(isNaN(selectedLogTypeId)) throw new UnrecognizedLogTypeError(logTypeSelect.value);
  94.  
  95. const currentText = logInput.domElement.value;
  96. if(shouldSignLog(selectedLogTypeId, currentText, onlyInNewLogs)) logInput.appendSignature();
  97.  
  98.  
  99. function shouldSignLog(currentLogTypeId, currentText, onlyInNewLogs)
  100. {
  101. return shouldSignLogType(currentLogTypeId) && shouldSignLogText(currentText, onlyInNewLogs);
  102. }
  103.  
  104.  
  105. function shouldSignLogType(logTypeId)
  106. {
  107. const logTypeIsUnselected = logTypeId < 0;
  108. if(logTypeIsUnselected) return true;
  109.  
  110. return configPanel.get(LOG_TYPE_CONFIG_FIELD_PREFIX + logTypeId);
  111. }
  112.  
  113.  
  114. function shouldSignLogText(logText, onlyInNewLogs)
  115. {
  116. return !logInput.logIsSigned() && (!onlyInNewLogs || (logText.length === 0));
  117. }
  118.  
  119.  
  120. function handleLogTypeChange()
  121. {
  122. const logTypeId = parseInt(logTypeSelect.value);
  123. if(isNaN(logTypeId)) throw new UnrecognizedLogTypeError(logTypeSelect.value);
  124.  
  125. if(logInput.containsOnlySignature()) logInput.domElement.value = ''
  126.  
  127. const currentText = logInput.domElement.value;
  128. if(shouldSignLog(logTypeId, currentText, onlyInNewLogs)) logInput.appendSignature();
  129. }
  130.  
  131.  
  132. function showMissingConfigurationNotification(configPanel)
  133. {
  134. const notificationOptions = Object.freeze(
  135. {
  136. title: "Missing Signature Text",
  137. text: "You haven't specified a signature yet.\n\nClick here to configure.",
  138. silent: true,
  139. onclick: () => void configPanel.open(),
  140. });
  141. GM_notification(notificationOptions);
  142. }
  143.  
  144.  
  145. function LogInput(inputElement, signature)
  146. {
  147. function containsOnlySignature()
  148. {
  149. return (inputElement.value === signature);
  150. }
  151.  
  152. function appendSignature()
  153. {
  154. const currentText = inputElement.value;
  155. const texts = [currentText, signature].filter(text => !!text);
  156. inputElement.value = texts.join("\n");
  157. }
  158.  
  159. function logIsSigned()
  160. {
  161. const currentText = inputElement.value;
  162. currentText.endsWith(signature);
  163. }
  164.  
  165. return {
  166. appendSignature,
  167. logIsSigned,
  168. containsOnlySignature,
  169. domElement: inputElement,
  170. };
  171. }
  172.  
  173.  
  174. function waitForNewLogTypeSelect()
  175. {
  176. return new Promise(resolve =>
  177. {
  178. const intervalId = window.setInterval(() =>
  179. {
  180. const element = document.querySelector("select.log-types");
  181. if(element)
  182. {
  183. window.clearInterval(intervalId);
  184. resolve(element);
  185. }
  186. }, 100);
  187. });
  188. }
  189.  
  190.  
  191. function initializeConfigurability(logTypes)
  192. {
  193. const logTypeFieldEntries = logTypes.map((logType, i) =>
  194. {
  195. const fieldName = LOG_TYPE_CONFIG_FIELD_PREFIX + logType.id;
  196. const fieldEntry =
  197. {
  198. section: (i === 0) ? ["Log types to sign"] : undefined,
  199. label: logType.name,
  200. type: "checkbox",
  201. default: true,
  202. };
  203.  
  204. return [fieldName, fieldEntry];
  205. });
  206. const logTypeFields = Object.fromEntries(logTypeFieldEntries);
  207.  
  208. const fields = Object.freeze(
  209. {
  210. "signature":
  211. {
  212. "label": 'Signature',
  213. "type": 'textarea',
  214. "section": ["General settings"],
  215. },
  216. "onlyInNewLogs":
  217. {
  218. "label": 'Insert only for new logs',
  219. "type": 'checkbox',
  220. "default": true,
  221. },
  222. ...logTypeFields,
  223. "configHasBeenOpen":
  224. {
  225. 'type': 'hidden',
  226. 'value': '',
  227. },
  228. });
  229.  
  230. return new Promise(resolve =>
  231. {
  232. const gmConfigConfig =
  233. {
  234. id: 'geocaching-log-singature',
  235. title: "Log Signature Settings",
  236. fields,
  237. events:
  238. {
  239. open: () => configPanel.set("configHasBeenOpen", "yes"),
  240. init: () => resolve(configPanel),
  241. },
  242. };
  243. const configPanel = new GM_config(gmConfigConfig);
  244. });
  245. }
  246.  
  247.  
  248. class UnrecognizedLogTypeError extends Error
  249. {
  250. constructor(logType)
  251. {
  252. super(`Unrecognized log type ID "${logType}".`);
  253. }
  254. }
  255. })();