diff --git a/dmsh/cli.py b/dmsh/cli.py index 53f63a3..09ab9cf 100644 --- a/dmsh/cli.py +++ b/dmsh/cli.py @@ -13,6 +13,10 @@ CONFIG = { # campaign start date "campaign_start_date": "2.1125.5.25", + + # croaker address + "croaker_host": 'gumpas.local', + "croaker_port": 8003, } diff --git a/dmsh/croaker.py b/dmsh/croaker.py new file mode 100644 index 0000000..c232ac8 --- /dev/null +++ b/dmsh/croaker.py @@ -0,0 +1,39 @@ +import socket +from functools import cached_property +from dataclasses import dataclass + + +@dataclass +class CroakerClient(): + host: str + port: int + + @cached_property + def playlists(self): + return self.send("LIST").split("\n") + + def list(self, *args): + if not args: + return self.playlists + return self.send(f"LIST {args[0]}") + + def play(self, *args): + if not args: + return "Error: Must specify the playlist to play." + return self.send(f"PLAY {args[0]}") + + def skip(self, *args): + return self.send("FFWD") + + def send(self, msg: str): + BUFSIZE = 4096 + data = bytearray() + with socket.create_connection((self.host, self.port)) as sock: + sock.sendall(f"{msg}\n".encode()) + while True: + buf = sock.recv(BUFSIZE) + data.extend(buf) + if len(buf) < BUFSIZE: + break + sock.sendall(b'KTHX\n') + return data.decode() diff --git a/dmsh/shell/interactive_shell.py b/dmsh/shell/interactive_shell.py index da8490c..5f32cf1 100644 --- a/dmsh/shell/interactive_shell.py +++ b/dmsh/shell/interactive_shell.py @@ -10,6 +10,7 @@ from dmsh import campaign from dmsh import jobs from dmsh import striders from dmsh import hoppers +from dmsh import croaker from reckoning.calendar import TelisaranCalendar from reckoning.telisaran import Day @@ -30,6 +31,12 @@ class DMShell(BasePrompt): self._name = "DM Shell" self._prompt = ["dm"] self._toolbar = [("class:bold", " DMSH ")] + + self._croaker = croaker.CroakerClient( + host=self.cache['croaker_host'], + port=self.cache['croaker_port'] + ) + self._key_bindings = BINDINGS self._register_subshells() self._register_keybindings() @@ -54,10 +61,48 @@ class DMShell(BasePrompt): ("", " [F5] Date"), ("", " [F6] Job"), ("", " [F8] Save"), + ("", " [F9] Music Hotkeys"), + ("", " [^1-9] Music"), ("", " [^Q] Quit "), ] ) + @self.key_bindings.add("m", "1") + def music_session_start(event): + self.music(["play", "session_start"]) + + @self.key_bindings.add("m", "2") + def music_session_start(event): + self.music(["play", "sahwat"]) + + @self.key_bindings.add("m", "3") + def music_session_start(event): + self.music(["play", "battle"]) + + @self.key_bindings.add("m", "4") + def music_session_start(event): + self.music(["play", "boss"]) + + @self.key_bindings.add("m", "5") + def music_session_start(event): + self.music(["play", "tense"]) + + @self.key_bindings.add("m", "6") + def music_session_start(event): + self.music(["play", "quiet"]) + + @self.key_bindings.add("m", "7") + def music_session_start(event): + self.music(["play", "adventure"]) + + @self.key_bindings.add("m", "8") + def music_session_start(event): + self.music(["play", "mystery"]) + + @self.key_bindings.add("m", "9") + def music_session_start(event): + self.music(["play", "ffxii_battle"]) + @self.key_bindings.add("c-q") @self.key_bindings.add("c-d") @self.key_bindings.add("") @@ -92,6 +137,24 @@ class DMShell(BasePrompt): def save(event): self.save() + @self.key_bindings.add("f9") + def music(event): + self._music_hotkeys() + + + def _music_hotkeys(self): + self.console.print("To switch music streams, press [b]m[/b] followed a number 1-9:") + self.console.print(" [b]1[/b] Session Start") + self.console.print(" [b]2[/b] Sahwat Overland") + self.console.print(" [b]3[/b] Overland Battle") + self.console.print(" [b]4[/b] Boss Battle") + self.console.print(" [b]5[/b] Tense") + self.console.print(" [b]6[/b] Quiet") + self.console.print(" [b]7[/b] Dungeon Adventure") + self.console.print(" [b]8[/b] Dungeon Mystery") + self.console.print(" [b]9[/b] Dungeon Battle") + self.console.print("\nSee also the [link]music[/link] command.") + def _handler_date_season(self, *args): self.console.print(self.cache['calendar'].season) @@ -391,3 +454,38 @@ class DMShell(BasePrompt): """ freq = parts[0] if parts else 'nodesert' self.console.print(self._rolltable("locations.yaml", frequency=freq, die=4)) + + @command(usage=""" + [title]MUSIC[/title] + + [b]music[/b] Croaker controller. + + [title]USAGE[/title] + + [link]> music [COMMAND[, ARGS]][/link] + + COMMAND Description + + list [NAME] List available playlists, or the contents of the NAME playlist + play NAME Switch to the named playlist + skip Skip the current track + """, completer=WordCompleter([ + 'list', + 'play', + 'skip', + ])) + def music(self, parts=[]): + """ + Control the music player + """ + if parts: + cmd, *args = parts + else: + cmd = 'list' + args = [] + + handler = getattr(self._croaker, cmd, None) + if not handler: + self.console.error(f"Unsupported command: {cmd}. Try 'help music'.") + return + self.console.print(handler(*args))