AOPS classroom notification sound

Beep when a new message div is created

当前为 2024-06-26 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name AOPS classroom notification sound
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.3
  5. // @description Beep when a new message div is created
  6. // @author Shaun Wang
  7. // @match https://artofproblemsolving.com/classroom/room/*
  8. // @grant none
  9. // ==/UserScript==
  10.  
  11.  
  12. (function() {
  13. 'use strict';
  14.  
  15. // notification sound
  16. function notificationSound() {
  17. var context = new (window.AudioContext || window.webkitAudioContext)();
  18. // Oscillators and gain nodes for A and E notes
  19. var oscillatorA = context.createOscillator();
  20. var oscillatorE = context.createOscillator();
  21. var gainNodeA = context.createGain(); // Gain node for A note
  22. var gainNodeE = context.createGain(); // Gain node for E note
  23.  
  24. oscillatorA.type = 'sine';
  25. oscillatorE.type = 'sine';
  26.  
  27. // Connect oscillators to their respective gain nodes
  28. oscillatorA.connect(gainNodeA);
  29. oscillatorE.connect(gainNodeE);
  30.  
  31. // Connect gain nodes to context destination
  32. gainNodeA.connect(context.destination);
  33. gainNodeE.connect(context.destination);
  34.  
  35. // A note settings
  36. oscillatorA.frequency.setValueAtTime(440, context.currentTime);
  37. gainNodeA.gain.setValueAtTime(1, context.currentTime);
  38. gainNodeA.gain.exponentialRampToValueAtTime(0.00001, context.currentTime + 1); // Ramp to 0.00001 over 1 second
  39. oscillatorA.start(context.currentTime);
  40. oscillatorA.stop(context.currentTime + 1); // Stop A after 1 second
  41.  
  42. // E note settings (starts shortly after A)
  43. oscillatorE.frequency.setValueAtTime(659.25, context.currentTime + 0.1); // Start E after 0.1 seconds
  44. gainNodeE.gain.setValueAtTime(0.2, context.currentTime + 0.1);
  45. gainNodeE.gain.exponentialRampToValueAtTime(0.00001, context.currentTime + 0.8); // Ramp to 0.00001 over 0.7 seconds
  46. oscillatorE.start(context.currentTime + 0.1);
  47. oscillatorE.stop(context.currentTime + 0.9); // Stop E after 0.8 seconds
  48.  
  49. }
  50.  
  51. // check if the new node has the required classes
  52. function isTargetNode(node) {
  53. return node.nodeType === 1 && node.classList.contains('styles_thread__3HaEQ') && node.classList.contains('styles_topTracked__wDRH_');
  54. }
  55.  
  56. // mark existing messages to avoid triggering sound on them
  57. function markExistingMessages() {
  58. const existingMessages = document.querySelectorAll('.styles_thread__3HaEQ.styles_topTracked__wDRH_');
  59. existingMessages.forEach(message => {
  60. message.dataset.seen = 'true';
  61. });
  62. }
  63.  
  64. // monitors the page
  65. var observer = new MutationObserver(function(mutationsList) {
  66. for (var mutation of mutationsList) {
  67. if (mutation.type === 'childList') {
  68. for (var addedNode of mutation.addedNodes) {
  69. if (isTargetNode(addedNode) && !addedNode.dataset.seen) {
  70. addedNode.dataset.seen = 'true';
  71. notificationSound();
  72. }
  73. }
  74. }
  75. }
  76. });
  77.  
  78. // mark existing messages and start observing for new ones
  79. window.addEventListener('load', function() {
  80. markExistingMessages();
  81. observer.observe(document.body, { childList: true, subtree: true });
  82. });
  83. })();