Plays a sound when a mention occurs, or when you are whispered, directly messaged, or addressed.
// ==UserScript==
// @name Wolfery Audio Notifier
// @name:de Wolfery Audio Benachrichtigungen
// @namespace https://forum.wolfery.com/u/felinex/
// @version 1.5
// @description Plays a sound when a mention occurs, or when you are whispered, directly messaged, or addressed.
// @description:de Spielt einen Sound ab, wenn eine Erwähnung (Mention) auftaucht, man angeflüstert, direkt angeschrieben (message) oder adressiert wird.
// @icon https://static.f-list.net/images/eicon/wolfery.png
// @license All Rights Reserved
// @author Felinex Gloomfort
// @match https://wolfery.com/*
// @match https://test.wolfery.com/*
// @match https://*.mucklet.com/*
// @icon
// @grant none
// ==/UserScript==
(function() {
'use strict';
console.log("[AudioNotifier] Script loaded.");
const SOUND_URL = 'https://actions.google.com/sounds/v1/alarms/beep_short.ogg';
const audio = new Audio(SOUND_URL);
audio.volume = 0.5;
let lastPlay = 0;
let initAttempts = 0;
function init() {
initAttempts++;
if (typeof window.app === 'undefined') {
if (initAttempts < 60) {
setTimeout(init, 500);
} else {
console.error("[AudioNotifier] ERROR: window.app was not found.");
}
return;
}
console.log(`[AudioNotifier] window.app found! Trying to load module 'charLog' (Attempt ${initAttempts})...`);
// Using getModule to fetch the module once the client initialized it.
// This avoids the "App.require may only be called from an AppModule's constructor" error.
const charLog = window.app.getModule('charLog');
if (!charLog) {
// Module is not ready yet, keep waiting
if (initAttempts < 60) {
setTimeout(init, 500);
} else {
console.error("[AudioNotifier] ERROR: 'charLog' module could not be loaded even after 30 seconds.");
}
return;
}
console.log("[AudioNotifier] Module 'charLog' successfully loaded. Adding EventModifier...");
try {
charLog.addEventModifier({
id: 'userscriptSoundNotifier',
sortOrder: 100,
callback: (ev, ctrl, mod) => {
console.log(`[AudioNotifier] Event received: ${ev.type}`, { event: ev, mod: mod, ctrlId: ctrl.id });
// Exclude muted events and own character's actions
if (mod.muted) return;
if (ev.char && ev.char.id === ctrl.id) return;
// 1. Check for mentions (Set by the system when highlight triggers match)
const isMention = !!mod.mentioned;
// 2. Target check (Is the character the target of a whisper/message/address?)
// We manually check the target attributes of the original event
let isTargetedToMe = false;
const isDirectType = ['whisper', 'message', 'address'].includes(ev.type);
if (isDirectType) {
// Either it is explicitly in the mod object (set by previous modifiers)
if (mod.targeted) {
isTargetedToMe = true;
}
// Or we look for the character directly in the event targets
else if (ev.target && ev.target.id === ctrl.id) {
isTargetedToMe = true;
}
else if (ev.targets && Array.isArray(ev.targets)) {
// For group messages (multiple targets)
if (ev.targets.some(t => t.id === ctrl.id)) {
isTargetedToMe = true;
}
}
}
console.log(`[AudioNotifier] Evaluation: Mention=${isMention}, Targeted=${isTargetedToMe}`);
// Trigger sound
if (isMention || isTargetedToMe) {
playSound();
}
}
});
console.log("[AudioNotifier] EventModifier successfully registered!");
} catch (err) {
console.error("[AudioNotifier] ERROR registering EventModifier:", err);
}
}
function playSound() {
const now = Date.now();
if (now - lastPlay < 1000) return; // Spam protection / throttling
lastPlay = now;
audio.play().then(() => {
console.log("[AudioNotifier] Sound played!");
}).catch(e => {
console.error("[AudioNotifier] ERROR playing sound. Browser blocking autoplay? Click anywhere on the page.", e);
});
}
// Catch first click to unlock browser autoplay restrictions
document.addEventListener('click', function unlockAudio() {
audio.play().then(() => { audio.pause(); audio.currentTime = 0; }).catch(() => {});
document.removeEventListener('click', unlockAudio);
}, { once: true });
// Wait briefly for the DOM loading process to begin before starting
setTimeout(init, 1000);
})();