Skip to content
Open
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
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,8 @@ tokens.txt
build/
dist/
Ghost.spec
changelog.py
changelog.py
lyrics
*.env
.env
env.
3 changes: 2 additions & 1 deletion gui/components/settings/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
from .apis import APIsPanel
from .session_spoofing import SessionSpoofingPanel
from .rich_presence import RichPresencePanel
from .snipers import SnipersPanel
from .snipers import SnipersPanel
from .spotify_lyrics import SpotifyLyricsPanel
145 changes: 145 additions & 0 deletions gui/components/settings/spotify_lyrics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import json
import os

import ttkbootstrap as ttk

from gui.components import RoundedButton, RoundedSwitch, SettingsPanel
from utils.files import get_application_support
from utils.spotify_lyrics import SpotifyLyricsService


class SpotifyLyricsPanel(SettingsPanel):
def __init__(self, root, parent, images, config, width=None):
super().__init__(root, parent, "Spotify Lyrics", images.get("rich_presence"), width=width, collapsed=False)
self.cfg = config
self.service = None
self.entries = {}
self.auth_button = None
self.settings_path = os.path.join(get_application_support(), "lyrics", "settings.json")
self.lyrics_settings = self._load_settings()

def _load_settings(self):
try:
with open(self.settings_path, "r") as settings_file:
return json.load(settings_file)
except (FileNotFoundError, json.JSONDecodeError):
return {
"credentials": {},
"view": {"timestamp": True, "label": True},
"timings": {"sendTimeOffset": 500, "enableAutooffset": True, "autooffset": 3},
"update": {"enableAutoupdate": True},
}

def _credential(self, key, default=""):
return self.lyrics_settings.get("credentials", {}).get(key, default)

def _save_settings(self, _event=None):
credentials = self.lyrics_settings.setdefault("credentials", {})
for key, entry in self.entries.items():
credentials[key] = entry.get()

view = self.lyrics_settings.setdefault("view", {})
view["timestamp"] = self.timestamp_entry.instate(["selected"])
view["label"] = self.label_entry.instate(["selected"])

os.makedirs(os.path.dirname(self.settings_path), exist_ok=True)
with open(self.settings_path, "w") as settings_file:
json.dump(self.lyrics_settings, settings_file, indent=4)
self.status_label.configure(text="Lyrics settings saved.")

def _start(self, _event=None):
self._save_settings()
if self.service and self.service.thread and self.service.thread.is_alive():
self.status_label.configure(text="Spotify Lyrics is already running.")
return

self.service = SpotifyLyricsService(
self.lyrics_settings.get("credentials", {}),
show_timestamp=self.lyrics_settings.get("view", {}).get("timestamp", True),
show_label=self.lyrics_settings.get("view", {}).get("label", True),
status_callback=self._set_status,
)
self.service.start()

def _authenticate(self, _event=None):
self._save_settings()
credentials = self.lyrics_settings.setdefault("credentials", {})
self.service = SpotifyLyricsService(credentials, status_callback=self._set_status)
self._set_status("Opening Spotify authentication ...")
self.service.authenticate(self._authentication_finished)

def _set_status(self, message):
try:
if not self.status_label.winfo_exists():
return
self.root.after(0, lambda: self.status_label.configure(text=message) if self.status_label.winfo_exists() else None)
except Exception:
return

def _authentication_finished(self, success, message):
if success:
self.root.after(0, self._save_settings)
self._set_status(message)

def stop(self):
if not self.service:
return
self.service.stop()
self.service = None

def _stop(self, _event=None):
self.stop()
self.status_label.configure(text="Spotify Lyrics stopped.")

def draw(self):
fields = [
("token", "Discord Token", self.cfg.get("token") or self._credential("token"), True),
("clientID", "Spotify Client ID", self._credential("clientID"), False),
("clientSecret", "Spotify Client Secret", self._credential("clientSecret"), True),
]

for row, (key, label_text, value, secret) in enumerate(fields):
label = ttk.Label(self.body, text=label_text)
label.configure(background=self.root.style.colors.get("dark"))
label.grid(row=row + 1, column=0, sticky=ttk.W, padx=(10, 10), pady=(2, 5))

entry = ttk.Entry(self.body, show="*" if secret else None)
entry.configure(foreground=self.root.style.colors.get("fg"))
entry.insert(0, value)
entry.grid(row=row + 1, column=1, sticky=ttk.EW, padx=(0, 10), pady=(2, 5))
self.entries[key] = entry

view = ttk.Frame(self.body, style="dark.TFrame")
view.grid(row=len(fields) + 1, column=0, columnspan=2, sticky=ttk.EW, padx=10, pady=(5, 5))
view.grid_columnconfigure(0, weight=1)

self.timestamp_entry = RoundedSwitch(
view,
variable=ttk.BooleanVar(value=self.lyrics_settings.get("view", {}).get("timestamp", True)),
parent_background=self.root.style.colors.get("dark"),
)
self.timestamp_entry.grid(row=0, column=1, sticky=ttk.E, padx=10, pady=5)
ttk.Label(view, text="Show timestamps").grid(row=0, column=0, sticky=ttk.W, pady=5)

self.label_entry = RoundedSwitch(
view,
variable=ttk.BooleanVar(value=self.lyrics_settings.get("view", {}).get("label", True)),
parent_background=self.root.style.colors.get("dark"),
)
self.label_entry.grid(row=1, column=1, sticky=ttk.E, padx=10, pady=5)
ttk.Label(view, text="Show lyrics label").grid(row=1, column=0, sticky=ttk.W, pady=5)

actions = ttk.Frame(self.body, style="dark.TFrame")
actions.grid(row=len(fields) + 2, column=0, columnspan=2, sticky=ttk.EW, padx=10, pady=(10, 5))
RoundedButton(actions, text="Save", command=self._save_settings).pack(side=ttk.LEFT, padx=(0, 5))
self.auth_button = RoundedButton(actions, text="Authenticate with Spotify", command=self._authenticate)
self.auth_button.pack(side=ttk.LEFT, padx=5)
RoundedButton(actions, text="Start", command=self._start).pack(side=ttk.LEFT, padx=5)
RoundedButton(actions, text="Stop", command=self._stop).pack(side=ttk.LEFT, padx=5)

self.status_label = ttk.Label(self.body, text="Ready")
self.status_label.configure(background=self.root.style.colors.get("dark"))
self.status_label.grid(row=len(fields) + 3, column=0, columnspan=2, sticky=ttk.W, padx=10, pady=(5, 10))

self.body.grid_columnconfigure(1, weight=1)
return self.wrapper
3 changes: 3 additions & 0 deletions gui/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,9 @@ def quit(self):
# # os._exit(0)
# self.root.destroy()
# sys.exit(0)
spotify_lyrics_panel = getattr(self.settings_page, "spotify_lyrics_panel", None)
if spotify_lyrics_panel:
spotify_lyrics_panel.stop()
self.root.destroy()
sys.exit(0)

Expand Down
10 changes: 9 additions & 1 deletion gui/pages/settings.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import sys
import ttkbootstrap as ttk
from gui.components.settings import GeneralPanel, ThemingPanel, APIsPanel, SessionSpoofingPanel, RichPresencePanel, SnipersPanel
from gui.components.settings import GeneralPanel, ThemingPanel, APIsPanel, SessionSpoofingPanel, RichPresencePanel, SnipersPanel, SpotifyLyricsPanel
from gui.components import RoundedFrame, DropdownMenu
from gui.helpers import Images, Style
from utils.config import Config
Expand All @@ -25,6 +25,7 @@ def __init__(self, root, bot_controller, draw_settings):
# "session_spoofing": None,
"rich_presence": None,
"snipers": None,
"spotify_lyrics": None,
}
self.pills = {}

Expand All @@ -33,6 +34,9 @@ def refresh_config(self):
return

try:
if getattr(self, "spotify_lyrics_panel", None):
self.spotify_lyrics_panel.stop()

for widget in self.parent.winfo_children():
widget.destroy()

Expand All @@ -50,6 +54,8 @@ def _create_sections(self, wrapper):
self.session_spoofing = SessionSpoofingPanel(self.root, general_wrapper, self.images, self.cfg).draw()
self.snipers = SnipersPanel(self.root, wrapper, self.images, self.cfg).draw()
self.rpc = RichPresencePanel(self.root, wrapper, self.images, self.cfg, bot_controller=self.bot_controller).draw()
self.spotify_lyrics_panel = SpotifyLyricsPanel(self.root, wrapper, self.images, self.cfg)
self.spotify_lyrics = self.spotify_lyrics_panel.draw()
self.apis = APIsPanel(self.root, general_wrapper, self.images, self.cfg).draw()
self.theming = ThemingPanel(self.root, wrapper, self.images, self.cfg, bot_controller=self.bot_controller).draw()

Expand All @@ -63,6 +69,7 @@ def _create_sections(self, wrapper):
# self.pages["session_spoofing"] = self.session_spoofing
self.pages["rich_presence"] = self.rpc
self.pages["snipers"] = self.snipers
self.pages["spotify_lyrics"] = self.spotify_lyrics

def _create_pill(self, parent, text, row, command):
def _hover_enter(_, pill, label):
Expand Down Expand Up @@ -121,6 +128,7 @@ def draw(self, parent):
# self._create_pill(self.pages_wrapper, "Session Spoofing", 3, lambda: self.toggle("session_spoofing"))
self._create_pill(self.pages_wrapper, "Rich Presence", 4, lambda: self.toggle("rich_presence"))
self._create_pill(self.pages_wrapper, "Snipers", 5, lambda: self.toggle("snipers"))
self._create_pill(self.pages_wrapper, "Spotify Lyrics", 6, lambda: self.toggle("spotify_lyrics"))

# -------

Expand Down
Loading
Loading