mirror of
https://github.com/Matthww/TuneBot.git
synced 2026-09-21 19:57:48 +00:00
Merge pull request #44 from strNophix/lastfm-scrobbler
Plugins + lastfm scrobbler
This commit is contained in:
@@ -22,51 +22,74 @@ from discord.ext.commands.errors import ExtensionNotFound
|
||||
from discord.ext.commands.errors import NoEntryPointError
|
||||
|
||||
from context import CustomContext
|
||||
from tunebot.plugins import FileSystemPluginLoader
|
||||
from tunebot.plugins import SimplePluginManager
|
||||
from tunebot.redis import GlobalRedisAutoJoin
|
||||
from tunebot.redis import GlobalRedisPlaylist
|
||||
from tunebot.redis import GlobalRedisPlaylistSource
|
||||
from tunebot.redis import GlobalRedisUtils
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from tunebot import PluginManagerBase
|
||||
from tunebot import GlobalPlaylist
|
||||
from tunebot import GlobalPlaylistSource
|
||||
from tunebot import GlobalAutoJoin
|
||||
from tunebot import PluginLoaderBase
|
||||
from tunebot import PluginManagerBase
|
||||
from tunebot import GlobalUtils
|
||||
|
||||
ColorDict = dict[str, "Color"]
|
||||
|
||||
|
||||
class TuneBot(commands.Bot):
|
||||
lavalink: lavalink.Client
|
||||
invite_link: str
|
||||
invite_link: str = ""
|
||||
initial_cog_names: list[str]
|
||||
colors: ColorDict
|
||||
|
||||
global_autojoin: "GlobalAutoJoin"
|
||||
global_playlist: "GlobalPlaylist"
|
||||
global_playlist_source: "GlobalPlaylistSource"
|
||||
global_utils: "GlobalUtils"
|
||||
|
||||
plugin_loader: "PluginLoaderBase"
|
||||
plugin_manager: "PluginManagerBase"
|
||||
|
||||
def __init__(self, config: Dict[Any, Any]):
|
||||
intents = discord.Intents(
|
||||
voice_states=True, guild_messages=True, guilds=True, messages=True
|
||||
voice_states=True,
|
||||
guild_messages=True,
|
||||
guilds=True,
|
||||
messages=True,
|
||||
members=True,
|
||||
)
|
||||
|
||||
self.rpc_is_help_message = True
|
||||
self.update_status.start()
|
||||
|
||||
self.config = config
|
||||
self.initial_cog_names: List[str] = self.config.get("cogs", [])
|
||||
self.colors: Dict[str, Color] = self.process_colours(config.get("colors", []))
|
||||
|
||||
self.initial_cog_names = self.config.get("cogs", [])
|
||||
self.colors = self.process_colours(config.get("colors", []))
|
||||
|
||||
self.redis_prefix = self.config["redis_prefix"]
|
||||
self._redis_client: Redis = aioredis.from_url(
|
||||
self.config["redis_url"], encoding="utf-8", decode_responses=True
|
||||
)
|
||||
|
||||
self.global_autojoin: GlobalAutoJoin = GlobalRedisAutoJoin(
|
||||
self._redis_client,
|
||||
self.redis_prefix,
|
||||
self.global_autojoin = GlobalRedisAutoJoin(
|
||||
self._redis_client, self.redis_prefix
|
||||
)
|
||||
self.global_playlist: GlobalPlaylist = GlobalRedisPlaylist(
|
||||
self._redis_client,
|
||||
self.redis_prefix,
|
||||
self.global_playlist = GlobalRedisPlaylist(
|
||||
self._redis_client, self.redis_prefix
|
||||
)
|
||||
self.global_playlist_source: GlobalPlaylistSource = GlobalRedisPlaylistSource(
|
||||
self._redis_client,
|
||||
self.redis_prefix,
|
||||
self.global_playlist_source = GlobalRedisPlaylistSource(
|
||||
self._redis_client, self.redis_prefix
|
||||
)
|
||||
self.global_utils = GlobalRedisUtils(self._redis_client, self.redis_prefix)
|
||||
|
||||
self.invite_link: str = ""
|
||||
self.plugin_loader = FileSystemPluginLoader(self)
|
||||
self.plugin_manager = SimplePluginManager(self)
|
||||
|
||||
slash_guilds = None
|
||||
if len(self.config["slash_command_guilds"]) > 0:
|
||||
@@ -86,23 +109,24 @@ class TuneBot(commands.Bot):
|
||||
self.loop.create_task(self.async_init())
|
||||
|
||||
async def async_init(self):
|
||||
await self.load_cogs(self.initial_cog_names)
|
||||
self.init_plugins()
|
||||
self.load_cogs(self.initial_cog_names)
|
||||
|
||||
async def prefix_callable(self, _, msg: Message) -> List[str]:
|
||||
return commands.when_mentioned_or(*self.config["prefixes"])(self, msg)
|
||||
|
||||
async def load_cogs(self, cog_names: Sequence[str]):
|
||||
def load_cogs(self, cog_names: Sequence[str]):
|
||||
for cog in cog_names:
|
||||
try:
|
||||
self.load_extension(cog)
|
||||
print(f"Succesfully loaded extension {cog}.")
|
||||
print(f"[✓] loaded extension: {cog}.")
|
||||
except (
|
||||
ExtensionNotFound,
|
||||
ExtensionAlreadyLoaded,
|
||||
NoEntryPointError,
|
||||
ExtensionFailed,
|
||||
) as e:
|
||||
print(f"Failed to load extension {cog}.\n\t{e}", file=sys.stderr)
|
||||
print(f"[x] failed loading extension: {cog}.\n\t{e}", file=sys.stderr)
|
||||
|
||||
async def on_ready(self):
|
||||
self.invite_link = f"https://discord.com/oauth2/authorize?client_id={self.user.id}&permissions=3230720&scope=bot%20applications.commands"
|
||||
@@ -117,7 +141,7 @@ class TuneBot(commands.Bot):
|
||||
ll["host"], ll["port"], ll["password"], ll["region"], ll["name"]
|
||||
)
|
||||
|
||||
def process_colours(self, colors: Dict[str, str]) -> Dict[str, Color]:
|
||||
def process_colours(self, colors: Dict[str, str]) -> ColorDict:
|
||||
colour_dict: Dict[str, Color] = {}
|
||||
for name, color in colors.items():
|
||||
colour_dict[name] = Color(int(color, 16))
|
||||
@@ -126,6 +150,19 @@ class TuneBot(commands.Bot):
|
||||
async def get_context(self, message: Message, *, cls=CustomContext):
|
||||
return await super().get_context(message, cls=cls)
|
||||
|
||||
def init_plugins(self):
|
||||
for plug_id, plug_conf in self.config["plugins"].items():
|
||||
if not plug_conf.get("enabled"):
|
||||
continue
|
||||
|
||||
try:
|
||||
plugin = self.plugin_loader.load_plugin(plug_conf)
|
||||
self.plugin_manager.enable_plugin(plug_id, plugin)
|
||||
print(f"[✓] loaded plugin: {plug_id}")
|
||||
except Exception as e:
|
||||
self.plugin_manager.remove_plugin(plug_id)
|
||||
print(f"[x] failed loading plugin: {plug_id}\n{e}")
|
||||
|
||||
@tasks.loop(seconds=30)
|
||||
async def update_status(self):
|
||||
await self.wait_until_ready()
|
||||
@@ -141,13 +178,6 @@ class TuneBot(commands.Bot):
|
||||
await self.change_presence(activity=activity)
|
||||
|
||||
|
||||
config_path = "config.json"
|
||||
if len(sys.argv) > 1:
|
||||
config_path = sys.argv[1]
|
||||
|
||||
config = json.load(open(config_path, "r", encoding="utf-8"))
|
||||
redis_prefix = config["redis_prefix"]
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
import uvloop
|
||||
@@ -157,5 +187,10 @@ if __name__ == "__main__":
|
||||
except ModuleNotFoundError:
|
||||
pass
|
||||
|
||||
config_path = "config.json"
|
||||
if len(sys.argv) > 1:
|
||||
config_path = sys.argv[1]
|
||||
|
||||
config = json.load(open(config_path, "r", encoding="utf-8"))
|
||||
token = config.pop("token")
|
||||
TuneBot(config).run(token, reconnect=True)
|
||||
|
||||
+30
-2
@@ -1,24 +1,29 @@
|
||||
import asyncio
|
||||
import datetime
|
||||
import re
|
||||
from typing import Any
|
||||
from typing import Optional
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import discord
|
||||
import lavalink
|
||||
from discord import Embed
|
||||
from discord.channel import TextChannel
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands.context import Context
|
||||
from discord.ext.commands.errors import CommandError
|
||||
from lavalink.models import AudioTrack
|
||||
from lavalink.models import DefaultPlayer
|
||||
|
||||
from bot import TuneBot
|
||||
from context import CustomContext
|
||||
from tunebot.plugins import ServiceEvent
|
||||
from utils.classes import BaseCog
|
||||
from utils.EmbedGenerator import EmbedGenerator
|
||||
from utils.exceptions import EmbeddedCommandException
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from discord import VoiceChannel
|
||||
|
||||
url_rx = re.compile(r"https?://(?:www\.)?.+")
|
||||
|
||||
|
||||
@@ -217,13 +222,36 @@ class Music(BaseCog):
|
||||
guild = self.bot.get_guild(guild_id)
|
||||
await guild.voice_client.disconnect(force=True)
|
||||
elif isinstance(event, lavalink.events.TrackStartEvent):
|
||||
channel_id = int(event.player.fetch("channel"))
|
||||
if channel := event.player.fetch("channel"):
|
||||
channel_id = int(channel)
|
||||
channel: TextChannel = self.bot.get_channel(channel_id)
|
||||
embed = await self.create_track_embed(event.player.current)
|
||||
await channel.send(embed=embed)
|
||||
elif isinstance(event, lavalink.events.TrackEndEvent):
|
||||
await self.fill_player_queue(event.player, 1)
|
||||
|
||||
if event.reason == "FINISHED":
|
||||
if event.player.channel_id:
|
||||
channel_id = int(event.player.channel_id)
|
||||
voice_channel: "VoiceChannel" = self.bot.get_channel(channel_id)
|
||||
in_voice: list[int] = []
|
||||
|
||||
for member in voice_channel.members:
|
||||
if member.bot:
|
||||
continue
|
||||
|
||||
in_voice.append(member.id)
|
||||
|
||||
payload: dict[str, Any] = {
|
||||
"last_track": event.track,
|
||||
"in_voice": in_voice,
|
||||
}
|
||||
s = ServiceEvent.TRACK_ENDED
|
||||
await self.bot.plugin_manager.dispatch(s, payload)
|
||||
else:
|
||||
fmt = f"Failed dispatching for TrackEnd event, missing channel_id on player"
|
||||
print(fmt)
|
||||
|
||||
@commands.command(name="connect", aliases=["p", "play", "join"])
|
||||
async def play(self, ctx: CustomContext):
|
||||
"""Start the radio"""
|
||||
|
||||
+14
-1
@@ -22,5 +22,18 @@
|
||||
"cogs": ["cogs.owner", "cogs.settings", "cogs.information", "cogs.music"],
|
||||
"slash_command_guilds": [],
|
||||
"queue_buffer_size": 5,
|
||||
"slash_descriptions": {}
|
||||
"slash_descriptions": {},
|
||||
"plugins": {
|
||||
"lastfm-scrobbler": {
|
||||
"services": ["plugins.lastfm_scrobbler.service"],
|
||||
"cogs": ["plugins.lastfm_scrobbler.cog"],
|
||||
"config": {
|
||||
"lastfm_api_key": "",
|
||||
"lastfm_api_secret": "",
|
||||
"session_key_table": "",
|
||||
"auth_url": ""
|
||||
},
|
||||
"enabled": false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from typing import Any
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from aioredis.client import Redis
|
||||
@@ -14,12 +15,18 @@ if TYPE_CHECKING:
|
||||
from tunebot import PlaylistSource
|
||||
from tunebot import AutoJoin
|
||||
from utils.classes import BaseCog
|
||||
from tunebot.plugins import ServiceEvent
|
||||
|
||||
AnyDict = dict[Any, Any]
|
||||
|
||||
|
||||
class CustomContext(commands.Context):
|
||||
bot: "TuneBot"
|
||||
cog: "BaseCog"
|
||||
|
||||
async def dispatch(self, event: "ServiceEvent", payload: AnyDict):
|
||||
await self.bot.plugin_manager.dispatch(event, payload)
|
||||
|
||||
@property
|
||||
def redis(self) -> Redis:
|
||||
return self.bot._redis_client
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from discord.ext import commands
|
||||
|
||||
from context import CustomContext
|
||||
from utils.classes import PluginCog
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bot import TuneBot
|
||||
|
||||
|
||||
class LastFMScrobblerCog(PluginCog, name="LastFMScrobbler"):
|
||||
def __init__(self, bot: "TuneBot") -> None:
|
||||
super().__init__(bot)
|
||||
self.plugin = self.get_plugin_instance("lastfm-scrobbler")
|
||||
|
||||
@commands.group(name="lfm", invoke_without_command=True)
|
||||
@commands.cooldown(rate=1, per=5, type=commands.BucketType.user)
|
||||
async def lfm(self, ctx: CustomContext):
|
||||
"""Enable/Disable LastFM Scrobbling"""
|
||||
embed = ctx.create_embed()
|
||||
embed.title = f"LastFM scrobbler usage:"
|
||||
embed.description = f"```{ctx.prefix}lfm enable\n{ctx.prefix}lfm del```"
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@lfm.command(name="enable", aliases=["set"])
|
||||
async def lfm_aut(self, ctx: CustomContext):
|
||||
"""Enable LastFM Scrobbling"""
|
||||
embed = ctx.create_embed()
|
||||
embed.title = f"Start scrobbling"
|
||||
auth_url = self.plugin.config["auth_url"] + "?user_id=" + str(ctx.author.id)
|
||||
embed.description = f"[Login]({auth_url})"
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@lfm.command(name="delete", aliases=["del"])
|
||||
async def lfm_del(self, ctx: CustomContext):
|
||||
"""Disable LastFM Scrobbling"""
|
||||
db_key = self.plugin.config["session_key_table"]
|
||||
await self.bot.global_utils.raw_table_del_entry(db_key, [ctx.author.id])
|
||||
embed = ctx.create_embed()
|
||||
embed.title = f"Stopped scrobbling"
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
|
||||
def setup(bot: "TuneBot"):
|
||||
bot.add_cog(LastFMScrobblerCog(bot))
|
||||
@@ -0,0 +1,84 @@
|
||||
import hashlib
|
||||
import time
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import aiohttp
|
||||
from lavalink.models import AudioTrack
|
||||
|
||||
from tunebot.plugins import ServiceEvent
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bot import TuneBot
|
||||
|
||||
AnyDict = dict[Any, Any]
|
||||
|
||||
|
||||
@dataclass
|
||||
class Track:
|
||||
name: str
|
||||
artist: str
|
||||
|
||||
|
||||
class LastFMScrobbler:
|
||||
api_url = "http://ws.audioscrobbler.com/2.0/"
|
||||
|
||||
def __init__(self, bot: "TuneBot", config: AnyDict) -> None:
|
||||
self.bot = bot
|
||||
self.plug_conf = config
|
||||
|
||||
async def on_dispatch(self, event: "ServiceEvent", payload: AnyDict):
|
||||
if event == ServiceEvent.TRACK_ENDED:
|
||||
_track: AudioTrack = payload.get("last_track", None)
|
||||
if not _track:
|
||||
print("LastFMScrobbler: Could not find required field `last_track`")
|
||||
return
|
||||
|
||||
# TODO: expand parsing options
|
||||
artist, track_name = _track.title.split(" - ", 1)
|
||||
track = Track(track_name, artist)
|
||||
|
||||
in_voice = payload.get("in_voice", [])
|
||||
for session_key in await self.fetch_lastfm_sessions(in_voice):
|
||||
if not session_key:
|
||||
continue
|
||||
|
||||
await self.scrobble(session_key, track)
|
||||
|
||||
async def fetch_lastfm_sessions(self, user_ids: list[int]) -> list[str]:
|
||||
table_name = self.plug_conf["config"]["session_key_table"]
|
||||
result: list[str] = await self.bot.global_utils.raw_table_lookup(
|
||||
table_name, user_ids
|
||||
)
|
||||
return result
|
||||
|
||||
async def scrobble(self, session_key: str, track: Track):
|
||||
params: AnyDict = {
|
||||
"method": "track.scrobble",
|
||||
"timestamp": str(int(time.time() - 30)),
|
||||
"track": track.name,
|
||||
"artist": track.artist,
|
||||
"sk": session_key,
|
||||
}
|
||||
resp = await self.lastfm_request(params)
|
||||
if resp.status != 200:
|
||||
fmt = f"Failed to scrobble for user {session_key} on track: {track.artist} - {track.name}"
|
||||
print(fmt)
|
||||
|
||||
async def lastfm_request(self, params: AnyDict) -> aiohttp.ClientResponse:
|
||||
params["api_key"] = self.plug_conf["config"]["lastfm_api_key"]
|
||||
params = {key: params[key] for key in sorted(params)}
|
||||
|
||||
secret = self.plug_conf["config"]["lastfm_api_secret"]
|
||||
sig_str = "".join(key + params[key] for key in params.keys()) + secret
|
||||
params["api_sig"] = hashlib.md5(sig_str.encode("utf8")).hexdigest()
|
||||
|
||||
params["format"] = "json"
|
||||
async with aiohttp.ClientSession() as sess:
|
||||
async with sess.post(self.api_url, params=params) as resp:
|
||||
return resp
|
||||
|
||||
|
||||
def setup(bot: "TuneBot", config: AnyDict):
|
||||
return LastFMScrobbler(bot, config)
|
||||
@@ -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)
|
||||
+13
-3
@@ -1,13 +1,15 @@
|
||||
import asyncio
|
||||
from typing import Dict
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from discord.ext.commands import Cog
|
||||
|
||||
from bot import TuneBot
|
||||
if TYPE_CHECKING:
|
||||
from tunebot import BasePluginInstance
|
||||
from bot import TuneBot
|
||||
|
||||
|
||||
class BaseCog(Cog):
|
||||
def __init__(self, bot: TuneBot) -> None:
|
||||
def __init__(self, bot: "TuneBot") -> None:
|
||||
self.bot = bot
|
||||
|
||||
slash_descriptions: Dict[str, str] = self.bot.config["slash_descriptions"]
|
||||
@@ -20,3 +22,11 @@ class BaseCog(Cog):
|
||||
hasattr(self.bot, "lavalink")
|
||||
and len(self.bot.lavalink.node_manager.available_nodes) > 0
|
||||
)
|
||||
|
||||
|
||||
class PluginCog(BaseCog):
|
||||
def get_plugin_instance(self, name: str) -> "BasePluginInstance":
|
||||
if plugin := self.bot.plugin_manager.get_plugin(name):
|
||||
return plugin
|
||||
|
||||
raise KeyError(f"Failed to retrieve plugin instance with name: {name}")
|
||||
|
||||
Reference in New Issue
Block a user