Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions actions/ChangeVoiceChannel.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,17 @@ def __init__(self, *args, **kwargs):

def on_ready(self):
super().on_ready()
self.register_backend_callback(VOICE_CHANNEL_SELECT, self._update_display)
self.plugin_base.connect_to_event(
event_id=f"{self.plugin_base.get_plugin_id()}::{VOICE_CHANNEL_SELECT}",
callback=self._update_display,
)

def _update_display(self, value: dict):
def _update_display(self, *args, **kwargs):
if not self.backend:
self.show_error()
return
self.hide_error()
value = args[1]
self._current_channel = value.get("channel_id", None) if value else None
self.icon_name = (
Icons.VOICE_CHANNEL_INACTIVE
Expand Down
10 changes: 7 additions & 3 deletions actions/Deafen.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ def __init__(self, *args, **kwargs):

def on_ready(self):
super().on_ready()
self.register_backend_callback(VOICE_SETTINGS_UPDATE, self._update_display)
self.plugin_base.connect_to_event(
event_id=f"{self.plugin_base.get_plugin_id()}::{VOICE_SETTINGS_UPDATE}",
callback=self._update_display,
)

def create_event_assigners(self):
self.event_manager.add_event_assigner(
Expand All @@ -46,13 +49,14 @@ def _on_toggle(self, _):
log.error(ex)
self.show_error(3)

def _update_display(self, value: dict):
def _update_display(self, *args, **kwargs):
if not self.backend:
self.show_error()
return
else:
self.hide_error()
self._deafened = value["deaf"]
data = args[1]
self._deafened = data.get("deaf", False)
icon = Icons.DEAFEN if self._deafened else Icons.UNDEAFEN
self.icon_name = Icons(icon)
self.current_icon = self.get_icon(self.icon_name)
Expand Down
10 changes: 7 additions & 3 deletions actions/Mute.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ def __init__(self, *args, **kwargs):

def on_ready(self):
super().on_ready()
self.register_backend_callback(VOICE_SETTINGS_UPDATE, self._update_display)
self.plugin_base.connect_to_event(
event_id=f"{self.plugin_base.get_plugin_id()}::{VOICE_SETTINGS_UPDATE}",
callback=self._update_display,
)

def create_event_assigners(self):
self.event_manager.add_event_assigner(
Expand Down Expand Up @@ -78,13 +81,14 @@ def _off_mute(self, _):
log.error(ex)
self.show_error(3)

def _update_display(self, value: dict):
def _update_display(self, *args, **kwargs):
if not self.backend:
self.show_error()
return
else:
self.hide_error()
self._muted = value["mute"]
data = args[1]
self._muted = data.get("mute", False)
icon = Icons.MUTE if self._muted else Icons.UNMUTE
self.icon_name = Icons(icon)
self.current_icon = self.get_icon(self.icon_name)
Expand Down
11 changes: 8 additions & 3 deletions actions/TogglePTT.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,11 @@ def __init__(self, *args, **kwargs):

def on_ready(self):
super().on_ready()
self.register_backend_callback(VOICE_SETTINGS_UPDATE, self._update_display)
#self.register_backend_callback(VOICE_SETTINGS_UPDATE, self._update_display)
self.plugin_base.connect_to_event(
event_id=f"{self.plugin_base.get_plugin_id()}::{VOICE_SETTINGS_UPDATE}",
callback=self._update_display,
)

def create_event_assigners(self):
self.event_manager.add_event_assigner(
Expand All @@ -52,13 +56,14 @@ def _on_toggle(self, _):
log.error(ex)
self.show_error(3)

def _update_display(self, value: dict):
def _update_display(self, *args, **kwargs):
if not self.backend:
self.show_error()
return
else:
self.hide_error()
self._mode = value["mode"]["type"]
data = args[1]
self._mode = data["mode"]["type"]
icon = Icons.PTT if self._mode == ActivityMethod.PTT else Icons.VOICE
self.icon_name = Icons(icon)
self.current_icon = self.get_icon(self.icon_name)
Expand Down
19 changes: 12 additions & 7 deletions actions/UserVolume.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,14 @@ def __init__(self, *args, **kwargs):
def on_ready(self):
super().on_ready()

# Subscribe to voice channel changes (doesn't need channel_id)
self.register_backend_callback(VOICE_CHANNEL_SELECT, self._on_voice_channel_select)

# Subscribe to GET_CHANNEL responses
self.register_backend_callback(GET_CHANNEL, self._on_get_channel)
self.plugin_base.connect_to_event(
event_id=f"{self.plugin_base.get_plugin_id()}::{VOICE_CHANNEL_SELECT}",
callback=self._on_voice_channel_select,
)
self.plugin_base.connect_to_event(
event_id=f"{self.plugin_base.get_plugin_id()}::{GET_CHANNEL}",
callback=self._on_get_channel,
)

# Initialize display
self._update_display()
Expand Down Expand Up @@ -130,8 +133,9 @@ def _adjust_volume(self, delta: int):

# === Discord Event Callbacks ===

def _on_voice_channel_select(self, data: dict):
def _on_voice_channel_select(self, *args, **kwargs):
"""Handle user joining/leaving voice channel."""
data = args[1]
try:
if data is None or data.get("channel_id") is None:
# Left voice channel - unsubscribe from previous channel
Expand Down Expand Up @@ -172,8 +176,9 @@ def _on_voice_channel_select(self, data: dict):
except Exception as ex:
log.error(f"UserVolume[{id(self)}]: Error in _on_voice_channel_select: {ex}")

def _on_get_channel(self, data: dict):
def _on_get_channel(self, *args, **kwargs):
"""Handle GET_CHANNEL response with initial user list."""
data = args[1]
if not data:
return

Expand Down
44 changes: 8 additions & 36 deletions backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ def __init__(self):
self.access_token: str = None
self.refresh_token: str = None
self.discord_client: AsyncDiscord = None
self.callbacks: dict = {}
self._is_authed: bool = False
self._current_voice_channel: str = None
self._is_reconnecting: bool = False
Expand Down Expand Up @@ -64,25 +63,19 @@ def discord_callback(self, code, event):
# Capture current user ID for filtering in UserVolume
data = event.get("data", {})
user = data.get("user", {})
self._register_callbacks()
self._current_user_id = user.get("id")
for k in self.callbacks:
self.discord_client.subscribe(k)
self._get_current_voice_channel()
case commands.DISPATCH:
evt = event.get("evt")
self.frontend.handle_callback(evt, event.get("data"))
self.frontend.trigger_event(evt, event.get("data"))
case commands.GET_SELECTED_VOICE_CHANNEL:
self._current_voice_channel = (
event.get("data").get("channel_id") if event.get("data") else None
)
self.frontend.handle_callback(
commands.VOICE_CHANNEL_SELECT, event.get("data")
)
self.frontend.trigger_event(commands.VOICE_CHANNEL_SELECT, event.get("data"))
case commands.GET_CHANNEL:
# Dispatch channel info (including voice_states) to frontend
self.frontend.handle_callback(
commands.GET_CHANNEL, event.get("data")
)
self.frontend.trigger_event(commands.GET_CHANNEL, event.get("data"))

def _update_tokens(self, access_token: str = "", refresh_token: str = ""):
self.access_token = access_token
Expand All @@ -96,15 +89,11 @@ def setup_client(self):
return
try:
self._is_reconnecting = True
log.debug("new client")
self.discord_client = AsyncDiscord(self.client_id, self.client_secret)
log.debug("connect")
self.discord_client.connect(self.discord_callback)
if not self.access_token:
log.debug("authorize")
self.discord_client.authorize()
else:
log.debug("authenticate")
self.discord_client.authenticate(self.access_token)
except Exception as ex:
self.frontend.on_auth_callback(False, str(ex))
Expand Down Expand Up @@ -136,25 +125,10 @@ def update_client_credentials(
def is_authed(self) -> bool:
return self._is_authed

def register_callback(self, key: str, callback: callable):
callbacks = self.callbacks.get(key, [])
# Deduplicate callbacks to prevent multiple executions
if callback not in callbacks:
callbacks.append(callback)
self.callbacks[key] = callbacks
if self._is_authed:
self.discord_client.subscribe(key)

def unregister_callback(self, key: str, callback: callable):
"""Remove a callback from the callback list."""
callbacks = self.callbacks.get(key, [])
if callback in callbacks:
callbacks.remove(callback)
if callbacks:
self.callbacks[key] = callbacks
else:
# Remove key entirely if no callbacks remain
del self.callbacks[key]
def _register_callbacks(self):
self.discord_client.subscribe(commands.VOICE_SETTINGS_UPDATE)
self.discord_client.subscribe(commands.VOICE_CHANNEL_SELECT)
self.discord_client.subscribe(commands.GET_CHANNEL)

def _ensure_connected(self) -> bool:
"""Ensure client is connected, trigger reconnection if needed."""
Expand Down Expand Up @@ -274,7 +248,6 @@ def subscribe_voice_states(self, channel_id: str) -> bool:
if not self._ensure_connected():
log.warning("Discord client not connected, cannot subscribe to voice states")
return False
log.debug(f"Subscribing to voice state events for channel {channel_id}")
args = {"channel_id": channel_id}
self.discord_client.subscribe(commands.VOICE_STATE_CREATE, args)
self.discord_client.subscribe(commands.VOICE_STATE_DELETE, args)
Expand All @@ -285,7 +258,6 @@ def unsubscribe_voice_states(self, channel_id: str) -> bool:
"""Unsubscribe from voice state events for a specific channel."""
if not self._ensure_connected():
return False
log.debug(f"Unsubscribing from voice state events for channel {channel_id}")
args = {"channel_id": channel_id}
self.discord_client.unsubscribe(commands.VOICE_STATE_CREATE, args)
self.discord_client.unsubscribe(commands.VOICE_STATE_DELETE, args)
Expand Down
35 changes: 35 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from src.backend.DeckManagement.InputIdentifier import Input
from src.backend.PluginManager.ActionInputSupport import ActionInputSupport
from src.backend.DeckManagement.ImageHelpers import image2pixbuf
from src.backend.PluginManager.EventHolder import EventHolder

# Import actions
from .settings import PluginSettings
Expand All @@ -21,6 +22,9 @@
from .actions.TogglePTT import TogglePTT
from .actions.UserVolume import UserVolume

# Import event IDs
from .discordrpc.commands import VOICE_CHANNEL_SELECT, VOICE_SETTINGS_UPDATE, GET_CHANNEL


class PluginTemplate(PluginBase):
def get_selector_icon(self) -> Gtk.Widget:
Expand All @@ -40,6 +44,7 @@ def __init__(self):
)
self._add_icons()
self._register_actions()
self._create_event_holders()
backend_path = os.path.join(self.PATH, "backend.py")
self.launch_backend(
backend_path=backend_path,
Expand Down Expand Up @@ -70,6 +75,29 @@ def __init__(self):
self.add_css_stylesheet(os.path.join(self.PATH, "style.css"))
self.setup_backend()

def _create_event_holders(self):
voice_channel_select = EventHolder(
plugin_base=self,
event_id_suffix=VOICE_CHANNEL_SELECT,
)

voice_settings_update = EventHolder(
plugin_base=self,
event_id_suffix=VOICE_SETTINGS_UPDATE,
)

get_channel = EventHolder(
plugin_base=self,
event_id_suffix=GET_CHANNEL,
)

self.add_event_holders([
voice_channel_select,
voice_settings_update,
get_channel,
])


def _add_icons(self):
self.add_icon("main", self.get_asset_path("Discord-Symbol-Blurple.png"))
self.add_icon("deafen", self.get_asset_path("deafen.png"))
Expand Down Expand Up @@ -213,3 +241,10 @@ def clear_callbacks(self, key: str, callback: callable):
self.callbacks[key] = callbacks
else:
del self.callbacks[key]

def trigger_event(self, event_id_suffix: str, data: any):
event_id = f"{self.get_plugin_id()}::{event_id_suffix}"
if not event_id in self.event_holders:
log.warning(f"Event ID {event_id} not registered.")
return
self.event_holders[event_id].trigger_event(data)
2 changes: 1 addition & 1 deletion manifest.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "1.10.0",
"version": "1.11.0",
"thumbnail": "store/thumbnail.png",
"id": "com_imdevinc_StreamControllerDiscordPlugin",
"name": "Discord - Debug",
Expand Down