mirror of
https://github.com/Matthww/TuneBot.git
synced 2026-09-21 22:47:51 +00:00
Merge pull request #44 from strNophix/lastfm-scrobbler
Plugins + lastfm scrobbler
This commit is contained in:
@@ -1,6 +1,16 @@
|
||||
from abc import ABC
|
||||
from abc import abstractmethod
|
||||
from typing import Any
|
||||
from typing import Optional
|
||||
from typing import Protocol
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Union
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from tunebot.plugins import ServiceEvent
|
||||
from discord.ext.commands import Cog
|
||||
|
||||
AnyDict = dict[Any, Any]
|
||||
|
||||
|
||||
class GlobalPlaylistSource(ABC):
|
||||
@@ -53,10 +63,53 @@ class AutoJoin(ABC):
|
||||
pass
|
||||
|
||||
|
||||
class ServiceBase(Protocol):
|
||||
async def on_dispatch(self, event: "ServiceEvent", payload: AnyDict):
|
||||
...
|
||||
|
||||
|
||||
class PluginManagerBase(Protocol):
|
||||
def get_plugin(self, name: str) -> Union["BasePluginInstance", None]:
|
||||
...
|
||||
|
||||
def remove_plugin(self, name: str):
|
||||
...
|
||||
|
||||
def enable_plugin(self, name: str, plugin: "BasePluginInstance"):
|
||||
...
|
||||
|
||||
async def dispatch(self, event: "ServiceEvent", payload: AnyDict = {}):
|
||||
...
|
||||
|
||||
|
||||
class PluginLoaderBase(Protocol):
|
||||
def load_plugin(self, plug_conf: AnyDict) -> "BasePluginInstance":
|
||||
...
|
||||
|
||||
|
||||
class GlobalUtils(Protocol):
|
||||
async def raw_table_lookup(self, table_name: str, keys: list[Any]) -> list[Any]:
|
||||
...
|
||||
|
||||
async def raw_table_del_entry(self, table_name: str, keys: list[Any]):
|
||||
...
|
||||
|
||||
|
||||
class BasePluginInstance(Protocol):
|
||||
config: AnyDict
|
||||
services: list["ServiceBase"]
|
||||
cogs: list[str]
|
||||
|
||||
|
||||
__all__ = (
|
||||
"GlobalPlaylistSource",
|
||||
"PlaylistSource",
|
||||
"GlobalPlaylist",
|
||||
"GlobalAutoJoin",
|
||||
"AutoJoin",
|
||||
"ServiceBase",
|
||||
"PluginManagerBase",
|
||||
"PluginLoaderBase",
|
||||
"GlobalUtils",
|
||||
"BasePluginInstance",
|
||||
)
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
# noreorder
|
||||
from tunebot.plugins.exceptions import *
|
||||
from tunebot.plugins.events import *
|
||||
from tunebot.plugins.plugin import *
|
||||
from tunebot.plugins.loader import *
|
||||
from tunebot.plugins.manager import *
|
||||
@@ -0,0 +1,16 @@
|
||||
from enum import auto
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class ServiceEvent(Enum):
|
||||
"""
|
||||
This Enum contains all possible events that can be dispatched to services
|
||||
|
||||
Args:
|
||||
Enum ([type]): [description]
|
||||
"""
|
||||
|
||||
TRACK_ENDED = auto()
|
||||
|
||||
|
||||
__all__ = ("ServiceEvent",)
|
||||
@@ -0,0 +1,5 @@
|
||||
class PluginInitFailed(Exception):
|
||||
pass
|
||||
|
||||
|
||||
__all__ = ("PluginInitFailed",)
|
||||
@@ -0,0 +1,42 @@
|
||||
import importlib
|
||||
from typing import Any
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from tunebot.plugins import PluginInstance
|
||||
from tunebot.plugins.exceptions import PluginInitFailed
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from tunebot import BasePluginInstance
|
||||
from tunebot import ServiceBase
|
||||
from bot import TuneBot
|
||||
|
||||
AnyDict = dict[Any, Any]
|
||||
|
||||
|
||||
class FileSystemPluginLoader:
|
||||
def __init__(self, bot: "TuneBot") -> None:
|
||||
self.bot = bot
|
||||
|
||||
def load_plugin(self, plug_conf: AnyDict) -> "BasePluginInstance":
|
||||
"""
|
||||
Loads a plugin from configuration and returns a sequence of Services
|
||||
|
||||
Raises:
|
||||
PluginInitFailed: [description]
|
||||
|
||||
Returns:
|
||||
tuple[list["ServiceBase"], list[str]]: [description]
|
||||
"""
|
||||
services: list["ServiceBase"] = []
|
||||
for service_location in plug_conf["services"]:
|
||||
module = importlib.import_module(service_location)
|
||||
|
||||
if not hasattr(module, "setup"):
|
||||
raise PluginInitFailed('Failed to find setup() for "{location}"')
|
||||
|
||||
services.append(module.setup(self.bot, plug_conf))
|
||||
|
||||
return PluginInstance(plug_conf["config"], services, plug_conf["cogs"])
|
||||
|
||||
|
||||
__all__ = ("FileSystemPluginLoader",)
|
||||
@@ -0,0 +1,79 @@
|
||||
from typing import Any
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Union
|
||||
|
||||
from discord.ext.commands.errors import ExtensionAlreadyLoaded
|
||||
from discord.ext.commands.errors import ExtensionFailed
|
||||
from discord.ext.commands.errors import ExtensionNotFound
|
||||
from discord.ext.commands.errors import NoEntryPointError
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from tunebot.abc import BasePluginInstance
|
||||
from tunebot.plugins import ServiceEvent
|
||||
from bot import TuneBot
|
||||
|
||||
AnyDict = dict[Any, Any]
|
||||
|
||||
|
||||
class SimplePluginManager:
|
||||
_plugins: dict[str, "BasePluginInstance"] = {}
|
||||
|
||||
def __init__(self, bot: "TuneBot") -> None:
|
||||
self.bot = bot
|
||||
|
||||
def get_plugin(self, name: str) -> Union["BasePluginInstance", None]:
|
||||
"""
|
||||
Retrieves the corresponding `BasePluginInstance` if it exists
|
||||
|
||||
Returns:
|
||||
Union["BasePluginInstance", None]: [description]
|
||||
"""
|
||||
return self._plugins.get(name)
|
||||
|
||||
def remove_plugin(self, name: str):
|
||||
"""
|
||||
Unloads/Removes all components related to the `BasePluginInstance`
|
||||
|
||||
Args:
|
||||
name (str): [description]
|
||||
"""
|
||||
plugin = self.get_plugin(name)
|
||||
|
||||
if not plugin:
|
||||
return
|
||||
|
||||
for cog in plugin.cogs:
|
||||
try:
|
||||
self.bot.unload_extension(cog)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
del self._plugins[name]
|
||||
|
||||
def enable_plugin(self, name: str, plugin: "BasePluginInstance"):
|
||||
"""
|
||||
Loads/Activates all cogs/services included within the plugin
|
||||
|
||||
Args:
|
||||
plugin_name (str): [description]
|
||||
services (list[): [description]
|
||||
cog_names (list[str]): [description]
|
||||
"""
|
||||
self._plugins[name] = plugin
|
||||
for cog_name in plugin.cogs:
|
||||
self.bot.load_extension(cog_name)
|
||||
|
||||
async def dispatch(self, event: "ServiceEvent", payload: AnyDict = {}):
|
||||
"""
|
||||
Dispatches an event to all registered services
|
||||
|
||||
Args:
|
||||
event (ServiceEvent): [description]
|
||||
payload (AnyDict, optional): [description]. Defaults to {}.
|
||||
"""
|
||||
for plugin in self._plugins.values():
|
||||
for service in plugin.services:
|
||||
await service.on_dispatch(event, payload)
|
||||
|
||||
|
||||
__all__ = ("SimplePluginManager",)
|
||||
@@ -0,0 +1,18 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from tunebot import ServiceBase
|
||||
|
||||
AnyDict = dict[Any, Any]
|
||||
|
||||
|
||||
@dataclass
|
||||
class PluginInstance:
|
||||
config: AnyDict
|
||||
services: list["ServiceBase"]
|
||||
cogs: list[str]
|
||||
|
||||
|
||||
__all__ = ("PluginInstance",)
|
||||
@@ -1,4 +1,6 @@
|
||||
from tunebot.redis.entity import * # noreorder
|
||||
# noreorder
|
||||
from tunebot.redis.entity import *
|
||||
from tunebot.redis.utils import *
|
||||
from tunebot.redis.autojoin import *
|
||||
from tunebot.redis.playlist import *
|
||||
from tunebot.redis.playlist_source import *
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
from typing import Any
|
||||
|
||||
from tunebot.redis import RedisBotEntity
|
||||
|
||||
|
||||
class GlobalRedisUtils(RedisBotEntity):
|
||||
async def raw_table_lookup(self, table_name: str, keys: list[Any]) -> list[Any]:
|
||||
if len(keys) == 0:
|
||||
return []
|
||||
|
||||
result: list[Any] = await self.redis.hmget(table_name, keys)
|
||||
return result
|
||||
|
||||
async def raw_table_del_entry(self, table_name: str, keys: list[Any]):
|
||||
if len(keys) == 0:
|
||||
return
|
||||
|
||||
await self.redis.hdel(table_name, *keys)
|
||||
Reference in New Issue
Block a user