Alerts to User (Dynamic Scanning with Notification Persistence)

Continuously scans for specific events and ensures the user gets notifications again if they return to the same event.

  1. // ==UserScript==
  2. // @name Alerts to User (Dynamic Scanning with Notification Persistence)
  3. // @namespace Violentmonkey Scripts
  4. // @match *://his.kaauh.org/lab/*
  5. // @grant none
  6. // @version 3.5
  7. // @author Hamad AlShegifi
  8. // @description Continuously scans for specific events and ensures the user gets notifications again if they return to the same event.
  9. // @license MIT
  10. // ==/UserScript==
  11.  
  12. (function () {
  13. 'use strict';
  14.  
  15. // Configuration Options
  16. const CONFIG = {
  17. SCAN_INTERVAL: 100, // Interval for scanning (in ms)
  18. FLASH_COLOR: "pink", // Flashing color for rows with ">"
  19. FLASH_INTERVAL: 500, // Interval for flashing effect (in ms)
  20. MODAL_TIMEOUT: 10000, // Timeout for modal auto-dismissal (in ms)
  21. DEBUG_MODE: true, // Enable/disable debug logs
  22. };
  23.  
  24. // Debug Logging
  25. function logDebug(message) {
  26. if (CONFIG.DEBUG_MODE) console.log(`[Alerts to User] ${message}`);
  27. }
  28.  
  29. // Function to create a custom modal dialog
  30. function showModal(message, onConfirm) {
  31. // Create overlay
  32. const overlay = document.createElement("div");
  33. overlay.style.position = "fixed";
  34. overlay.style.top = "0";
  35. overlay.style.left = "0";
  36. overlay.style.width = "100%";
  37. overlay.style.height = "100%";
  38. overlay.style.backgroundColor = "rgba(0, 0, 0, 0.5)";
  39. overlay.style.zIndex = "999";
  40. document.body.appendChild(overlay);
  41.  
  42. // Create modal
  43. const modal = document.createElement("div");
  44. modal.style.position = "fixed";
  45. modal.style.top = "50%";
  46. modal.style.left = "50%";
  47. modal.style.transform = "translate(-50%, -50%) scale(0.5)";
  48. modal.style.backgroundColor = "#f4f4f9";
  49. modal.style.padding = "30px";
  50. modal.style.boxShadow = "0px 10px 30px rgba(0, 0, 0, 0.15)";
  51. modal.style.zIndex = "1000";
  52. modal.style.borderRadius = "15px";
  53. modal.style.textAlign = "center";
  54. modal.style.transition = "transform 0.3s ease, opacity 0.3s ease";
  55. modal.style.opacity = "0";
  56.  
  57. // Add heading
  58. const heading = document.createElement("h2");
  59. heading.textContent = "Attention!";
  60. heading.style.fontFamily = "'Arial', sans-serif";
  61. heading.style.color = "#333";
  62. heading.style.marginBottom = "10px";
  63. heading.style.fontSize = "24px";
  64. modal.appendChild(heading);
  65.  
  66. // Add message
  67. const content = document.createElement("p");
  68. content.textContent = message;
  69. content.style.fontFamily = "'Arial', sans-serif";
  70. content.style.color = "#555";
  71. content.style.marginBottom = "20px";
  72. content.style.fontSize = "16px";
  73. content.style.lineHeight = "1.5";
  74. modal.appendChild(content);
  75.  
  76. // Add input field for user comment (if onConfirm is provided)
  77. if (onConfirm) {
  78. const input = document.createElement("input");
  79. input.type = "text";
  80. input.placeholder = "Enter your comment";
  81. input.style.padding = "10px";
  82. input.style.width = "100%";
  83. input.style.marginBottom = "20px";
  84. input.style.border = "1px solid #ccc";
  85. input.style.borderRadius = "5px";
  86. input.style.fontSize = "16px";
  87. modal.appendChild(input);
  88.  
  89. // Add Confirm button
  90. const confirmButton = createButton("Confirm", "#ff4081", () => {
  91. const comment = input.value.trim();
  92. if (comment) {
  93. onConfirm(comment);
  94. closeModal(modal, overlay);
  95. } else {
  96. alert("Please enter a comment!");
  97. }
  98. });
  99. modal.appendChild(confirmButton);
  100. } else {
  101. // Add OK button (for alerts without input)
  102. const okButton = createButton("OK", "#ff4081", () => {
  103. closeModal(modal, overlay);
  104. });
  105. modal.appendChild(okButton);
  106. }
  107.  
  108. // Append modal to the body
  109. document.body.appendChild(modal);
  110.  
  111. // Animate modal appearance
  112. setTimeout(() => {
  113. modal.style.transform = "translate(-50%, -50%) scale(1)";
  114. modal.style.opacity = "1";
  115. }, 10);
  116.  
  117. // Auto-dismiss modal after timeout
  118. setTimeout(() => {
  119. closeModal(modal, overlay);
  120. }, CONFIG.MODAL_TIMEOUT);
  121. }
  122.  
  123. // Helper function to create styled buttons
  124. function createButton(text, backgroundColor, onClick) {
  125. const button = document.createElement("button");
  126. button.textContent = text;
  127. button.style.padding = "10px 20px";
  128. button.style.border = "none";
  129. button.style.backgroundColor = backgroundColor;
  130. button.style.color = "white";
  131. button.style.borderRadius = "30px";
  132. button.style.cursor = "pointer";
  133. button.style.fontSize = "16px";
  134. button.style.transition = "background-color 0.3s ease, transform 0.2s ease";
  135.  
  136. // Hover effects
  137. button.addEventListener("mouseenter", () => {
  138. button.style.backgroundColor = darkenColor(backgroundColor, 20);
  139. button.style.transform = "scale(1.05)";
  140. });
  141. button.addEventListener("mouseleave", () => {
  142. button.style.backgroundColor = backgroundColor;
  143. button.style.transform = "scale(1)";
  144. });
  145.  
  146. // Click handler
  147. button.addEventListener("click", onClick);
  148.  
  149. return button;
  150. }
  151.  
  152. // Helper function to darken a color
  153. function darkenColor(color, percent) {
  154. const num = parseInt(color.slice(1), 16);
  155. const amt = Math.round(2.55 * percent);
  156. const R = (num >> 16) - amt;
  157. const G = ((num >> 8) & 0x00ff) - amt;
  158. const B = (num & 0x0000ff) - amt;
  159. return `#${(0x1000000 + (R < 255 ? (R < 1 ? 0 : R) : 255) * 0x10000 +
  160. (G < 255 ? (G < 1 ? 0 : G) : 255) * 0x100 +
  161. (B < 255 ? (B < 1 ? 0 : B) : 255))
  162. .toString(16)
  163. .slice(1)}`;
  164. }
  165.  
  166. // Function to close a modal
  167. function closeModal(modal, overlay) {
  168. modal.style.transform = "translate(-50%, -50%) scale(0.5)";
  169. modal.style.opacity = "0";
  170. overlay.style.opacity = "0";
  171. setTimeout(() => {
  172. document.body.removeChild(modal);
  173. document.body.removeChild(overlay);
  174. }, 300); // Match transition duration
  175. }
  176.  
  177. // Function to apply flashing effect to rows
  178. function applyFlashingEffect(rows) {
  179. rows.forEach(row => {
  180. row.style.transition = "background-color 0.5s ease";
  181. let isPink = false;
  182.  
  183. const intervalId = setInterval(() => {
  184. isPink = !isPink;
  185. row.style.backgroundColor = isPink ? CONFIG.FLASH_COLOR : "transparent";
  186. }, CONFIG.FLASH_INTERVAL);
  187.  
  188. // Stop flashing if the row is removed from the DOM
  189. const observer = new MutationObserver(() => {
  190. if (!document.contains(row)) {
  191. clearInterval(intervalId);
  192. observer.disconnect();
  193. }
  194. });
  195. observer.observe(document.body, { childList: true, subtree: true });
  196. });
  197. }
  198.  
  199. // Function to replace "NO RESULT" or "NO-XRESULT" with user comment
  200. function replacePlaceholderWithComment(divElement, comment) {
  201. if (divElement) {
  202. divElement.innerText = comment;
  203. }
  204. }
  205.  
  206. // Function to check for issues in the data
  207. function checkForIssues() {
  208. const resultDivs = document.querySelectorAll(
  209. 'div[role="gridcell"][col-id="TestResult"] app-result-value-render div'
  210. );
  211. const criticalDivs = document.querySelectorAll(
  212. 'div[role="gridcell"][col-id="LTFlag"] app-ref-high-low div span.critial-alret-indication'
  213. );
  214.  
  215. let hasMatch = false;
  216.  
  217. // Check for ">", "NO RESULT", and "NO-XRESULT"
  218. resultDivs.forEach(div => {
  219. const text = div.textContent.trim().toLowerCase();
  220.  
  221. if (text.includes(">") && !hasAlreadyNotified("greaterThan")) {
  222. const row = div.closest("div[role='row']");
  223. if (row) {
  224. applyFlashingEffect([row]);
  225. showModal("Dilution is required for this sample (> detected)!");
  226. setNotificationFlag("greaterThan");
  227. hasMatch = true;
  228. }
  229. }
  230.  
  231. if ((text === "no result" || text === "no-xresult") && !hasAlreadyNotified(text)) {
  232. showModal(`Please enter a comment to replace '${text.toUpperCase()}':`, (comment) => {
  233. replacePlaceholderWithComment(div, comment);
  234. setNotificationFlag(text);
  235. });
  236. hasMatch = true;
  237. }
  238. });
  239.  
  240. // Check for critical results (CL/CH)
  241. criticalDivs.forEach(div => {
  242. const text = div.textContent.trim();
  243. if (text === "CL" && !hasAlreadyNotified("CL")) {
  244. showModal("CRITICAL LOW RESULT DETECTED !!");
  245. setNotificationFlag("CL");
  246. hasMatch = true;
  247. } else if (text === "CH" && !hasAlreadyNotified("CH")) {
  248. showModal("CRITICAL HIGH RESULT DETECTED !!");
  249. setNotificationFlag("CH");
  250. hasMatch = true;
  251. }
  252. });
  253.  
  254. return hasMatch;
  255. }
  256.  
  257. // Function to check if a user has already been notified about a specific event
  258. function hasAlreadyNotified(eventKey) {
  259. return localStorage.getItem(eventKey) !== null;
  260. }
  261.  
  262. // Function to set a notification flag in localStorage
  263. function setNotificationFlag(eventKey) {
  264. localStorage.setItem(eventKey, true);
  265. }
  266.  
  267. // Continuous scanning function
  268. function startContinuousScanning() {
  269. logDebug("Starting continuous scanning...");
  270. const intervalId = setInterval(() => {
  271. const hasMatch = checkForIssues();
  272. if (hasMatch) {
  273. clearInterval(intervalId);
  274. logDebug("Match found. Stopping continuous scanning.");
  275. }
  276. }, CONFIG.SCAN_INTERVAL);
  277. }
  278.  
  279. // Use MutationObserver to detect dynamic content loading
  280. function observeDOMChanges() {
  281. const targetNode = document.body;
  282. const config = { childList: true, subtree: true };
  283.  
  284. const observer = new MutationObserver(() => {
  285. const resultDivs = document.querySelectorAll(
  286. 'div[role="gridcell"][col-id="TestResult"] app-result-value-render div'
  287. );
  288. if (resultDivs.length > 0) {
  289. logDebug("Relevant elements detected. Starting continuous scanning...");
  290. startContinuousScanning();
  291. observer.disconnect(); // Stop observing once elements are found
  292. }
  293. });
  294.  
  295. observer.observe(targetNode, config);
  296. }
  297.  
  298. // Main execution
  299. try {
  300. window.addEventListener('load', () => {
  301. logDebug("Page fully loaded. Observing DOM changes...");
  302. observeDOMChanges();
  303. });
  304. } catch (error) {
  305. logDebug(`An error occurred: ${error.message}`);
  306. }
  307. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址