feat(sounds): add per-JID mute support and enhance playback behavior
Some users requested the ability to mute notifications from specific JIDs without disabling all sounds. This change introduces `mute` and `unmute` commands to allow fine-grained control over sound playback. While implementing this, the sound path expansion logic was improved by switching to `pathlib` with `lru_cache` for efficiency and clarity. Playback is now also rate-limited to avoid overlapping audio triggers from rapid message bursts. This lays the groundwork for more user-specific customization of sound notifications while keeping the experience smooth and reliable. Additionally the bug with ignoring sound's `kind` was fixed: now the plugin respects specific sounds set for rooms, chats and private messages within rooms. Finally, code was formatted via Black to avoid ambiguity in the formatting.
This commit is contained in:
154
stable/sounds.py
154
stable/sounds.py
@@ -5,20 +5,43 @@ Default settings require mpg123 to be installed on the host system:
|
||||
brew install mpg123
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
from os.path import expanduser
|
||||
from functools import lru_cache
|
||||
from pathlib import Path
|
||||
from typing import Tuple
|
||||
|
||||
import prof
|
||||
import subprocess
|
||||
import time
|
||||
|
||||
_last_played = {}
|
||||
|
||||
|
||||
def _play_sound(soundfile):
|
||||
if soundfile.startswith("~/"):
|
||||
soundfilenew = soundfile.replace("~", expanduser("~"), 1)
|
||||
else:
|
||||
soundfilenew = soundfile
|
||||
@lru_cache(maxsize=16)
|
||||
def get_expanded_path(path_str: str) -> Tuple[Path, str]:
|
||||
path = Path(path_str).expanduser()
|
||||
return path, str(path)
|
||||
|
||||
|
||||
def _play_sound_file(soundfile: str):
|
||||
soundfile_path, soundfile_str = get_expanded_path(soundfile)
|
||||
|
||||
if not soundfile_path.exists():
|
||||
prof.cons_show(f"Sound file not found: {soundfile_str}")
|
||||
return
|
||||
|
||||
now = time.monotonic()
|
||||
if _last_played.get(soundfile_str, 0) + 0.5 > now:
|
||||
return # skip if played too recently
|
||||
_last_played[soundfile_str] = now
|
||||
|
||||
audioplayer = prof.settings_string_get("sounds", "audioplayer", "mpg123 -q")
|
||||
subprocess.Popen([e for e in audioplayer.split(" ") + [soundfilenew]])
|
||||
subprocess.Popen([*audioplayer.split(), soundfile_str], start_new_session=True)
|
||||
|
||||
|
||||
def _play_sound(kind: str = "chat"):
|
||||
soundfile = prof.settings_string_get("sounds", kind, None)
|
||||
if soundfile:
|
||||
_play_sound_file(soundfile)
|
||||
|
||||
|
||||
def _cmd_sounds(arg1=None, arg2=None, arg3=None):
|
||||
@@ -52,7 +75,7 @@ def _cmd_sounds(arg1=None, arg2=None, arg3=None):
|
||||
|
||||
else:
|
||||
prof.cons_show("No sounds set.")
|
||||
|
||||
|
||||
return
|
||||
|
||||
if arg1 == "on":
|
||||
@@ -66,10 +89,14 @@ def _cmd_sounds(arg1=None, arg2=None, arg3=None):
|
||||
return
|
||||
|
||||
if arg1 == "test":
|
||||
if arg2 not in ("chat", "private", "room",):
|
||||
if arg2 not in (
|
||||
"chat",
|
||||
"private",
|
||||
"room",
|
||||
):
|
||||
prof.cons_bad_cmd_usage("/sounds")
|
||||
return
|
||||
|
||||
|
||||
enabled = prof.settings_boolean_get("sounds", "enabled", False)
|
||||
if not enabled:
|
||||
prof.cons_show("Sounds disabled")
|
||||
@@ -80,7 +107,7 @@ def _cmd_sounds(arg1=None, arg2=None, arg3=None):
|
||||
prof.cons_show("No sound file for " + arg2)
|
||||
return
|
||||
|
||||
_play_sound(soundfile)
|
||||
_play_sound(kind=arg2)
|
||||
return
|
||||
|
||||
if arg1 == "set":
|
||||
@@ -148,11 +175,27 @@ def _cmd_sounds(arg1=None, arg2=None, arg3=None):
|
||||
|
||||
return
|
||||
|
||||
if arg1 == "mute":
|
||||
if not arg2:
|
||||
prof.cons_bad_cmd_usage("/sounds mute <jid>")
|
||||
else:
|
||||
prof.settings_string_list_add("sounds", "muted", arg2)
|
||||
prof.cons_show("Muted JID: " + arg2)
|
||||
return
|
||||
|
||||
if arg1 == "unmute":
|
||||
if not arg2:
|
||||
prof.cons_bad_cmd_usage("/sounds unmute <jid>")
|
||||
else:
|
||||
prof.settings_string_list_remove("sounds", "muted", arg2)
|
||||
prof.cons_show("Unmuted JID: " + arg2)
|
||||
return
|
||||
|
||||
prof.cons_bad_cmd_usage("/sounds")
|
||||
|
||||
|
||||
def prof_init(version, status, account_name, fulljid):
|
||||
synopsis = [
|
||||
synopsis = [
|
||||
"/sounds",
|
||||
"/sounds on|off",
|
||||
"/sounds set audioplayer <cmd>",
|
||||
@@ -164,23 +207,35 @@ def prof_init(version, status, account_name, fulljid):
|
||||
"/sounds clear chat",
|
||||
"/sounds clear room",
|
||||
"/sounds clear private",
|
||||
"/sounds rooms add <roomjid>"
|
||||
"/sounds rooms add <roomjid>",
|
||||
]
|
||||
description = "Play sounds on various Profanity events. Calling with no args shows current sound files."
|
||||
args = [
|
||||
[ "on|off", "Enable or disable playing sounds." ],
|
||||
[ "set audioplayer <cmd>", "Commmand line of audio player to use for playing sound files. Defaults to mpg123."],
|
||||
[ "reset audioplayer", "Resets the audio player to the default mpg123." ],
|
||||
[ "set chat <file>", "Path to sound file to play on chat messages." ],
|
||||
[ "set room <file>", "Path to sound file to play on room messages." ],
|
||||
[ "set private <file>", "Path to sound file to play on private room messages." ],
|
||||
[ "test private|room|chat", "Test the configured audio player and sound file."],
|
||||
[ "clear chat", "Remove the sound for chat messages." ],
|
||||
[ "clear room", "Remove the sound for room messages." ],
|
||||
[ "clear private", "Remove the sound for private messages." ],
|
||||
[ "rooms add <roomjid>", "Add the room to the list that will play the room sound."],
|
||||
[ "rooms remove <roomjid>", "Remove the room from the list that will play the room sound."],
|
||||
[ "rooms clear", "Clear the room list, all rooms will play sounds on new messages."]
|
||||
["on|off", "Enable or disable playing sounds."],
|
||||
[
|
||||
"set audioplayer <cmd>",
|
||||
"Commmand line of audio player to use for playing sound files. Defaults to mpg123.",
|
||||
],
|
||||
["reset audioplayer", "Resets the audio player to the default mpg123."],
|
||||
["set chat <file>", "Path to sound file to play on chat messages."],
|
||||
["set room <file>", "Path to sound file to play on room messages."],
|
||||
["set private <file>", "Path to sound file to play on private room messages."],
|
||||
["test private|room|chat", "Test the configured audio player and sound file."],
|
||||
["clear chat", "Remove the sound for chat messages."],
|
||||
["clear room", "Remove the sound for room messages."],
|
||||
["clear private", "Remove the sound for private messages."],
|
||||
[
|
||||
"rooms add <roomjid>",
|
||||
"Add the room to the list that will play the room sound.",
|
||||
],
|
||||
[
|
||||
"rooms remove <roomjid>",
|
||||
"Remove the room from the list that will play the room sound.",
|
||||
],
|
||||
[
|
||||
"rooms clear",
|
||||
"Clear the room list, all rooms will play sounds on new messages.",
|
||||
],
|
||||
]
|
||||
examples = [
|
||||
"/sounds set audioplayer paplay",
|
||||
@@ -190,17 +245,21 @@ def prof_init(version, status, account_name, fulljid):
|
||||
"/sounds test private",
|
||||
"/sounds remove private",
|
||||
"/sounds rooms add myroom@conference.server.org",
|
||||
"/sounds on"
|
||||
"/sounds on",
|
||||
]
|
||||
|
||||
prof.register_command("/sounds", 0, 3, synopsis, description, args, examples, _cmd_sounds)
|
||||
prof.register_command(
|
||||
"/sounds", 0, 3, synopsis, description, args, examples, _cmd_sounds
|
||||
)
|
||||
|
||||
prof.completer_add("/sounds", [ "set", "clear", "on", "off", "rooms", "reset" ])
|
||||
prof.completer_add("/sounds set", [ "chat", "room", "private", "audioplayer" ])
|
||||
prof.completer_add("/sounds reset", [ "audioplayer" ])
|
||||
prof.completer_add("/sounds test", [ "chat", "room", "private" ])
|
||||
prof.completer_add("/sounds clear", [ "chat", "room", "private" ])
|
||||
prof.completer_add("/sounds rooms", [ "add", "remove", "clear" ])
|
||||
prof.completer_add(
|
||||
"/sounds", ["set", "clear", "on", "off", "rooms", "reset", "mute", "unmute"]
|
||||
)
|
||||
prof.completer_add("/sounds set", ["chat", "room", "private", "audioplayer"])
|
||||
prof.completer_add("/sounds reset", ["audioplayer"])
|
||||
prof.completer_add("/sounds test", ["chat", "room", "private"])
|
||||
prof.completer_add("/sounds clear", ["chat", "room", "private"])
|
||||
prof.completer_add("/sounds rooms", ["add", "remove", "clear"])
|
||||
prof.filepath_completer_add("/sounds set chat")
|
||||
prof.filepath_completer_add("/sounds set room")
|
||||
prof.filepath_completer_add("/sounds set private")
|
||||
@@ -211,11 +270,11 @@ def prof_post_chat_message_display(barejid, resource, message):
|
||||
if not enabled:
|
||||
return
|
||||
|
||||
soundfile = prof.settings_string_get("sounds", "chat", None)
|
||||
if not soundfile:
|
||||
muted = prof.settings_string_list_get("sounds", "muted")
|
||||
if muted and barejid in muted:
|
||||
return
|
||||
|
||||
_play_sound(soundfile)
|
||||
_play_sound(kind="chat")
|
||||
|
||||
|
||||
def prof_post_room_message_display(barejid, nick, message):
|
||||
@@ -224,21 +283,21 @@ def prof_post_room_message_display(barejid, nick, message):
|
||||
return
|
||||
|
||||
if my_nick == nick:
|
||||
return;
|
||||
return
|
||||
|
||||
enabled = prof.settings_boolean_get("sounds", "enabled", False)
|
||||
if not enabled:
|
||||
return
|
||||
|
||||
soundfile = prof.settings_string_get("sounds", "room", None)
|
||||
if not soundfile:
|
||||
return
|
||||
|
||||
roomlist = prof.settings_string_list_get("sounds", "rooms")
|
||||
if roomlist and (barejid not in roomlist):
|
||||
return
|
||||
|
||||
_play_sound(soundfile)
|
||||
muted = prof.settings_string_list_get("sounds", "muted")
|
||||
if muted and barejid in muted:
|
||||
return
|
||||
|
||||
_play_sound(soundfile, kind="room")
|
||||
|
||||
|
||||
def prof_post_priv_message_display(barejid, nick, message):
|
||||
@@ -246,7 +305,8 @@ def prof_post_priv_message_display(barejid, nick, message):
|
||||
if not enabled:
|
||||
return
|
||||
|
||||
soundfile = prof.settings_string_get("sounds", "private", None)
|
||||
if soundfile:
|
||||
_play_sound(soundfile)
|
||||
muted = prof.settings_string_list_get("sounds", "muted")
|
||||
if muted and barejid in muted:
|
||||
return
|
||||
|
||||
_play_sound(kind="private")
|
||||
|
||||
Reference in New Issue
Block a user