diff --git a/plugins/RichDiscordStatus/__init__.py b/plugins/RichDiscordStatus/__init__.py index 4e77326..c92e5c1 100644 --- a/plugins/RichDiscordStatus/__init__.py +++ b/plugins/RichDiscordStatus/__init__.py @@ -16,10 +16,9 @@ class RichDiscordStatus(PluginBase): def get_status(self): data = { "start": self.start_time, - "large_image": "icon" + "large_image": "icon", + "buttons": [{"label": "GitHub", "url": self.info.url}] } - if AD_ENABLE: - data["buttons"] = [{"label": "GitHub", "url": self.info.url}] if self.app.currect_sound is not None: if (self.app.currect_sound.title is not None) and (self.app.currect_sound.artist is not None): data["details"] = f"{self.app.currect_sound.artist} - {self.app.currect_sound.title}" @@ -49,18 +48,26 @@ async def __status__(self) -> None: rpc.update(**self.get_status()) await asyncio.sleep(1) except DiscordNotFound: - await asyncio.sleep(10) + await asyncio.sleep(3) + + def on_init(self) -> None: + self.running = False def on_run(self) -> None: self.start_time = time.time() async def on_compose(self): self.running = True - self.thread = self.app.run_worker(self.__status__, "Rich Discord Status", "seaplayer.plugins.discord.status", thread=True) + self.thread = self.app.run_worker( + self.__status__, + "Rich Discord Status", + "seaplayer.plugins.discord.status", + thread=True, + exit_on_error=False + ) async def on_quit(self) -> None: self.running = False - await self.thread.wait() # ! Registration Plugin Class plugin_main = RichDiscordStatus \ No newline at end of file diff --git a/plugins/RichDiscordStatus/info.json b/plugins/RichDiscordStatus/info.json index 11ff06b..c41f6d9 100644 --- a/plugins/RichDiscordStatus/info.json +++ b/plugins/RichDiscordStatus/info.json @@ -1,7 +1,7 @@ { "name": "Rich Discord Status", "name_id": "seaplayer.plugins.discord.status", - "version": "0.2.0", + "version": "0.2.1", "author": "Romanin", "description": "Now what you are listening to will be visible to everyone in Discord.", "url": "https://github.com/romanin-rf/SeaPlayer" diff --git a/pyproject.toml b/pyproject.toml index 2b3d963..2f039fc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "SeaPlayer" -version = "0.7.4" +version = "0.7.5" description = "SeaPlayer is a player that works in the terminal." repository = "https://github.com/romanin-rf/SeaPlayer" authors = ["Romanin "] diff --git a/seaplayer/functions.py b/seaplayer/functions.py index 4cf3fae..3a216f4 100644 --- a/seaplayer/functions.py +++ b/seaplayer/functions.py @@ -36,6 +36,9 @@ def rich_exception(exc: Exception) -> str: return f"[red]{exc.__class__.__name__}[/red]: {exc.__str__()}" # ! Functions +def formater(**kwargs) -> str: + return ", ".join([f"{key}={repr(value)}" for key, value in kwargs.items()]) + def check_status(sound: CodecBase) -> Literal["Stoped", "Playing", "Paused"]: if sound.playing: if sound.paused: diff --git a/seaplayer/modules/colorizer.py b/seaplayer/modules/colorizer.py index 34a50da..d6a9ca9 100644 --- a/seaplayer/modules/colorizer.py +++ b/seaplayer/modules/colorizer.py @@ -64,4 +64,5 @@ def pullyper(tp: type) -> str: else: return tp.__name__ -def richefication(tp: type) -> str: return replaces(pullyper(tp), REPLACE_TYPES) \ No newline at end of file +def richefication(tp: type) -> str: + return replaces(pullyper(tp), REPLACE_TYPES) \ No newline at end of file diff --git a/seaplayer/plug/pipw.py b/seaplayer/plug/pipw.py index c4cc973..ca45acb 100644 --- a/seaplayer/plug/pipw.py +++ b/seaplayer/plug/pipw.py @@ -1,6 +1,5 @@ import sys import subprocess -from typing import Tuple # ! Main Class class PIPManager: diff --git a/seaplayer/plug/pluginbase.py b/seaplayer/plug/pluginbase.py index b949275..251c0f9 100644 --- a/seaplayer/plug/pluginbase.py +++ b/seaplayer/plug/pluginbase.py @@ -1,6 +1,11 @@ from pydantic import BaseModel +from textual.binding import Binding +from textual.screen import Screen +# > Typing +from typing import Optional, Generator, Type, Any # > Local Import's -from typing import Optional +from ..codeсbase import CodecBase +from ..functions import formater # ! Plugin Info Class class PluginInfo(BaseModel): @@ -14,7 +19,7 @@ class PluginInfo(BaseModel): # ! Plugin Base Class class PluginBase: def __init_repr__(self) -> str: - return f"[green]{self.info.name}[/] ({repr(self.info.name_id)}) [#00ffee]v{self.info.version}[/#00ffee] [yellow]is initialized[/yellow]!" + return f"{self.info.name} ({repr(self.info.name_id)}) [#60fdff]v{self.info.version}[/#60fdff] is [yellow]initialized[/yellow]!" def __init__(self, app, pl, info: PluginInfo) -> None: self.app = app @@ -23,7 +28,24 @@ def __init__(self, app, pl, info: PluginInfo) -> None: # > Logs self.app.info(self.__init_repr__()) + def __str__(self) -> str: + return f"{self.__class__.__name__}({formater(info=self.info)})" + + def __repr__(self) -> str: + return self.__str__() + + # ! App Specific Functions + def install_screen(self, name: str, screen: Screen) -> None: + self.app.SCREENS[name] = screen + self.app.install_screen(screen, name) + + def add_codecs(self, *codecs: Type[CodecBase]) -> None: + self.app.CODECS += [ *codecs ] + # ! Dev Functions + def on_bindings(self) -> Generator[Binding, Any, None]: + yield None + def on_init(self): pass diff --git a/seaplayer/plug/pluginbase.pyi b/seaplayer/plug/pluginbase.pyi index 610530c..ab6cdd4 100644 --- a/seaplayer/plug/pluginbase.pyi +++ b/seaplayer/plug/pluginbase.pyi @@ -1,8 +1,11 @@ from pydantic import BaseModel +from textual.screen import Screen +from textual.binding import Binding # > Typing -from typing import Optional +from typing import Optional, Generator, Type, Any # > Local Import's from ..seaplayer import SeaPlayer +from ..codeсbase import CodecBase from .pluginloader import PluginLoader # ! Plugin Info Class @@ -22,6 +25,11 @@ class PluginBase: def __init_repr__(self) -> str: ... def __init__(self, app: SeaPlayer, pl: PluginLoader, info: PluginInfo) -> None: ... + # ! App Specific Methods + def install_screen(self, name: str, screen: Screen) -> None: ... + def add_codecs(self, *codecs: Type[CodecBase]) -> None: ... + def on_bindings(self) -> Generator[Binding, Any, None]: ... + # ! App On Methods def on_init(self) -> None: ... def on_run(self) -> None: ... async def on_compose(self) -> None: ... diff --git a/seaplayer/plug/pluginloader.py b/seaplayer/plug/pluginloader.py index 1b65aba..951188c 100644 --- a/seaplayer/plug/pluginloader.py +++ b/seaplayer/plug/pluginloader.py @@ -5,11 +5,12 @@ from pathlib import Path from pydantic import BaseModel from rich.console import Console +from textual.binding import Binding # > ImportLib from importlib.util import spec_from_file_location, module_from_spec # > Typing from types import ModuleType -from typing import Optional, Dict, Union, Any, List, Tuple, Type +from typing import Optional, Dict, Union, Any, List, Tuple, Type, Generator # > Local Import's from .pipw import pip from .pluginbase import PluginInfo, PluginBase @@ -47,7 +48,7 @@ def load_module(init_path: str) -> PluginModuleType: ) module = module_from_spec(module_spec) - sys.modules[module_spec.name] = module + sys.modules[module_spec.name] = module # TODO: Temporary option as there is a risk of replacing existing modules # TODO: Make an environment-module to surround all these modules for security @@ -127,7 +128,7 @@ def enable_plugin_by_name_id(self, name_id: str) -> None: # ! Plugin Loader Class class PluginLoader: __title__: str = "PluginLoader" - __version__: str = "0.2.7" + __version__: str = "0.3.0" __author__: str = "Romanin" __email__: str = "semina054@gmail.com" @@ -208,8 +209,9 @@ def load_plugin_info(path: str) -> PluginInfo: data = file.read() return PluginInfo.model_validate_json(data) + # ! On Init Method def on_init(self) -> None: - self.app.info(f"{self.__title__} [#00ffee]v{self.__version__}[/#00ffee] from {self.__author__} ({self.__email__})", in_console=True) + self.app.info(f"{self.__title__} [#60fdff]v{self.__version__}[/#60fdff] from {self.__author__} ({self.__email__})", in_console=True) plugins_paths = list(self.search_plugins_paths()) self.app.info(f"Found plugins : {repr([os.path.basename(os.path.dirname(i[0])) for i in plugins_paths])}", in_console=True) self.app.info(f"Initialization plugins...", in_console=True) @@ -249,6 +251,15 @@ def on_init(self) -> None: self.app.info(f"Plugins loaded ([red]OFF[/red]) : {repr(self.off_plugins)}", in_console=True) self.app.info(f"---", in_console=True) + # ! App Specific Methods + def on_bindings(self) -> Generator[Binding, Any, None]: + yield None + for plugin in self.on_plugins: + for binding in plugin.on_bindings(): + if binding is not None: + yield binding + + # ! On Methods def on_run(self) -> None: for i in self.on_plugins: try: diff --git a/seaplayer/plug/pluginloader.pyi b/seaplayer/plug/pluginloader.pyi index deb23a6..2877d3f 100644 --- a/seaplayer/plug/pluginloader.pyi +++ b/seaplayer/plug/pluginloader.pyi @@ -1,5 +1,6 @@ from pathlib import Path from pydantic import BaseModel +from textual.binding import Binding # > Typing from types import ModuleType from typing import ( @@ -79,6 +80,10 @@ class PluginLoader: @staticmethod def load_plugin_info(path: str) -> PluginInfo: ... + # ! App Specific Methods + def on_bindings(self) -> Generator[Binding, Any, None]: ... + + # ! App On Methods def on_init(self) -> None: ... def on_run(self) -> None: ... async def on_compose(self) -> None: ... diff --git a/seaplayer/seaplayer.py b/seaplayer/seaplayer.py index 2124c17..ac1ef61 100644 --- a/seaplayer/seaplayer.py +++ b/seaplayer/seaplayer.py @@ -1,4 +1,5 @@ import os +import time import glob import asyncio # > Graphics @@ -21,7 +22,8 @@ aiter, check_status, image_from_bytes, - get_sound_basename, aio_check_status_code + get_sound_basename, + aio_check_status_code ) from .objects import ( Nofy, @@ -80,13 +82,14 @@ class SeaPlayer(App): "unknown": Unknown(id="screen_unknown"), "configurate": Configurate(id="screen_configurate") } + ENABLE_COMMAND_PALETTE = False # ! SeaPlayer Configuration cache = Cacher(CACHE_DIRPATH) config = SeaPlayerConfig(CONFIG_FILEPATH) image_type: Optional[Union[Type[AsyncImageLabel], Type[StandartImageLabel]]] = None - # ! Textual Keys Configuration + # ! Bindings BINDINGS = list(build_bindings(config)) # ! Template Configuration @@ -124,9 +127,17 @@ def __init__(self, *args, **kwargs) -> None: if ENABLE_PLUGIN_SYSTEM: self.plugin_loader = PluginLoader(self) self.plugin_loader.on_init() + for _binding in self.plugin_loader.on_bindings(): + if _binding is not None: + self.BINDINGS.append(_binding) def update_bindings(self) -> None: - self._bindings = _Bindings(list(build_bindings(self.config))) + bindings = list(build_bindings(self.config)) + if ENABLE_PLUGIN_SYSTEM: + for _binding in self.plugin_loader.on_bindings(): + if _binding is not None: + bindings.append(_binding) + self._bindings = _Bindings(bindings) # ! Inherited Functions async def action_push_screen(self, screen: str) -> None: @@ -158,6 +169,7 @@ def callnofy( ) -> CallNofy: cn = CallNofy(text, dosk) self.screen.mount(cn) + self.install_screen return cn async def aio_callnofy( @@ -246,7 +258,7 @@ async def update_loop_playback(self) -> None: def compose(self) -> ComposeResult: # * Other self.info("---") - self.info(f"{__title__} [#00ffee]v{__version__}[/#00ffee] from {__author__} ({__email__})") + self.info(f"{__title__} [#60fdff]v{__version__}[/#60fdff] from {__author__} ({__email__})") self.info(f"Source : {__url__}") self.info(f"Codecs : {repr(self.CODECS)}") self.info(f"Config Path : {repr(self.config.filepath)}") @@ -309,7 +321,6 @@ async def _spm(input: InputField, value: Any) -> None: if self.config.log_menu_enable: yield self.log_menu yield Footer() - self.run_worker( self.update_loop_playback, name="PLAYBACK_CONTROLLER", @@ -379,7 +390,6 @@ async def set_sound_for_playback( async def add_sounds_to_list(self) -> None: added_oks = 0 loading_nofy = await self.aio_callnofy(f"Found [cyan]{len(self.last_paths_globalized)}[/cyan] values. Loading...") - async for path in aiter(self.last_paths_globalized): sound = None async for codec in aiter(self.CODECS): @@ -499,7 +509,7 @@ async def action_quit(self): sound.unpause() sound.stop() return await super().action_quit() - + # ! Other def run(self, *args, **kwargs): if ENABLE_PLUGIN_SYSTEM: diff --git a/seaplayer/types/Cache.py b/seaplayer/types/Cache.py index 6af06b9..63b2ef8 100644 --- a/seaplayer/types/Cache.py +++ b/seaplayer/types/Cache.py @@ -11,15 +11,14 @@ class Cacher: def __init__(self, cache_dirpath: str) -> None: self.main_dirpath = os.path.abspath(cache_dirpath) self.vars_dirpath = os.path.join(self.main_dirpath, "vars") - # * Create Directory os.makedirs(self.main_dirpath, 0o755, True) os.makedirs(self.vars_dirpath, 0o755, True) - # * Check Directory assert os.path.isdir(self.main_dirpath) assert os.path.isdir(self.vars_dirpath) + # ! I/O Methods def write(self, data: Any, filepath: str) -> None: with open(filepath, "wb") as file: pickle.dump(data, file) @@ -32,9 +31,14 @@ def read(self, filepath: str, default: D) -> D: self.write(default, filepath) return default - def write_var(self, value: Any, name: str, *, group: str="main") -> None: self.write(value, os.path.join(self.vars_dirpath, f"{name}-{group}.pycache")) - def read_var(self, name: str, default: D, *, group: str="main") -> D: return self.read(os.path.join(self.vars_dirpath, f"{name}-{group}.pycache"), default) + # ! Var Methods + def write_var(self, value: Any, name: str, *, group: str="main") -> None: + self.write(value, os.path.join(self.vars_dirpath, f"{name}-{group}.pycache")) + + def read_var(self, name: str, default: D, *, group: str="main") -> D: + return self.read(os.path.join(self.vars_dirpath, f"{name}-{group}.pycache"), default) + # ! Main Methods def var(self, name: str, default: D, *, group: str="main") -> D: def setter(s, value: D) -> None: self.write_var(value, name, group=group) diff --git a/seaplayer/units.py b/seaplayer/units.py index 77f53ce..d8dd7be 100644 --- a/seaplayer/units.py +++ b/seaplayer/units.py @@ -6,7 +6,7 @@ # ! Metadata __title__ = "SeaPlayer" -__version__ = "0.7.4" +__version__ = "0.7.5" __author__ = "Romanin" __email__ = "semina054@gmail.com" __url__ = "https://github.com/romanin-rf/SeaPlayer"