diff --git a/TwitchActionBase.py b/TwitchActionBase.py
deleted file mode 100644
index b627441..0000000
--- a/TwitchActionBase.py
+++ /dev/null
@@ -1,102 +0,0 @@
-from gi.repository import Gtk, Adw
-import gi
-
-from src.backend.PluginManager.ActionBase import ActionBase
-
-gi.require_version("Gtk", "4.0")
-gi.require_version("Adw", "1")
-
-
-class TwitchActionBase(ActionBase):
- def get_config_rows(self) -> list:
- authed = self.plugin_base.backend.is_authed()
- if not authed:
- label = "actions.base.credentials.no-credentials"
- css_style = "twitch-controller-red"
- else:
- label = "actions.base.credentials.authenticated"
- css_style = "twitch-controller-green"
-
- self.status_label = Gtk.Label(
- label=self.plugin_base.lm.get(label), css_classes=[css_style])
- self.client_id = Adw.EntryRow(
- title=self.plugin_base.lm.get("actions.base.twitch_client_id"))
- self.client_secret = Adw.PasswordEntryRow(
- title=self.plugin_base.lm.get("actions.base.twitch_client_secret"))
- self.auth_button = Gtk.Button(label=self.plugin_base.lm.get(
- "actions.base.credentials.validate"))
- self.auth_button.set_margin_top(10)
- self.auth_button.set_margin_bottom(10)
-
- self.client_id.connect("notify::text", self._on_change_client_id)
- self.client_secret.connect(
- "notify::text", self._on_change_client_secret)
- self.auth_button.connect("clicked", self._on_auth_clicked)
-
- group = Adw.PreferencesGroup()
- group.set_title(self.plugin_base.lm.get(
- "actions.base.credentials.title"))
- group.add(self.client_id)
- group.add(self.client_secret)
- group.add(self.status_label)
- group.add(self.auth_button)
-
- self.load_config()
- return [group]
-
- def get_custom_config_area(self):
- label = Gtk.Label(
- use_markup=True,
- label=f"{self.plugin_base.lm.get('actions.info.link.label')} {self.plugin_base.lm.get('actions.info.link.text')}")
- return label
-
- def load_config(self):
- settings = self.plugin_base.get_settings()
- client_id = settings.setdefault("client_id", "")
- client_secret = settings.setdefault("client_secret", "")
-
- self.client_id.set_text(client_id)
- self.client_secret.set_text(client_secret)
-
- self.plugin_base.set_settings(settings)
-
- def _on_change_client_id(self, entry, _):
- settings = self.plugin_base.get_settings()
- settings["client_id"] = entry.get_text()
- self.plugin_base.set_settings(settings)
-
- def _on_change_client_secret(self, entry, _):
- settings = self.plugin_base.get_settings()
- settings["client_secret"] = entry.get_text()
- self.plugin_base.set_settings(settings)
-
- def _on_auth_clicked(self, _):
- settings = self.plugin_base.get_settings()
- client_id = settings.get('client_id')
- client_secret = settings.get('client_secret')
- if not client_id or not client_secret:
- self._set_status(self.plugin_base.lm.get(
- "actions.base.credentials.no-credentials"), True)
- return
- self.auth_button.set_sensitive(False)
- self.plugin_base.auth_callback_fn = self.on_auth_completed
- self.plugin_base.backend.update_client_credentials(
- client_id, client_secret)
-
- def _set_status(self, message: str, is_error: bool = False):
- self.status_label.set_label(message)
- if is_error:
- self.status_label.remove_css_class("twitch-controller-green")
- self.status_label.add_css_class("twitch-controller-red")
- else:
- self.status_label.remove_css_class("twitch-controller-red")
- self.status_label.add_css_class("twitch-controller-green")
-
- def on_auth_completed(self, success: bool) -> None:
- self.auth_button.set_sensitive(True)
- if success:
- self._set_status(self.plugin_base.lm.get(
- "actions.base.credentials.authenticated"), False)
- else:
- self._set_status(self.plugin_base.lm.get(
- "actions.base.credentials.failed"), True)
diff --git a/actions/ad_schedule.py b/actions/ad_schedule.py
index ecb9b07..f92cfa6 100644
--- a/actions/ad_schedule.py
+++ b/actions/ad_schedule.py
@@ -2,17 +2,18 @@
import time
import os
-from loguru import logger as log
+from src.backend.PluginManager.ActionBase import ActionBase
-from plugins.com_imdevinc_StreamControllerTwitchPlugin.TwitchActionBase import TwitchActionBase
+from loguru import logger as log
# Currently an issue with TwitchPy that gets the wrong time format, can't use this yet
-class NextAd(TwitchActionBase):
+class NextAd(ActionBase):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.__ad_thread: threading.Thread = None
+ self.has_configuration = True
def on_ready(self):
self.set_media(media_path=os.path.join(
@@ -24,6 +25,8 @@ def on_ready(self):
def ad_thread(self):
while True:
+ if not self.get_is_present():
+ return
self.get_next_ad()
time.sleep(1)
diff --git a/actions/chat_mode.py b/actions/chat_mode.py
index 455baeb..c2e9ac1 100644
--- a/actions/chat_mode.py
+++ b/actions/chat_mode.py
@@ -7,7 +7,7 @@
from loguru import logger as log
-from plugins.com_imdevinc_StreamControllerTwitchPlugin.TwitchActionBase import TwitchActionBase
+from src.backend.PluginManager.ActionBase import ActionBase
gi.require_version("Gtk", "4.0")
gi.require_version("Adw", "1")
@@ -20,10 +20,11 @@
}
-class ChatMode(TwitchActionBase):
+class ChatMode(ActionBase):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._mode: str = None
+ self.has_configuration = True
def on_ready(self):
self._load_config()
@@ -89,6 +90,8 @@ def _on_change_mode(self, *_):
def get_mode_status(self):
while True:
+ if not self.get_is_present():
+ return
try:
if self._mode is None:
raise Exception(f'no config: {self._mode}')
diff --git a/actions/clip.py b/actions/clip.py
index d91d155..455e7e9 100644
--- a/actions/clip.py
+++ b/actions/clip.py
@@ -3,10 +3,10 @@
from loguru import logger as log
-from plugins.com_imdevinc_StreamControllerTwitchPlugin.TwitchActionBase import TwitchActionBase
+from src.backend.PluginManager.ActionBase import ActionBase
-class Clip(TwitchActionBase):
+class Clip(ActionBase):
def on_ready(self):
self.set_media(media_path=os.path.join(
self.plugin_base.PATH, "assets", "camera.png"), size=0.85)
diff --git a/actions/marker.py b/actions/marker.py
index 1f75c05..7a8fa5c 100644
--- a/actions/marker.py
+++ b/actions/marker.py
@@ -2,10 +2,10 @@
from loguru import logger as log
-from plugins.com_imdevinc_StreamControllerTwitchPlugin.TwitchActionBase import TwitchActionBase
+from src.backend.PluginManager.ActionBase import ActionBase
-class Marker(TwitchActionBase):
+class Marker(ActionBase):
def on_ready(self):
self.set_media(media_path=os.path.join(
self.plugin_base.PATH, "assets", "bookmark.png"), size=0.85)
diff --git a/actions/message.py b/actions/message.py
index ffc58b1..004f682 100644
--- a/actions/message.py
+++ b/actions/message.py
@@ -5,13 +5,16 @@
from loguru import logger as log
-from plugins.com_imdevinc_StreamControllerTwitchPlugin.TwitchActionBase import TwitchActionBase
+from src.backend.PluginManager.ActionBase import ActionBase
gi.require_version("Gtk", "4.0")
gi.require_version("Adw", "1")
-class SendMessage(TwitchActionBase):
+class SendMessage(ActionBase):
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.has_configuration = True
def on_ready(self):
self.set_media(media_path=os.path.join(
diff --git a/actions/play_ad.py b/actions/play_ad.py
index fd01aa7..50bf637 100644
--- a/actions/play_ad.py
+++ b/actions/play_ad.py
@@ -4,7 +4,7 @@
from loguru import logger as log
-from plugins.com_imdevinc_StreamControllerTwitchPlugin.TwitchActionBase import TwitchActionBase
+from src.backend.PluginManager.ActionBase import ActionBase
gi.require_version("Gtk", "4.0")
gi.require_version("Adw", "1")
@@ -12,9 +12,13 @@
options = [30, 60, 90, 120]
-class PlayAd(TwitchActionBase):
+class PlayAd(ActionBase):
_time: int = 30
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.has_configuration = True
+
def on_ready(self):
self.set_media(media_path=os.path.join(
self.plugin_base.PATH, "assets", "money.png"), size=0.85)
diff --git a/actions/snooze_ad.py b/actions/snooze_ad.py
index 7e6c060..67bf7e4 100644
--- a/actions/snooze_ad.py
+++ b/actions/snooze_ad.py
@@ -3,10 +3,10 @@
from loguru import logger as log
-from plugins.com_imdevinc_StreamControllerTwitchPlugin.TwitchActionBase import TwitchActionBase
+from src.backend.PluginManager.ActionBase import ActionBase
-class SnoozeAd(TwitchActionBase):
+class SnoozeAd(ActionBase):
def on_ready(self):
self.set_media(media_path=os.path.join(
self.plugin_base.PATH, "assets", "delay.png"), size=0.85)
diff --git a/actions/viewers.py b/actions/viewers.py
index 4d4b6a0..56c4d99 100644
--- a/actions/viewers.py
+++ b/actions/viewers.py
@@ -4,10 +4,10 @@
from loguru import logger as log
-from plugins.com_imdevinc_StreamControllerTwitchPlugin.TwitchActionBase import TwitchActionBase
+from src.backend.PluginManager.ActionBase import ActionBase
-class Viewers(TwitchActionBase):
+class Viewers(ActionBase):
__viewer_thread: threading.Thread = None
def __init__(self, *args, **kwargs):
@@ -33,6 +33,8 @@ def on_key_down(self):
def viewers_thread(self):
while True:
+ if not self.get_is_present():
+ return
self.show_current_viewers()
time.sleep(10)
diff --git a/main.py b/main.py
index 0973063..54b75e7 100644
--- a/main.py
+++ b/main.py
@@ -1,11 +1,17 @@
import os
import globals as gl
+import json
+
+from loguru import logger
# Import StreamController modules
from src.backend.PluginManager.PluginBase import PluginBase
from src.backend.PluginManager.ActionHolder import ActionHolder
+from src.backend.DeckManagement.InputIdentifier import Input
+from src.backend.PluginManager.ActionInputSupport import ActionInputSupport
# Import actions
+from .settings import PluginSettings
from .actions.message import SendMessage
from .actions.chat_mode import ChatMode
from .actions.clip import Clip
@@ -20,9 +26,13 @@ class PluginTemplate(PluginBase):
def __init__(self):
super().__init__()
+ self.has_plugin_settings = True
+
self.lm = self.locale_manager
self.lm.set_to_os_default()
+ self._settings_manager = PluginSettings(self)
+
self.auth_callback_fn: callable = None
# Launch backend
@@ -50,6 +60,11 @@ def __init__(self):
action_base=SendMessage,
action_id="com_imdevinc_StreamControllerTwitchPlugin::SendMessage",
action_name="Send Chat Message",
+ action_support={
+ Input.Key: ActionInputSupport.SUPPORTED,
+ Input.Dial: ActionInputSupport.UNTESTED,
+ Input.Touchscreen: ActionInputSupport.UNTESTED,
+ }
)
self.add_action_holder(self.message_action_holder)
@@ -58,6 +73,11 @@ def __init__(self):
action_base=Viewers,
action_id="com_imdevinc_StreamControllerTwitchPlugin::Viewers",
action_name="Show Viewers",
+ action_support={
+ Input.Key: ActionInputSupport.SUPPORTED,
+ Input.Dial: ActionInputSupport.UNTESTED,
+ Input.Touchscreen: ActionInputSupport.UNTESTED,
+ }
)
self.add_action_holder(self.viewer_action_holder)
@@ -65,7 +85,12 @@ def __init__(self):
plugin_base=self,
action_base=Marker,
action_id="com_imdevinc_StreamControllerTwitchPlugin::Marker",
- action_name="Create Stream Marker"
+ action_name="Create Stream Marker",
+ action_support={
+ Input.Key: ActionInputSupport.SUPPORTED,
+ Input.Dial: ActionInputSupport.UNTESTED,
+ Input.Touchscreen: ActionInputSupport.UNTESTED,
+ }
)
self.add_action_holder(self.marker_action_holder)
@@ -73,7 +98,12 @@ def __init__(self):
plugin_base=self,
action_base=ChatMode,
action_id="com_imdevinc_StreamControllerTwitchPlugin::ChatMode",
- action_name="Toggle Chat Mode"
+ action_name="Toggle Chat Mode",
+ action_support={
+ Input.Key: ActionInputSupport.SUPPORTED,
+ Input.Dial: ActionInputSupport.UNTESTED,
+ Input.Touchscreen: ActionInputSupport.UNTESTED,
+ }
)
self.add_action_holder(self.chat_mode_action_holder)
@@ -81,7 +111,12 @@ def __init__(self):
plugin_base=self,
action_base=Clip,
action_id="com_imdevinc_StreamControllerTwitchPlugin::Clip",
- action_name="Create Clip"
+ action_name="Create Clip",
+ action_support={
+ Input.Key: ActionInputSupport.SUPPORTED,
+ Input.Dial: ActionInputSupport.UNTESTED,
+ Input.Touchscreen: ActionInputSupport.UNTESTED,
+ }
)
self.add_action_holder(self.clip_action_holder)
@@ -89,7 +124,12 @@ def __init__(self):
plugin_base=self,
action_base=SnoozeAd,
action_id="com_imdevinc_StreamControllerTwitchPlugin::SnoozeAd",
- action_name="Snooze Ad"
+ action_name="Snooze Ad",
+ action_support={
+ Input.Key: ActionInputSupport.SUPPORTED,
+ Input.Dial: ActionInputSupport.UNTESTED,
+ Input.Touchscreen: ActionInputSupport.UNTESTED,
+ }
)
self.add_action_holder(self.snooze_ad_action_holder)
@@ -97,7 +137,12 @@ def __init__(self):
plugin_base=self,
action_base=PlayAd,
action_id="com_imdevinc_StreamControllerTwitchPlugin::PlayAd",
- action_name="Play Ad"
+ action_name="Play Ad",
+ action_support={
+ Input.Key: ActionInputSupport.SUPPORTED,
+ Input.Dial: ActionInputSupport.UNTESTED,
+ Input.Touchscreen: ActionInputSupport.UNTESTED,
+ }
)
self.add_action_holder(self.play_ad_action_holder)
@@ -105,16 +150,32 @@ def __init__(self):
# plugin_base=self,
# action_base=NextAd,
# action_id="com_imdevinc_StreamControllerTwitchPlugin::NextAd",
- # action_name="Next Ad"
+ # action_name="Next Ad",
+ # action_support={
+ # Input.Key: ActionInputSupport.SUPPORTED,
+ # Input.Dial: ActionInputSupport.UNTESTED,
+ # Input.Touchscreen: ActionInputSupport.UNTESTED,
+ # }
# )
# self.add_action_holder(self.next_ad_action_holder)
+ try:
+ with open(os.path.join(self.PATH, "manifest.json"), "r", encoding="UTF-8") as f:
+ data = json.load(f)
+ except Exception as ex:
+ logger.error(ex)
+ data = {}
+ app_manifest = {
+ "plugin_version": data.get("version", "0.0.0"),
+ "app_version": data.get("app-version", "0.0.0")
+ }
+
# Register plugin
self.register(
plugin_name="Twitch Integration",
github_repo="https://github.com/imdevinc/StreamControllerTwitchPlugin",
- plugin_version="1.0.0",
- app_version="1.1.1-alpha"
+ plugin_version=app_manifest.get("plugin_version"),
+ app_version=app_manifest.get("app_version")
)
self.add_css_stylesheet(os.path.join(self.PATH, "style.css"))
@@ -125,6 +186,9 @@ def save_auth_settings(self, client_id: str, client_secret: str, auth_code: str)
settings['auth_code'] = auth_code
self.set_settings(settings)
- def on_auth_callback(self, success: bool) -> None:
+ def on_auth_callback(self, success: bool, message: str = "") -> None:
if self.auth_callback_fn:
- self.auth_callback_fn(success)
+ self.auth_callback_fn(success, message)
+
+ def get_settings_area(self):
+ return self._settings_manager.get_settings_area()
diff --git a/manifest.json b/manifest.json
index 80ed573..328b973 100644
--- a/manifest.json
+++ b/manifest.json
@@ -1,5 +1,5 @@
{
- "version": "1.0.1",
+ "version": "1.4.0",
"thumbnail": "store/thumbnail.png",
"id": "com_imdevinc_StreamControllerTwitchPlugin",
"name": "Twitch Integration",
@@ -10,7 +10,7 @@
"twitch"
],
"minimum-app-version": "1.5.0-beta.4",
- "app-version": "1.5.0-beta.4",
+ "app-version": "1.5.0-beta.8",
"description": "Adds different controls for interacting with your Twitch stream",
"short-description": "Control Twitch"
}
diff --git a/settings.py b/settings.py
new file mode 100644
index 0000000..e082a09
--- /dev/null
+++ b/settings.py
@@ -0,0 +1,113 @@
+from gi.repository import Gtk, Adw
+import gi
+
+from loguru import logger
+
+from src.backend.PluginManager import PluginBase
+
+gi.require_version("Gtk", "4.0")
+gi.require_version("Adw", "1")
+
+KEY_CLIENT_SECRET = "client_secret"
+KEY_CLIENT_ID = "client_id"
+
+
+class PluginSettings:
+ _status_label: Gtk.Label
+ _client_id: Adw.EntryRow
+ _client_secret: Adw.PasswordEntryRow
+ _auth_button: Gtk.Button
+
+ def __init__(self, plugin_base: PluginBase):
+ self._plugin_base = plugin_base
+
+ def get_settings_area(self) -> Adw.PreferencesGroup:
+ if not self._plugin_base.backend.is_authed():
+ self._status_label = Gtk.Label(label=self._plugin_base.lm.get(
+ "actions.base.credentials.failed"), css_classes=["twitch-controller-red"])
+ else:
+ self._status_label = Gtk.Label(label=self._plugin_base.lm.get(
+ "actions.base.credentials.authenticated"), css_classes=["twitch-controller-green"])
+ self._client_id = Adw.EntryRow(
+ title=self._plugin_base.lm.get("actions.base.twitch_client_id"))
+ self._client_secret = Adw.PasswordEntryRow(
+ title=self._plugin_base.lm.get("actions.base.twitch_client_secret"))
+ self._auth_button = Gtk.Button(
+ label=self._plugin_base.lm.get("actions.base.credentials.validate"))
+ self._auth_button.set_margin_top(10)
+ self._auth_button.set_margin_bottom(10)
+ self._client_id.connect("notify::text", self._on_change_client_id)
+ self._client_secret.connect(
+ "notify::text", self._on_change_client_secret)
+ self._auth_button.connect("clicked", self._on_auth_clicked)
+
+ gh_link_label = self._plugin_base.lm.get("actions.info.link.label")
+ gh_link_text = self._plugin_base.lm.get("actions.info.link.text")
+ gh_label = Gtk.Label(
+ use_markup=True, label=f"{gh_link_label} {gh_link_text}")
+
+ self._load_settings()
+ self._enable_auth()
+
+ pref_group = Adw.PreferencesGroup()
+ pref_group.set_title(self._plugin_base.lm.get(
+ "actions.base.credentials.title"))
+ pref_group.add(self._status_label)
+ pref_group.add(self._client_id)
+ pref_group.add(self._client_secret)
+ pref_group.add(self._auth_button)
+ pref_group.add(gh_label)
+ return pref_group
+
+ def _load_settings(self):
+ settings = self._plugin_base.get_settings()
+ client_id = settings.get(KEY_CLIENT_ID, "")
+ client_secret = settings.get(KEY_CLIENT_SECRET, "")
+ self._client_id.set_text(client_id)
+ self._client_secret.set_text(client_secret)
+
+ def _update_status(self, message: str, is_error: bool):
+ style = "twitch-controller-red" if is_error else "twitch-controller-green"
+ self._status_label.set_text(message)
+ self._status_label.set_css_classes([style])
+
+ def _update_settings(self, key: str, value: str):
+ settings = self._plugin_base.get_settings()
+ settings[key] = value
+ self._plugin_base.set_settings(settings)
+
+ def _on_change_client_id(self, entry, _):
+ val = entry.get_text().strip()
+ self._update_settings(KEY_CLIENT_ID, val)
+ self._enable_auth()
+
+ def _on_change_client_secret(self, entry, _):
+ val = entry.get_text().strip()
+ self._update_settings(KEY_CLIENT_SECRET, val)
+ self._enable_auth()
+
+ def _on_auth_clicked(self, _):
+ if not self._plugin_base.backend:
+ self._update_status("Failed to load backend", True)
+ return
+ settings = self._plugin_base.get_settings()
+ client_id = settings.get(KEY_CLIENT_ID)
+ client_secret = settings.get(KEY_CLIENT_SECRET)
+ self._plugin_base.auth_callback_fn = self._on_auth_completed
+ self._plugin_base.backend.update_client_credentials(
+ client_id, client_secret)
+
+ def _enable_auth(self):
+ settings = self._plugin_base.get_settings()
+ client_secret = settings.get(KEY_CLIENT_SECRET, "")
+ client_id = settings.get(KEY_CLIENT_ID, "")
+ self._auth_button.set_sensitive(
+ len(client_id) > 0 and len(client_secret) > 0)
+
+ def _on_auth_completed(self, success: bool, message: str = ""):
+ self._enable_auth()
+ if not message:
+ lm_key = "authenticated" if success else "failed"
+ message = self._plugin_base.lm.get(
+ f"actions.base.credentials.{lm_key}")
+ self._update_status(message, not success)
diff --git a/twitch_backend.py b/twitch_backend.py
index e37966a..b6861a4 100644
--- a/twitch_backend.py
+++ b/twitch_backend.py
@@ -172,8 +172,8 @@ def update_client_credentials(self, client_id: str, client_secret: str) -> None:
url = f'https://id.twitch.tv/oauth2/authorize?{encoded_params}'
check = requests.get(url, timeout=5)
if check.status_code != 200:
- print('invalid client ID')
- self.auth_failed()
+ message = check.json().get("message") if check.json() else "Incorrect Client ID"
+ self.auth_failed(message)
return
webbrowser.open(
@@ -185,7 +185,7 @@ def new_code(self, auth_code: str) -> None:
def validate_auth(self) -> None:
try:
_ = self.twitch.get_streams(first=1, user_id=self.user_id)
- except ClientError:
+ except Exception as ex:
self.auth_with_code(
self.client_id, self.client_secret, self.auth_code)
@@ -205,9 +205,9 @@ def auth_with_code(self, client_id: str, client_secret: str, auth_code: str) ->
log.error("failed to authenticate", e)
self.auth_failed()
- def auth_failed(self) -> None:
+ def auth_failed(self, message: str = "") -> None:
self.user_id = None
- self.frontend.on_auth_callback(False)
+ self.frontend.on_auth_callback(False, message)
def is_authed(self) -> bool:
return self.user_id != None