20 Commits
Author SHA1 Message Date
niku b702297b40 Merge pull request #41 from Matthww/dev
Release v1.1.0
2021-11-22 21:15:14 +01:00
matthew 76bfd719c3 Merge pull request #40 from strNophix/source-manager-setting
Source manager decorator
2021-11-22 21:10:12 +01:00
niku c45bd8ced3 Added decorator for checking if eligable for managing sources 2021-11-22 18:28:48 +01:00
niku b37c3f533a Merge pull request #39 from strNophix/remove-duplicate-commands
Removed duplicate commands
2021-11-22 11:15:55 +01:00
niku 9c55bcdc36 Removed duplicate commands 2021-11-22 11:11:17 +01:00
niku 1495d214a1 Merge pull request #37 from strNophix/database-rewrite
database rewrite + autojoin
2021-11-22 10:53:23 +01:00
niku 4f65522bd8 Merge branch 'dev' into database-rewrite 2021-11-22 10:53:00 +01:00
niku 3e6ee9ecc9 Merge pull request #38 from strNophix/decoupling-lavalink
decoupled lavalink creation from cogs.music
2021-11-21 20:54:52 +01:00
niku 8504d6abfd decoupled lavalink creation from cogs.music 2021-11-20 00:37:17 +01:00
niku d2ecbbf6ae prevent circular import in tunebot.redis.__init__.py 2021-11-19 21:58:47 +01:00
niku adb3fe11a6 Merge remote-tracking branch 'upstream/dev' into database-rewrite 2021-11-19 21:39:10 +01:00
niku ebb07960bf database rewrite 2021-11-19 21:10:29 +01:00
matthew 64770dbeb5 Merge pull request #36 from Matthww/dev
Release 1.0.4
2021-11-19 19:34:52 +01:00
matthew 1697002666 Merge pull request #35 from strNophix/source-management
Source management
2021-11-19 19:34:24 +01:00
niku 60f0bd9070 Made source commandgroup hidden 2021-11-18 23:03:19 +01:00
niku b8dc8b2b20 Added owner checks for source command 2021-11-18 23:01:04 +01:00
niku f1133367d2 Merge pull request #34 from Matthww/dev
Ciritical fix with unsupplied owner_ids
2021-11-18 22:22:19 +01:00
niku 998749f1ef Merge pull request #33 from strNophix/owner-id-fix
Critical bug with unsupplied owner_ids
2021-11-18 22:21:28 +01:00
niku 12eb3a70e1 Critical fix with owner_id 2021-11-18 22:17:12 +01:00
niku 316f3bacc9 Added sources functionality + pre-commit 2021-11-18 21:59:37 +01:00
21 changed files with 487 additions and 95 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
default_language_version: default_language_version:
python: python3.8 python: python3.9
repos: repos:
- repo: https://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.0.1 rev: v4.0.1
+31
View File
@@ -4,6 +4,7 @@ from typing import Any
from typing import Dict from typing import Dict
from typing import List from typing import List
from typing import Sequence from typing import Sequence
from typing import TYPE_CHECKING
import aioredis import aioredis
import discord import discord
@@ -21,6 +22,14 @@ from discord.ext.commands.errors import ExtensionNotFound
from discord.ext.commands.errors import NoEntryPointError from discord.ext.commands.errors import NoEntryPointError
from context import CustomContext from context import CustomContext
from tunebot.redis import GlobalRedisAutoJoin
from tunebot.redis import GlobalRedisPlaylist
from tunebot.redis import GlobalRedisPlaylistSource
if TYPE_CHECKING:
from tunebot import GlobalPlaylist
from tunebot import GlobalPlaylistSource
from tunebot import GlobalAutoJoin
class TuneBot(commands.Bot): class TuneBot(commands.Bot):
@@ -39,9 +48,24 @@ class TuneBot(commands.Bot):
self.initial_cog_names: List[str] = self.config.get("cogs", []) self.initial_cog_names: List[str] = self.config.get("cogs", [])
self.colors: Dict[str, Color] = self.process_colours(config.get("colors", [])) self.colors: Dict[str, Color] = self.process_colours(config.get("colors", []))
self.redis_prefix = self.config["redis_prefix"]
self._redis_client: Redis = aioredis.from_url( self._redis_client: Redis = aioredis.from_url(
self.config["redis_url"], encoding="utf-8", decode_responses=True self.config["redis_url"], encoding="utf-8", decode_responses=True
) )
self.global_autojoin: GlobalAutoJoin = GlobalRedisAutoJoin(
self._redis_client,
self.redis_prefix,
)
self.global_playlist: GlobalPlaylist = GlobalRedisPlaylist(
self._redis_client,
self.redis_prefix,
)
self.global_playlist_source: GlobalPlaylistSource = GlobalRedisPlaylistSource(
self._redis_client,
self.redis_prefix,
)
self.invite_link: str = "" self.invite_link: str = ""
slash_guilds = None slash_guilds = None
@@ -50,6 +74,7 @@ class TuneBot(commands.Bot):
super().__init__( super().__init__(
command_prefix=self.prefix_callable, command_prefix=self.prefix_callable,
owner_ids=self.config["owner_ids"],
description=self.config["info"]["description"], description=self.config["info"]["description"],
case_insensitive=False, case_insensitive=False,
fetch_offline_members=False, fetch_offline_members=False,
@@ -86,6 +111,12 @@ class TuneBot(commands.Bot):
print(f"Version: {discord.__version__}") print(f"Version: {discord.__version__}")
print(f"Invite: {self.invite_link}") print(f"Invite: {self.invite_link}")
ll = self.config["lavalink"]
self.lavalink = lavalink.Client(self.user.id)
self.lavalink.add_node(
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]) -> Dict[str, Color]:
colour_dict: Dict[str, Color] = {} colour_dict: Dict[str, Color] = {}
for name, color in colors.items(): for name, color in colors.items():
+3 -6
View File
@@ -6,14 +6,12 @@ import discord
import humanize import humanize
import lavalink import lavalink
from discord.ext import commands from discord.ext import commands
from discord.ext import tasks
from discord.ext.commands import Context from discord.ext.commands import Context
from bot import TuneBot from bot import TuneBot
from context import CustomContext from context import CustomContext
from utils.database import AutoJoin
from utils.EmbedGenerator import EmbedGenerator
from utils.classes import BaseCog from utils.classes import BaseCog
from utils.EmbedGenerator import EmbedGenerator
from utils.paginator import HelpPaginator from utils.paginator import HelpPaginator
@@ -63,16 +61,15 @@ class InformationCog(BaseCog, name="Information"):
fmt = ( fmt = (
f"**Lavalink:** `{lavalink.__version__}`\n\n" f"**Lavalink:** `{lavalink.__version__}`\n\n"
f"Connected to `{len(self.bot.lavalink.node_manager.available_nodes)}` nodes.\n" f"Connected to `{len(self.bot.lavalink.node_manager.available_nodes)}` nodes.\n"
#f"Best available Node `{self.bot.lavalink.node_manager.find_ideal_node().name.__repr__()}`\n" # f"Best available Node `{self.bot.lavalink.node_manager.find_ideal_node().name.__repr__()}`\n"
f"`{len(self.bot.lavalink.player_manager.players)}` players are distributed on nodes.\n" f"`{len(self.bot.lavalink.player_manager.players)}` players are distributed on nodes.\n"
f"`{sum([n.stats.players for n in nodes])}` players are distributed on server.\n" f"`{sum([n.stats.players for n in nodes])}` players are distributed on server.\n"
f"`{sum([n.stats.playing_players for n in nodes])}` players are playing on server.\n\n" f"`{sum([n.stats.playing_players for n in nodes])}` players are playing on server.\n\n"
f"Server Memory: `{used}/{total}` | `({free} free)`\n" f"Server Memory: `{used}/{total}` | `({free} free)`\n"
f"Server CPU: `{cpu}`\n\n" f"Server CPU: `{cpu}`\n\n"
#f"Server Uptime: `{datetime.timedelta(milliseconds=node.stats.uptime)}`" # f"Server Uptime: `{datetime.timedelta(milliseconds=node.stats.uptime)}`"
) )
await ctx.send(fmt) await ctx.send(fmt)
AutoJoin.get_channels()
@commands.command(name="help", aliases=["about", "info"]) @commands.command(name="help", aliases=["about", "info"])
@commands.cooldown(1, 1, commands.BucketType.user) @commands.cooldown(1, 1, commands.BucketType.user)
+8 -26
View File
@@ -4,24 +4,20 @@ import re
from typing import Optional from typing import Optional
import discord import discord
from discord.channel import TextChannel
from discord.ext.commands.context import Context
from discord.ext.commands.errors import CommandError
import lavalink import lavalink
from discord import Embed from discord import Embed
from discord.channel import TextChannel from discord.channel import TextChannel
from discord.ext import commands from discord.ext import commands
from discord.ext.commands.context import Context from discord.ext.commands.context import Context
from discord.ext.commands.errors import CommandError
from lavalink.models import AudioTrack from lavalink.models import AudioTrack
from lavalink.models import DefaultPlayer from lavalink.models import DefaultPlayer
from bot import TuneBot from bot import TuneBot
from utils.classes import BaseCog
from context import CustomContext from context import CustomContext
from utils.database import AutoJoin from utils.classes import BaseCog
from utils.database import Playlist
from utils.exceptions import EmbeddedCommandException
from utils.EmbedGenerator import EmbedGenerator from utils.EmbedGenerator import EmbedGenerator
from utils.exceptions import EmbeddedCommandException
url_rx = re.compile(r"https?://(?:www\.)?.+") url_rx = re.compile(r"https?://(?:www\.)?.+")
@@ -78,25 +74,11 @@ class LavalinkVoiceClient(discord.VoiceClient):
class Music(BaseCog): class Music(BaseCog):
@commands.Cog.listener() @commands.Cog.listener()
async def on_ready(self): async def on_ready(self):
if not hasattr( while not self.is_lavalink_ready():
self.bot, "lavalink"
): # This ensures the client isn't overwritten during cog reloads.
self.bot.lavalink = lavalink.Client(self.bot.user.id)
ll = self.bot.config["lavalink"]
self.bot.lavalink.add_node(
ll["host"], ll["port"], ll["password"], ll["region"], ll["name"]
)
self.bot.lavalink.add_event_hook(self.track_hook)
await self.async_init()
async def async_init(self):
redis_result = await AutoJoin.get_channels(self.bot._redis_client)
while len(self.bot.lavalink.node_manager.available_nodes) == 0:
await asyncio.sleep(1) await asyncio.sleep(1)
self.bot.lavalink.add_event_hook(self.track_hook)
redis_result = await self.bot.global_autojoin.fetch_channels()
for guild_id, (voicechannel_id, textchannel_id) in redis_result.items(): for guild_id, (voicechannel_id, textchannel_id) in redis_result.items():
player = self.bot.lavalink.player_manager.create(guild_id) player = self.bot.lavalink.player_manager.create(guild_id)
player.store("channel", textchannel_id) player.store("channel", textchannel_id)
@@ -112,7 +94,7 @@ class Music(BaseCog):
await textchannel.send("Automatically joined the voice channel") await textchannel.send("Automatically joined the voice channel")
async def fill_player_queue(self, player: DefaultPlayer, buffer: Optional[int] = 1): async def fill_player_queue(self, player: DefaultPlayer, buffer: Optional[int] = 1):
queries = await Playlist.random(self.bot._redis_client, buffer) queries = await self.bot.global_playlist.pick_random(buffer)
# Get the results for the query from Lavalink. # Get the results for the query from Lavalink.
for query in queries: for query in queries:
result = await player.node.get_tracks(query) result = await player.node.get_tracks(query)
@@ -155,7 +137,7 @@ class Music(BaseCog):
# This is essentially the same as `@commands.guild_only()` # This is essentially the same as `@commands.guild_only()`
# except it saves us repeating ourselves (and also a few lines). # except it saves us repeating ourselves (and also a few lines).
if not hasattr(self.bot, "lavalink"): if not self.is_lavalink_ready():
await ctx.send("Still starting please wait a moment.") await ctx.send("Still starting please wait a moment.")
if guild_check: if guild_check:
+134 -17
View File
@@ -1,13 +1,12 @@
from typing import Any
from discord.ext import commands from discord.ext import commands
from utils.EmbedGenerator import EmbedGenerator from discord.message import Message
from utils.classes import BaseCog
from utils.database import AutoJoin
from bot import TuneBot
from discord.ext.commands import Context
from bot import TuneBot from bot import TuneBot
from context import CustomContext from context import CustomContext
from utils.database import AutoJoin from utils.classes import BaseCog
from utils.decorators import source_manager_only
from utils.EmbedGenerator import EmbedGenerator from utils.EmbedGenerator import EmbedGenerator
@@ -19,28 +18,146 @@ class SettingsCog(BaseCog, name="Settings"):
await EmbedGenerator.Message( await EmbedGenerator.Message(
ctx, ctx,
"Autojoin", "Autojoin",
f"Usage:\n\n`{ctx.prefix}autojoin set`\n`{ctx.prefix}autojoin unset`", f"Usage:\n\n`{ctx.prefix}autojoin enable`\n`{ctx.prefix}autojoin disable`",
) )
@autojoin.command(name="enable") @autojoin.command(name="enable", aliases=["set"])
@commands.has_permissions(manage_channels=True) @commands.has_permissions(manage_channels=True)
@commands.cooldown(rate=1, per=5, type=commands.BucketType.user) @commands.cooldown(rate=1, per=5, type=commands.BucketType.user)
async def autojoin_set(self, ctx: CustomContext): async def autojoin_set(self, ctx: CustomContext):
"""Enable the bot automatically joining""" """Enable the bot automatically joining"""
voicechannel_id = ctx.author.voice.channel.id voice_state = ctx.author.voice
textchannel_id = ctx.message.channel.id if not voice_state:
await AutoJoin.update_channel( embed = ctx.create_embed()
ctx.redis, ctx.guild.id, voicechannel_id, textchannel_id embed.title = "Please join a voice channel before running this command."
) await ctx.send(embed=embed)
await EmbedGenerator.Message(ctx, "Autojoin", "`enabled`") return
@autojoin.command(name="disable") await ctx.autojoin.update(voice_state.channel.id, ctx.message.channel.id)
embed = ctx.create_embed()
embed.title = f"AutoJoin enabled for #{voice_state.channel.name}"
await ctx.send(embed=embed)
@autojoin.command(name="disable", aliases=["unset"])
@commands.has_permissions(manage_channels=True) @commands.has_permissions(manage_channels=True)
@commands.cooldown(rate=1, per=5, type=commands.BucketType.user) @commands.cooldown(rate=1, per=5, type=commands.BucketType.user)
async def autojoin_del(self, ctx: CustomContext): async def autojoin_del(self, ctx: CustomContext):
"""Disable the bot automatically joining""" """Disable the bot automatically joining"""
await AutoJoin.del_channel(ctx.redis, ctx.guild.id) await ctx.autojoin.disable()
await EmbedGenerator.Message(ctx, "Autojoin", "`disabled`") embed = ctx.create_embed()
embed.title = f"AutoJoin disabled"
await ctx.send(embed=embed)
@source_manager_only()
@commands.group(
name="source",
aliases=["src"],
invoke_without_command=True,
slash_command=False,
hidden=True,
)
async def source(self, ctx: CustomContext):
"""Displays all possible options for the `source` command"""
prefix = self.bot.config["prefixes"][0]
embed = ctx.create_embed()
embed.title = "All options:"
embed.description = f"```{prefix}source list\n{prefix}source add <url>\n{prefix}source remove <url>\n{prefix}source sync```"
await ctx.send(embed=embed)
@source_manager_only()
@source.command(name="remove")
async def source_remove(self, ctx: CustomContext, source_url: str):
"""Removes a source from the bot"""
if await ctx.playlist_source.remove(source_url):
prefix = self.bot.config["prefixes"][0]
embed = ctx.create_embed()
embed.title = "Removed source succesfully"
embed.description = (
f"Please use `{prefix}source sync` to persist these changes."
)
await ctx.send(embed=embed)
return
embed = ctx.create_embed()
embed.title = "Could not remove source, the specified source might not exist"
await ctx.send(embed=embed)
@source_manager_only()
@source.command(name="add")
async def source_add(self, ctx: CustomContext, source_url: str):
"""
Add's a source to the bot
Supported sources: YouTube, SoundCloud, Bandcamp, Vimeo, Twitch and HTTP(S) URL's
"""
embed = ctx.create_embed()
embed.title = "Started processing source"
message = await ctx.send(embed=embed)
query_result: Any = await self.bot.lavalink.get_tracks(source_url)
if query_result["loadType"] == "LOAD_FAILED":
embed = ctx.create_embed()
embed.title = "The specified URL is not a valid source"
embed.description = f"Supported sources: YouTube, SoundCloud, Bandcamp, Vimeo, Twitch and HTTP(S) URL's"
if isinstance(message, Message):
await message.edit(embed=embed)
else:
await ctx.send(embed=embed)
return
await ctx.playlist_source.add(source_url)
track_urls = [str(track["info"]["uri"]) for track in query_result["tracks"]]
await self.bot.global_playlist.add_tracks(track_urls)
embed = ctx.create_embed()
embed.title = "Finished processing source"
embed.description = f"Added {len(track_urls)} tracks"
if isinstance(message, Message):
await message.edit(embed=embed)
else:
await ctx.send(embed=embed)
@source_manager_only()
@source.command(name="list", aliases=["ls"])
async def source_list(self, ctx: CustomContext):
"""Display a list of sources"""
# TODO: Implement pagination for sources
sources = await self.bot.global_playlist_source.fetch_sources()
if len(sources) > 0:
description = "\n".join([f"[{source}]({source})" for source in sources])
else:
description = "This bot has no sources yet"
embed = ctx.create_embed()
embed.title = "All sources:"
embed.description = description
await ctx.send(embed=embed)
@source_manager_only()
@source.command(name="sync")
async def source_sync(self, ctx: CustomContext):
"""Forcefully resyncs all sources"""
failed_sources: list[str] = []
await self.bot.global_playlist.clear()
sources = await self.bot.global_playlist_source.fetch_sources()
for source_url in sources:
query_result: Any = await self.bot.lavalink.get_tracks(source_url)
if query_result["loadType"] == "LOAD_FAILED":
failed_sources.append(source_url)
continue
track_urls = [str(track["info"]["uri"]) for track in query_result["tracks"]]
await self.bot.global_playlist.add_tracks(track_urls)
embed = ctx.create_embed()
embed.title = f"Finished sync ({len(failed_sources)} issues)"
if len(failed_sources) > 0:
embed.description = "\n".join(
[f"[{source_url}]({source_url})" for source_url in failed_sources]
)
await ctx.send(embed=embed)
def setup(bot: TuneBot): def setup(bot: TuneBot):
+1
View File
@@ -1,6 +1,7 @@
{ {
"token": "", "token": "",
"owner_ids": [194545408960102400, 190875175460405249], "owner_ids": [194545408960102400, 190875175460405249],
"manager_ids": [],
"prefixes": ["ck!"], "prefixes": ["ck!"],
"redis_url": "", "redis_url": "",
"redis_prefix": "", "redis_prefix": "",
+40 -1
View File
@@ -1,17 +1,56 @@
from typing import TYPE_CHECKING
from aioredis.client import Redis from aioredis.client import Redis
from discord import Embed
from discord.ext import commands from discord.ext import commands
from discord.ext.commands.errors import CommandInvokeError from discord.ext.commands.errors import CommandInvokeError
from lavalink.models import DefaultPlayer from lavalink.models import DefaultPlayer
from tunebot.redis import RedisAutoJoin
from tunebot.redis import RedisPlaylistSource
if TYPE_CHECKING:
from bot import TuneBot
from tunebot import PlaylistSource
from tunebot import AutoJoin
from utils.classes import BaseCog
class CustomContext(commands.Context): class CustomContext(commands.Context):
bot: "TuneBot"
cog: "BaseCog"
@property @property
def redis(self) -> Redis: def redis(self) -> Redis:
return self.bot._redis_client return self.bot._redis_client
@property @property
def player(self) -> DefaultPlayer: def player(self) -> DefaultPlayer:
if hasattr(self.bot, "lavalink"): if self.cog.is_lavalink_ready():
return self.bot.lavalink.player_manager.get(self.guild.id) return self.bot.lavalink.player_manager.get(self.guild.id)
raise CommandInvokeError("Lavalink is still starting up.") raise CommandInvokeError("Lavalink is still starting up.")
def create_embed(self) -> Embed:
bot: TuneBot = self.bot
color = bot.colors["embed"]
avatar = None
if avatar_asset := self.author.avatar:
avatar = avatar_asset.with_static_format("jpeg")
embed = Embed(color=color)
embed.set_footer(text=f"Requested by: {self.author}", icon_url=avatar)
return embed
@property
def playlist_source(self) -> "PlaylistSource":
if not hasattr(self, "_playlist_source"):
self._playlist_source = RedisPlaylistSource(self)
return self._playlist_source
@property
def autojoin(self) -> "AutoJoin":
if not hasattr(self, "_autojoin"):
self._autojoin = RedisAutoJoin(self)
return self._autojoin
+3 -3
View File
@@ -1,8 +1,9 @@
import subprocess
import json import json
import subprocess
import sys
import redis import redis
from yt_dlp import YoutubeDL from yt_dlp import YoutubeDL
import sys
if len(sys.argv) < 2: if len(sys.argv) < 2:
raise Exception("Expected youtube playlist/channel/video") raise Exception("Expected youtube playlist/channel/video")
@@ -25,4 +26,3 @@ for vid_url in vid_urls.split("\n"):
vid_url = "https://www.youtube.com/watch?v=" + vid_url vid_url = "https://www.youtube.com/watch?v=" + vid_url
redis_client.sadd(f"{redis_prefix}:playlist", vid_url) redis_client.sadd(f"{redis_prefix}:playlist", vid_url)
print(vid_url) print(vid_url)
+4 -3
View File
@@ -1,8 +1,9 @@
import json import json
from aiotube import Playlist
import redis
import sys import sys
import redis
from aiotube import Playlist
if len(sys.argv) < 2: if len(sys.argv) < 2:
raise Exception("Expected path to file as argument") raise Exception("Expected path to file as argument")
@@ -18,4 +19,4 @@ for line in file:
for vid in playlist.videos(): for vid in playlist.videos():
yt_url = vid.url yt_url = vid.url
redis_client.sadd(f"{redis_prefix}:playlist", yt_url) redis_client.sadd(f"{redis_prefix}:playlist", yt_url)
print(yt_url) print(yt_url)
+1
View File
@@ -0,0 +1 @@
from tunebot.abc import *
+58
View File
@@ -0,0 +1,58 @@
from abc import ABC
from abc import abstractmethod
from typing import Optional
class GlobalPlaylistSource(ABC):
@abstractmethod
async def fetch_sources(self) -> set[str]:
pass
class PlaylistSource(ABC):
@abstractmethod
async def add(self, source_url: str):
pass
@abstractmethod
async def remove(self, source_url: str) -> bool:
pass
class GlobalPlaylist(ABC):
@abstractmethod
async def pick_random(self, amount: Optional[int] = 1) -> set[str]:
pass
@abstractmethod
async def add_tracks(self, urls: list[str]):
pass
@abstractmethod
async def clear(self):
pass
class GlobalAutoJoin(ABC):
@abstractmethod
async def fetch_channels(self) -> dict[str, list[str]]:
pass
class AutoJoin(ABC):
@abstractmethod
async def update(self, voice_channel_id: int, text_channel_id: int):
pass
@abstractmethod
async def disable(self):
pass
__all__ = (
"GlobalPlaylistSource",
"PlaylistSource",
"GlobalPlaylist",
"GlobalAutoJoin",
"AutoJoin",
)
+4
View File
@@ -0,0 +1,4 @@
from tunebot.redis.entity import * # noreorder
from tunebot.redis.autojoin import *
from tunebot.redis.playlist import *
from tunebot.redis.playlist_source import *
+41
View File
@@ -0,0 +1,41 @@
from tunebot import AutoJoin
from tunebot import GlobalAutoJoin
from tunebot.redis import RedisBotEntity
from tunebot.redis import RedisContextEntity
class GlobalRedisAutoJoin(RedisBotEntity, GlobalAutoJoin):
async def fetch_channels(self) -> dict[str, list[str]]:
"""
Retrieves all guilds (with their configurations) where AutoJoin is enabled
"""
channels: dict[str, str] = await self.redis.hgetall(self.key("autojoin"))
return {key: value.split(":") for key, value in channels.items()}
class RedisAutoJoin(RedisContextEntity, AutoJoin):
async def update(self, voice_channel_id: int, text_channel_id: int):
"""
Upserts the configuration of an AutoJoin guild.
Args:
voice_channel_id (int): [description]
text_channel_id (int): [description]
"""
if not self.ctx.guild:
raise Exception("This method can only be invoked inside of a guild.")
value = f"{voice_channel_id}:{text_channel_id}"
await self.redis.hset(self.key("autojoin"), self.ctx.guild.id, value)
async def disable(self):
"""
Removes the AutoJoin configuration of a guild.
"""
if not self.ctx.guild:
raise Exception("This method can only be invoked inside of a guild.")
await self.redis.hdel(self.key("autojoin"), self.ctx.guild.id)
__all__ = ("GlobalRedisAutoJoin", "RedisAutoJoin")
+49
View File
@@ -0,0 +1,49 @@
from abc import ABC
from abc import abstractmethod
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from context import CustomContext
from aioredis.client import Redis
class RedisEntity(ABC):
@property
@abstractmethod
def redis() -> Redis:
pass
@abstractmethod
def key(self, name: str) -> str:
pass
class RedisBotEntity(RedisEntity):
def __init__(self, redis: Redis, prefix: str) -> None:
self._redis = redis
self.prefix = prefix
super().__init__()
@property
def redis(self) -> Redis:
return self._redis
def key(self, name: str) -> str:
return ":".join([self.prefix, name])
class RedisContextEntity(RedisEntity):
def __init__(self, ctx: "CustomContext") -> None:
self.ctx = ctx
super().__init__()
@property
def redis(self) -> Redis:
return self.ctx.redis
def key(self, name: str) -> str:
return ":".join([self.ctx.bot.redis_prefix, name])
__all__ = ("RedisEntity", "RedisBotEntity", "RedisContextEntity")
+33
View File
@@ -0,0 +1,33 @@
from typing import Optional
from tunebot.abc import GlobalPlaylist
from tunebot.redis import RedisBotEntity
class GlobalRedisPlaylist(RedisBotEntity, GlobalPlaylist):
async def pick_random(self, amount: Optional[int] = 1) -> set[str]:
"""
Picks an amount of random tracks from the playlist in Redis
Args:
amount (Optional[int], optional): [description]. Defaults to 1.
"""
return await self.redis.srandmember(self.key("playlist"), amount)
async def add_tracks(self, urls: list[str]):
"""
Adds one or more urls to the playlist in Redis
Args:
urls (list[str]): [description]
"""
await self.redis.sadd(self.key("playlist"), *urls)
async def clear(self):
"""
Clears the entire playlist in Redis
"""
await self.redis.delete(self.key("playlist"))
__all__ = ("GlobalRedisPlaylist",)
+35
View File
@@ -0,0 +1,35 @@
from tunebot import GlobalPlaylistSource
from tunebot import PlaylistSource
from tunebot.redis import RedisBotEntity
from tunebot.redis import RedisContextEntity
class GlobalRedisPlaylistSource(RedisBotEntity, GlobalPlaylistSource):
async def fetch_sources(self) -> set[str]:
"""
Fetches all sources
"""
return await self.redis.smembers(self.key("sources"))
class RedisPlaylistSource(RedisContextEntity, PlaylistSource):
async def add(self, source_url: str):
"""
Adds a playlist source to Redis
Args:
source_url (str): [description]
"""
await self.redis.sadd(self.key("sources"), source_url)
async def remove(self, source_url: str) -> bool:
"""
Removes a playlist source from Redis
Args:
source_url (str): [description]
"""
return await self.redis.srem(self.key("sources"), source_url)
__all__ = ("GlobalRedisPlaylistSource", "RedisPlaylistSource")
+3 -2
View File
@@ -1,8 +1,9 @@
from typing import Optional, Union from typing import Optional
from typing import Union
import discord import discord
from discord.ext.commands import Context
from discord import Embed from discord import Embed
from discord.ext.commands import Context
class EmbedGenerator: class EmbedGenerator:
+9
View File
@@ -1,5 +1,8 @@
import asyncio
from typing import Dict from typing import Dict
from discord.ext.commands import Cog from discord.ext.commands import Cog
from bot import TuneBot from bot import TuneBot
@@ -11,3 +14,9 @@ class BaseCog(Cog):
for command in self.walk_commands(): for command in self.walk_commands():
if brief := slash_descriptions.get(command.qualified_name): if brief := slash_descriptions.get(command.qualified_name):
command.brief = brief command.brief = brief
def is_lavalink_ready(self) -> bool:
return (
hasattr(self.bot, "lavalink")
and len(self.bot.lavalink.node_manager.available_nodes) > 0
)
-35
View File
@@ -1,35 +0,0 @@
from typing import Dict
from typing import List
from typing import Optional
from aioredis import Redis
from bot import redis_prefix
class AutoJoin:
@staticmethod
async def get_channels(redis: Redis) -> Dict[str, str]:
# return all channels
channels = await redis.hgetall(f"{redis_prefix}:autojoin")
return {key: value.split("-") for key, value in channels.items()}
@staticmethod
async def update_channel(
redis: Redis, guild_id: int, voice_channel_id: int, text_channel_id: int
):
await redis.hset(
f"{redis_prefix}:autojoin",
guild_id,
f"{voice_channel_id}-{text_channel_id}",
)
@staticmethod
async def del_channel(redis: Redis, guild_id: int):
await redis.hdel(f"{redis_prefix}:autojoin", guild_id)
class Playlist:
@staticmethod
async def random(redis: Redis, amount: Optional[int] = 1) -> List[str]:
return await redis.srandmember(f"{redis_prefix}:playlist", amount)
+27
View File
@@ -0,0 +1,27 @@
from typing import Callable
from typing import TYPE_CHECKING
from typing import TypeVar
from discord.ext.commands import check
from discord.ext.commands.errors import NotOwner
if TYPE_CHECKING:
from context import CustomContext
T = TypeVar("T")
def source_manager_only() -> Callable[[T], T]:
"""
A :func:`.check` that checks if the person invoking this command is allowed to modify the radio sources.
"""
async def predicate(ctx: "CustomContext") -> bool:
is_manager = ctx.author.id in ctx.bot.config["manager_ids"]
is_owner = await ctx.bot.is_owner(ctx.author)
if not is_manager and not is_owner:
raise NotOwner("You are not allowed to modify the sources.")
return True
return check(predicate)
+2 -1
View File
@@ -1,7 +1,8 @@
from discord.embeds import Embed from discord.embeds import Embed
from context import CustomContext
from discord.ext.commands import CommandError from discord.ext.commands import CommandError
from context import CustomContext
class EmbeddedCommandException(CommandError): class EmbeddedCommandException(CommandError):
def __init__(self, embed: Embed) -> None: def __init__(self, embed: Embed) -> None: