42 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
matthew 68fff401c7 Merge pull request #29 from Matthww/dev
Release v1.0.3
2021-11-06 22:29:16 +01:00
matthew 2e007a6fb3 Merge pull request #28 from Matthww/fix_wlinfo
Hot fix for wlinfo
2021-11-06 22:28:39 +01:00
matthew 47d64c2cb3 Temp fix for wlinfo 2021-11-06 22:28:04 +01:00
matthew 1e7f54e561 Release v1.0.2
Release v1.0.2
2021-11-06 21:40:50 +01:00
niku 162abc8617 Merge pull request #27 from Matthww/fix_wlinfo
Fixed wlinfo
2021-11-06 21:40:06 +01:00
matthew a3d9d71af4 Merge branch 'dev' into fix_wlinfo 2021-11-06 21:38:29 +01:00
matthew bc94157a0c Fixed wlinfo 2021-11-06 21:36:24 +01:00
niku a8db9e70df Merge pull request #24 from Matthww/add_packackages
Add packages
2021-11-06 20:48:09 +01:00
niku f6d52555dd Merge branch 'dev' into add_packackages 2021-11-06 20:47:52 +01:00
matthew 6658c96615 oops no hardcoding 2021-11-06 20:42:54 +01:00
matthew 707f908ebf Add packages to poetry 2021-11-06 20:40:48 +01:00
matthew af4a16669f Merge pull request #23 from strNophix/context-properties
using @property for existing context functions
2021-11-06 18:39:24 +01:00
niku 9b2babf4e3 using @property for existing context functions 2021-11-06 17:56:17 +01:00
Matthww c3fc008f3a pepega lock 2021-11-06 16:39:59 +00:00
niku 4aa5cd9124 Merge pull request #21 from Matthww/dev
Release 1.0.1
2021-11-06 17:30:51 +01:00
niku 896bb93e69 Merge pull request #22 from Matthww/scripts
Add redis pumper 🍆
2021-11-06 17:26:19 +01:00
niku ae762683ab Merge branch 'dev' into scripts 2021-11-06 17:26:11 +01:00
matthew bb7062397e Add redis pumper 🍆 2021-11-06 17:21:59 +01:00
matthew 247c895136 Merge pull request #20 from strNophix/rebranding
Rebranding to TuneBot + updated pyproject.toml
2021-11-06 17:07:47 +01:00
matthew 7e74c1f5a7 Merge pull request #19 from strNophix/config-argument
Take bot config as argument
2021-11-06 17:07:20 +01:00
niku df90ba3a00 take bot config as argument 2021-11-06 16:08:19 +01:00
niku aed057daa4 Rebranding to TuneBot + updated pyproject.toml 2021-11-06 15:58:10 +01:00
27 changed files with 864 additions and 188 deletions
+3
View File
@@ -1,3 +1,6 @@
# Project files
scripts/playlists/*.txt
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
+1 -1
View File
@@ -1,5 +1,5 @@
default_language_version:
python: python3.8
python: python3.9
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.0.1
+2 -2
View File
@@ -1,2 +1,2 @@
# ChristmasBot
Christmas Music Bot
# TuneBot
A very configurable Discord radio
+38 -3
View File
@@ -4,6 +4,7 @@ from typing import Any
from typing import Dict
from typing import List
from typing import Sequence
from typing import TYPE_CHECKING
import aioredis
import discord
@@ -21,9 +22,17 @@ from discord.ext.commands.errors import ExtensionNotFound
from discord.ext.commands.errors import NoEntryPointError
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 ChristmasBot(commands.Bot):
class TuneBot(commands.Bot):
lavalink: lavalink.Client
invite_link: str
@@ -39,9 +48,24 @@ class ChristmasBot(commands.Bot):
self.initial_cog_names: List[str] = self.config.get("cogs", [])
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.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 = ""
slash_guilds = None
@@ -50,6 +74,7 @@ class ChristmasBot(commands.Bot):
super().__init__(
command_prefix=self.prefix_callable,
owner_ids=self.config["owner_ids"],
description=self.config["info"]["description"],
case_insensitive=False,
fetch_offline_members=False,
@@ -86,6 +111,12 @@ class ChristmasBot(commands.Bot):
print(f"Version: {discord.__version__}")
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]:
colour_dict: Dict[str, Color] = {}
for name, color in colors.items():
@@ -110,7 +141,11 @@ class ChristmasBot(commands.Bot):
await self.change_presence(activity=activity)
config = json.load(open("config.json", "r", encoding="utf-8"))
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__":
@@ -123,4 +158,4 @@ if __name__ == "__main__":
pass
token = config.pop("token")
ChristmasBot(config).run(token, reconnect=True)
TuneBot(config).run(token, reconnect=True)
+15 -18
View File
@@ -6,14 +6,12 @@ import discord
import humanize
import lavalink
from discord.ext import commands
from discord.ext import tasks
from discord.ext.commands import Context
from bot import ChristmasBot
from bot import TuneBot
from context import CustomContext
from utils.database import AutoJoin
from utils.EmbedGenerator import EmbedGenerator
from utils.classes import BaseCog
from utils.EmbedGenerator import EmbedGenerator
from utils.paginator import HelpPaginator
@@ -53,26 +51,25 @@ class InformationCog(BaseCog, name="Information"):
async def wlinfo(self, ctx: CustomContext):
"""Retrieve various node/server/player information"""
player = self.bot.lavalink.player_manager.get(ctx.guild.id)
node = player.node
nodes = self.bot.lavalink.node_manager.available_nodes
used = humanize.naturalsize(node.stats.memory_used)
total = humanize.naturalsize(node.stats.memory_allocated)
free = humanize.naturalsize(node.stats.memory_free)
cpu = node.stats.cpu_cores
used = humanize.naturalsize(sum([n.stats.memory_used for n in nodes]))
total = humanize.naturalsize(sum([n.stats.memory_allocated for n in nodes]))
free = humanize.naturalsize(sum([n.stats.memory_free for n in nodes]))
cpu = sum([n.stats.cpu_cores for n in nodes])
fmt = (
f"**WaveLink:** `{lavalink.__version__}`\n\n"
f"Connected to `{len(self.bot.lavalink.nodes)}` nodes.\n"
f"Best available Node `{self.bot.lavalink.get_best_node().__repr__()}`\n"
f"`{len(self.bot.lavalink.players)}` players are distributed on nodes.\n"
f"`{node.stats.players}` players are distributed on server.\n"
f"`{node.stats.playing_players}` players are playing on server.\n\n"
f"**Lavalink:** `{lavalink.__version__}`\n\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"`{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.playing_players for n in nodes])}` players are playing on server.\n\n"
f"Server Memory: `{used}/{total}` | `({free} free)`\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)
AutoJoin.get_channels()
@commands.command(name="help", aliases=["about", "info"])
@commands.cooldown(1, 1, commands.BucketType.user)
@@ -123,6 +120,6 @@ class InformationCog(BaseCog, name="Information"):
await ctx.send(embed=embed)
def setup(bot: ChristmasBot):
def setup(bot: TuneBot):
bot.remove_command("help")
bot.add_cog(InformationCog(bot))
+26 -48
View File
@@ -4,24 +4,20 @@ import re
from typing import Optional
import discord
from discord.channel import TextChannel
from discord.ext.commands.context import Context
from discord.ext.commands.errors import CommandError
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 ChristmasBot
from utils.classes import BaseCog
from bot import TuneBot
from context import CustomContext
from utils.database import AutoJoin
from utils.database import Playlist
from utils.exceptions import EmbeddedCommandException
from utils.classes import BaseCog
from utils.EmbedGenerator import EmbedGenerator
from utils.exceptions import EmbeddedCommandException
url_rx = re.compile(r"https?://(?:www\.)?.+")
@@ -78,25 +74,11 @@ class LavalinkVoiceClient(discord.VoiceClient):
class Music(BaseCog):
@commands.Cog.listener()
async def on_ready(self):
if not hasattr(
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:
while not self.is_lavalink_ready():
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():
player = self.bot.lavalink.player_manager.create(guild_id)
player.store("channel", textchannel_id)
@@ -112,7 +94,7 @@ class Music(BaseCog):
await textchannel.send("Automatically joined the voice channel")
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.
for query in queries:
result = await player.node.get_tracks(query)
@@ -155,7 +137,7 @@ class Music(BaseCog):
# This is essentially the same as `@commands.guild_only()`
# 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.")
if guild_check:
@@ -241,15 +223,16 @@ class Music(BaseCog):
async def play(self, ctx: CustomContext):
"""Start the radio"""
# Get the player for this guild from cache.
player = ctx.get_player()
if player.is_connected:
if ctx.player.is_connected:
await ctx.send("Already connected")
return
await self.fill_player_queue(player, self.bot.config["queue_buffer_size"]+1)
if not player.is_playing:
await player.play()
await self.fill_player_queue(
ctx.player, self.bot.config["queue_buffer_size"] + 1
)
if not ctx.player.is_playing:
await ctx.player.play()
await EmbedGenerator.Title(ctx, "*⃣ | Connected.")
return
@@ -257,21 +240,19 @@ class Music(BaseCog):
@commands.command(name="skip", aliases=["next"])
async def skip(self, ctx: CustomContext):
"""Skip the current song"""
player = ctx.get_player()
await player.skip()
await ctx.player.skip()
await ctx.send("Skipped current song")
@commands.command(name="queue")
async def queue(self, ctx: CustomContext):
"""Display the current radio queue"""
player = ctx.get_player()
embed_color = self.bot.colors["embed"]
embed = Embed(title="Coming Up...", colour=embed_color)
if len(player.queue) > 0:
if len(ctx.player.queue) > 0:
embed.description = "\n".join(
f"{index}. [{track.title}]({track.uri})"
for index, track in enumerate(player.queue, 1)
for index, track in enumerate(ctx.player.queue, 1)
)
else:
embed.description = "We are still determining a playlist"
@@ -281,11 +262,9 @@ class Music(BaseCog):
@commands.command(name="disconnect", aliases=["dc", "stop"])
async def disconnect(self, ctx: CustomContext):
"""Disconnects the radio from the channel"""
player = ctx.get_player()
if not ctx.author.voice or (
player.is_connected
and ctx.author.voice.channel.id != int(player.channel_id)
ctx.player.is_connected
and ctx.author.voice.channel.id != int(ctx.player.channel_id)
):
# Abuse prevention. Users not in voice channels, or not in the same voice channel as the bot
# may not disconnect the bot.
@@ -294,20 +273,19 @@ class Music(BaseCog):
# Clear the queue to ensure old tracks don't start playing
# when someone else queues something.
player.queue.clear()
ctx.player.queue.clear()
# Stop the current track so Lavalink consumes less resources.
await player.stop()
await ctx.player.stop()
# Disconnect from the voice channel.
await ctx.voice_client.disconnect(force=True)
await EmbedGenerator.Title(ctx, "*⃣ | Disconnected.")
@commands.command(name="now")
async def now_playing(self, ctx: CustomContext):
player: DefaultPlayer = self.bot.lavalink.player_manager.get(ctx.guild.id)
track = player.current
embed = await self.create_track_embed(track)
"""Displays information about the currently played track"""
embed = await self.create_track_embed(ctx.player.current)
await ctx.send(embed=embed)
def setup(bot: ChristmasBot):
def setup(bot: TuneBot):
bot.add_cog(Music(bot))
+3 -3
View File
@@ -12,12 +12,12 @@ import discord
from discord.ext import commands
from discord.ext.commands.context import Context
from bot import ChristmasBot
from bot import TuneBot
from utils.classes import BaseCog
class OwnerCog(BaseCog):
def __init__(self, bot: ChristmasBot):
def __init__(self, bot: TuneBot):
super().__init__(bot)
self._last_result = None
@@ -209,5 +209,5 @@ class OwnerCog(BaseCog):
await message.edit(content=f"```fix\n{content}\n```")
def setup(bot: ChristmasBot):
def setup(bot: TuneBot):
bot.add_cog(OwnerCog(bot))
+137 -20
View File
@@ -1,13 +1,12 @@
from discord.ext import commands
from utils.EmbedGenerator import EmbedGenerator
from utils.classes import BaseCog
from utils.database import AutoJoin
from bot import ChristmasBot
from discord.ext.commands import Context
from typing import Any
from bot import ChristmasBot
from discord.ext import commands
from discord.message import Message
from bot import TuneBot
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
@@ -19,29 +18,147 @@ class SettingsCog(BaseCog, name="Settings"):
await EmbedGenerator.Message(
ctx,
"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.cooldown(rate=1, per=5, type=commands.BucketType.user)
async def autojoin_set(self, ctx: CustomContext):
"""Enable the bot automatically joining"""
voicechannel_id = ctx.author.voice.channel.id
textchannel_id = ctx.message.channel.id
await AutoJoin.update_channel(
ctx.get_redis(), ctx.guild.id, voicechannel_id, textchannel_id
)
await EmbedGenerator.Message(ctx, "Autojoin", "`enabled`")
voice_state = ctx.author.voice
if not voice_state:
embed = ctx.create_embed()
embed.title = "Please join a voice channel before running this command."
await ctx.send(embed=embed)
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.cooldown(rate=1, per=5, type=commands.BucketType.user)
async def autojoin_del(self, ctx: CustomContext):
"""Disable the bot automatically joining"""
await AutoJoin.del_channel(ctx.get_redis(), ctx.guild.id)
await EmbedGenerator.Message(ctx, "Autojoin", "`disabled`")
await ctx.autojoin.disable()
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: ChristmasBot):
def setup(bot: TuneBot):
bot.add_cog(SettingsCog(bot))
+1
View File
@@ -1,6 +1,7 @@
{
"token": "",
"owner_ids": [194545408960102400, 190875175460405249],
"manager_ids": [],
"prefixes": ["ck!"],
"redis_url": "",
"redis_prefix": "",
+44 -3
View File
@@ -1,15 +1,56 @@
from typing import TYPE_CHECKING
from aioredis.client import Redis
from discord import Embed
from discord.ext import commands
from discord.ext.commands.errors import CommandInvokeError
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):
def get_redis(self) -> Redis:
bot: "TuneBot"
cog: "BaseCog"
@property
def redis(self) -> Redis:
return self.bot._redis_client
def get_player(self) -> DefaultPlayer:
if hasattr(self.bot, "lavalink"):
@property
def player(self) -> DefaultPlayer:
if self.cog.is_lavalink_ready():
return self.bot.lavalink.player_manager.get(self.guild.id)
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
Generated
+272 -46
View File
@@ -31,6 +31,18 @@ typing-extensions = "*"
[package.extras]
hiredis = ["hiredis (>=1.0)"]
[[package]]
name = "aiotube"
version = "1.3.5"
description = "Get YouTube Public Data without YouTubeAPI"
category = "dev"
optional = false
python-versions = ">=3.6"
[package.dependencies]
pytube = "*"
urllib3 = "*"
[[package]]
name = "async-timeout"
version = "3.0.1"
@@ -67,7 +79,7 @@ testing = ["pytest (>=4.6)", "pytest-flake8", "pytest-cov", "pytest-black (>=0.3
[[package]]
name = "black"
version = "21.9b0"
version = "21.10b0"
description = "The uncompromising code formatter."
category = "dev"
optional = false
@@ -87,11 +99,19 @@ typing-extensions = [
[package.extras]
colorama = ["colorama (>=0.4.3)"]
d = ["aiohttp (>=3.6.0)", "aiohttp-cors (>=0.4.0)"]
d = ["aiohttp (>=3.7.4)"]
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
python2 = ["typed-ast (>=1.4.2)"]
python2 = ["typed-ast (>=1.4.3)"]
uvloop = ["uvloop (>=0.15.2)"]
[[package]]
name = "certifi"
version = "2021.10.8"
description = "Python package for providing Mozilla's CA Bundle."
category = "dev"
optional = false
python-versions = "*"
[[package]]
name = "cffi"
version = "1.15.0"
@@ -119,6 +139,17 @@ category = "main"
optional = false
python-versions = "*"
[[package]]
name = "charset-normalizer"
version = "2.0.7"
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
category = "dev"
optional = false
python-versions = ">=3.5.0"
[package.extras]
unicode_backport = ["unicodedata2"]
[[package]]
name = "click"
version = "8.0.3"
@@ -236,6 +267,14 @@ category = "main"
optional = false
python-versions = ">=3.5"
[[package]]
name = "mutagen"
version = "1.45.1"
description = "read and write audio tags for many formats"
category = "dev"
optional = false
python-versions = ">=3.5, <4"
[[package]]
name = "mypy-extensions"
version = "0.4.3"
@@ -298,12 +337,20 @@ virtualenv = ">=20.0.8"
[[package]]
name = "pycparser"
version = "2.20"
version = "2.21"
description = "C parser in Python"
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[[package]]
name = "pycryptodomex"
version = "3.11.0"
description = "Cryptographic library for Python"
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]]
name = "pynacl"
version = "1.4.0"
@@ -320,6 +367,14 @@ six = "*"
docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"]
tests = ["pytest (>=3.2.1,!=3.3.0)", "hypothesis (>=3.27.0)"]
[[package]]
name = "pytube"
version = "11.0.1"
description = "Python 3 library for downloading YouTube Videos."
category = "dev"
optional = false
python-versions = ">=3.6"
[[package]]
name = "pyyaml"
version = "6.0"
@@ -328,14 +383,43 @@ category = "dev"
optional = false
python-versions = ">=3.6"
[[package]]
name = "redis"
version = "3.5.3"
description = "Python client for Redis key-value store"
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[package.extras]
hiredis = ["hiredis (>=0.1.3)"]
[[package]]
name = "regex"
version = "2021.10.23"
version = "2021.11.2"
description = "Alternative regular expression module, to replace re."
category = "dev"
optional = false
python-versions = "*"
[[package]]
name = "requests"
version = "2.26.0"
description = "Python HTTP for Humans."
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
[package.dependencies]
certifi = ">=2017.4.17"
charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""}
idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""}
urllib3 = ">=1.21.1,<1.27"
[package.extras]
socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"]
use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"]
[[package]]
name = "six"
version = "1.16.0"
@@ -368,6 +452,19 @@ category = "main"
optional = false
python-versions = "*"
[[package]]
name = "urllib3"
version = "1.26.7"
description = "HTTP library with thread-safe connection pooling, file post, and more."
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
[package.extras]
brotli = ["brotlipy (>=0.6.0)"]
secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
[[package]]
name = "uvloop"
version = "0.16.0"
@@ -381,6 +478,14 @@ dev = ["Cython (>=0.29.24,<0.30.0)", "pytest (>=3.6.0)", "Sphinx (>=4.1.2,<4.2.0
docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)"]
test = ["aiohttp", "flake8 (>=3.9.2,<3.10.0)", "psutil", "pycodestyle (>=2.7.0,<2.8.0)", "pyOpenSSL (>=19.0.0,<19.1.0)", "mypy (>=0.800)"]
[[package]]
name = "websockets"
version = "10.0"
description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)"
category = "dev"
optional = false
python-versions = ">=3.7"
[[package]]
name = "virtualenv"
version = "20.10.0"
@@ -412,10 +517,23 @@ python-versions = ">=3.5"
idna = ">=2.0"
multidict = ">=4.0"
[[package]]
name = "yt-dlp"
version = "2021.10.22"
description = "Command-line program to download videos from YouTube.com and many other other video platforms."
category = "dev"
optional = false
python-versions = ">=3.6"
[package.dependencies]
mutagen = "*"
pycryptodomex = "*"
websockets = "*"
[metadata]
lock-version = "1.1"
python-versions = "^3.8"
content-hash = "7428479732f5183e24be61ddeee7230bafdd6b76920e2d6d548c6a091e570111"
content-hash = "87b61ad577bb9413177b2749b0c4b30026aa512bf3a6eb62e74c0ef7e2470ed4"
[metadata.files]
aiohttp = [
@@ -437,6 +555,10 @@ aioredis = [
{file = "aioredis-2.0.0-py3-none-any.whl", hash = "sha256:9921d68a3df5c5cdb0d5b49ad4fc88a4cfdd60c108325df4f0066e8410c55ffb"},
{file = "aioredis-2.0.0.tar.gz", hash = "sha256:3a2de4b614e6a5f8e104238924294dc4e811aefbe17ddf52c04a93cbf06e67db"},
]
aiotube = [
{file = "aiotube-1.3.5-py3-none-any.whl", hash = "sha256:4dcef22dd27e6229b82186555c7f4779b62f751d669b9517b88d8d7af977338f"},
{file = "aiotube-1.3.5.tar.gz", hash = "sha256:3b015060480136812249433b02b5d869ad62a82916a92fe741bf4f907b7a948b"},
]
async-timeout = [
{file = "async-timeout-3.0.1.tar.gz", hash = "sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f"},
{file = "async_timeout-3.0.1-py3-none-any.whl", hash = "sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3"},
@@ -450,8 +572,12 @@ attrs = [
{file = "backports.entry_points_selectable-1.1.0.tar.gz", hash = "sha256:988468260ec1c196dab6ae1149260e2f5472c9110334e5d51adcb77867361f6a"},
]
black = [
{file = "black-21.9b0-py3-none-any.whl", hash = "sha256:380f1b5da05e5a1429225676655dddb96f5ae8c75bdf91e53d798871b902a115"},
{file = "black-21.9b0.tar.gz", hash = "sha256:7de4cfc7eb6b710de325712d40125689101d21d25283eed7e9998722cf10eb91"},
{file = "black-21.10b0-py3-none-any.whl", hash = "sha256:6eb7448da9143ee65b856a5f3676b7dda98ad9abe0f87fce8c59291f15e82a5b"},
{file = "black-21.10b0.tar.gz", hash = "sha256:a9952229092e325fe5f3dae56d81f639b23f7131eb840781947e4b2886030f33"},
]
certifi = [
{file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"},
{file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"},
]
cffi = [
{file = "cffi-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962"},
@@ -513,6 +639,10 @@ chardet = [
{file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"},
{file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"},
]
charset-normalizer = [
{file = "charset-normalizer-2.0.7.tar.gz", hash = "sha256:e019de665e2bcf9c2b64e2e5aa025fa991da8720daa3c1138cadd2fd1856aed0"},
{file = "charset_normalizer-2.0.7-py3-none-any.whl", hash = "sha256:f7af805c321bfa1ce6714c51f254e0d5bb5e5834039bc17db7ebe3a4cec9492b"},
]
click = [
{file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"},
{file = "click-8.0.3.tar.gz", hash = "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"},
@@ -564,6 +694,10 @@ multidict = [
{file = "multidict-4.7.6-cp38-cp38-win_amd64.whl", hash = "sha256:7388d2ef3c55a8ba80da62ecfafa06a1c097c18032a501ffd4cabbc52d7f2b19"},
{file = "multidict-4.7.6.tar.gz", hash = "sha256:fbb77a75e529021e7c4a8d4e823d88ef4d23674a202be4f5addffc72cbb91430"},
]
mutagen = [
{file = "mutagen-1.45.1-py3-none-any.whl", hash = "sha256:9c9f243fcec7f410f138cb12c21c84c64fde4195481a30c9bfb05b5f003adfed"},
{file = "mutagen-1.45.1.tar.gz", hash = "sha256:6397602efb3c2d7baebd2166ed85731ae1c1d475abca22090b7141ff5034b3e1"},
]
mypy-extensions = [
{file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
{file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
@@ -610,8 +744,40 @@ pre-commit = [
{file = "pre_commit-2.15.0.tar.gz", hash = "sha256:3c25add78dbdfb6a28a651780d5c311ac40dd17f160eb3954a0c59da40a505a7"},
]
pycparser = [
{file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"},
{file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"},
{file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"},
{file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"},
]
pycryptodomex = [
{file = "pycryptodomex-3.11.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:7abfd84a362e4411f7c5f5758c18cbf377a2a2be64b9232e78544d75640c677e"},
{file = "pycryptodomex-3.11.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:6a76d7821ae43df8a0e814cca32114875916b9fc2158603b364853de37eb9002"},
{file = "pycryptodomex-3.11.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:1580db5878b1d16a233550829f7c189c43005f7aa818f2f95c7dddbd6a7163cc"},
{file = "pycryptodomex-3.11.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:c825611a951baad63faeb9ef1517ef96a20202d6029ae2485b729152cc703fab"},
{file = "pycryptodomex-3.11.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:7cc5ee80b2d5ee8f59a761741cfb916a068c97cac5e700c8ce01e1927616aa2f"},
{file = "pycryptodomex-3.11.0-cp27-cp27m-manylinux2014_aarch64.whl", hash = "sha256:fbe09e3ae95f47c7551a24781d2e348974cde4a0b33bc3b1566f6216479db2b1"},
{file = "pycryptodomex-3.11.0-cp27-cp27m-win32.whl", hash = "sha256:9eace1e5420abc4f9e76de01e49caca349b7c80bda9c1643193e23a06c2a332c"},
{file = "pycryptodomex-3.11.0-cp27-cp27m-win_amd64.whl", hash = "sha256:adc25aa8cfc537373dd46ae97863f16fd955edee14bf54d3eb52bde4e4ac8c7b"},
{file = "pycryptodomex-3.11.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:cf30b5e03d974874185b989839c396d799f6e2d4b4d5b2d8bd3ba464eb3cc33f"},
{file = "pycryptodomex-3.11.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:c91772cf6808cc2d80279e80b491c48cb688797b6d914ff624ca95d855c24ee5"},
{file = "pycryptodomex-3.11.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:c391ec5c423a374a36b90f7c8805fdf51a0410a2b5be9cebd8990e0021cb6da4"},
{file = "pycryptodomex-3.11.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:64a83ab6f54496ab968a6f21a41a620afe0a742573d609fd03dcab7210645153"},
{file = "pycryptodomex-3.11.0-cp27-cp27mu-manylinux2014_aarch64.whl", hash = "sha256:252ac9c1e1ae1c256a75539e234be3096f2d100b9f4bae42ef88067787b9b249"},
{file = "pycryptodomex-3.11.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:bf2ea67eaa1fff0aecef6da881144f0f91e314b4123491f9a4fa8df0598e48fe"},
{file = "pycryptodomex-3.11.0-cp35-abi3-manylinux1_i686.whl", hash = "sha256:fe2b8c464ba335e71aed74f830bf2b2881913f8905d166f9c0fe06ca44a1cb5e"},
{file = "pycryptodomex-3.11.0-cp35-abi3-manylinux1_x86_64.whl", hash = "sha256:ff0826f3886e85708a0e8ef7ec47020723b998cfed6ae47962d915fcb89ec780"},
{file = "pycryptodomex-3.11.0-cp35-abi3-manylinux2010_i686.whl", hash = "sha256:1d4d13c59d2cfbc0863c725f5812d66ff0d6836ba738ef26a52e1291056a1c7c"},
{file = "pycryptodomex-3.11.0-cp35-abi3-manylinux2010_x86_64.whl", hash = "sha256:2b586d13ef07fa6197b6348a48dbbe9525f4f496205de14edfa4e91d99e69672"},
{file = "pycryptodomex-3.11.0-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:f35ccfa44a1dd267e392cd76d8525cfcfabee61dd070e15ad2119c54c0c31ddf"},
{file = "pycryptodomex-3.11.0-cp35-abi3-win32.whl", hash = "sha256:5baf690d27f39f2ba22f06e8e32c5f1972573ca65db6bdbb8b2c7177a0112dab"},
{file = "pycryptodomex-3.11.0-cp35-abi3-win_amd64.whl", hash = "sha256:919cadcedad552e78349d1626115cfd246fc03ad469a4a62c91a12204f0f0d85"},
{file = "pycryptodomex-3.11.0-pp27-pypy_73-macosx_10_9_x86_64.whl", hash = "sha256:c10b2f6bcbaa9aa51fe08207654100074786d423b03482c0cbe44406ca92d146"},
{file = "pycryptodomex-3.11.0-pp27-pypy_73-manylinux1_x86_64.whl", hash = "sha256:91662b27f5aa8a6d2ad63be9a7d1a403e07bf3c2c5b265a7cc5cbadf6f988e06"},
{file = "pycryptodomex-3.11.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:207e53bdbf3a26de6e9dcf3ebaf67ba70a61f733f84c464eca55d278211c1b71"},
{file = "pycryptodomex-3.11.0-pp27-pypy_73-win32.whl", hash = "sha256:1dd4271d8d022216533c3547f071662b44d703fd5dbb632c4b5e77b3ee47567f"},
{file = "pycryptodomex-3.11.0-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c43ddcff251e8b427b3e414b026636617276e008a9d78a44a9195d4bdfcaa0fe"},
{file = "pycryptodomex-3.11.0-pp36-pypy36_pp73-manylinux1_x86_64.whl", hash = "sha256:ef25d682d0d9ab25c5022a298b5cba9084c7b148a3e71846df2c67ea664eacc7"},
{file = "pycryptodomex-3.11.0-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:4c7c6418a3c08b2ebfc2cf50ce52de267618063b533083a2c73b40ec54a1b6f5"},
{file = "pycryptodomex-3.11.0-pp36-pypy36_pp73-win32.whl", hash = "sha256:15d25c532de744648f0976c56bd10d07b2a44b7eb2a6261ffe2497980b1102d8"},
{file = "pycryptodomex-3.11.0.tar.gz", hash = "sha256:0398366656bb55ebdb1d1d493a7175fc48ade449283086db254ac44c7d318d6d"},
]
pynacl = [
{file = "PyNaCl-1.4.0-cp27-cp27m-macosx_10_10_x86_64.whl", hash = "sha256:ea6841bc3a76fa4942ce00f3bda7d436fda21e2d91602b9e21b7ca9ecab8f3ff"},
@@ -633,6 +799,10 @@ pynacl = [
{file = "PyNaCl-1.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:7c6092102219f59ff29788860ccb021e80fffd953920c4a8653889c029b2d420"},
{file = "PyNaCl-1.4.0.tar.gz", hash = "sha256:54e9a2c849c742006516ad56a88f5c74bf2ce92c9f67435187c3c5953b346505"},
]
pytube = [
{file = "pytube-11.0.1-py3-none-any.whl", hash = "sha256:d4dfea7394d7662edac3831432b349b2afac984e0d0bc4bdb611ec1f3fc16318"},
{file = "pytube-11.0.1.tar.gz", hash = "sha256:47643a6ff553cbc4d6be748ff14c9c45f79984e15005adff25d62b18110abe43"},
]
pyyaml = [
{file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"},
{file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"},
@@ -668,43 +838,64 @@ pyyaml = [
{file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"},
{file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"},
]
redis = [
{file = "redis-3.5.3-py2.py3-none-any.whl", hash = "sha256:432b788c4530cfe16d8d943a09d40ca6c16149727e4afe8c2c9d5580c59d9f24"},
{file = "redis-3.5.3.tar.gz", hash = "sha256:0e7e0cfca8660dea8b7d5cd8c4f6c5e29e11f31158c0b0ae91a397f00e5a05a2"},
]
regex = [
{file = "regex-2021.10.23-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:45b65d6a275a478ac2cbd7fdbf7cc93c1982d613de4574b56fd6972ceadb8395"},
{file = "regex-2021.10.23-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:74d071dbe4b53c602edd87a7476ab23015a991374ddb228d941929ad7c8c922e"},
{file = "regex-2021.10.23-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:34d870f9f27f2161709054d73646fc9aca49480617a65533fc2b4611c518e455"},
{file = "regex-2021.10.23-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fb698037c35109d3c2e30f2beb499e5ebae6e4bb8ff2e60c50b9a805a716f79"},
{file = "regex-2021.10.23-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cb46b542133999580ffb691baf67410306833ee1e4f58ed06b6a7aaf4e046952"},
{file = "regex-2021.10.23-cp310-cp310-win32.whl", hash = "sha256:5e9c9e0ce92f27cef79e28e877c6b6988c48b16942258f3bc55d39b5f911df4f"},
{file = "regex-2021.10.23-cp310-cp310-win_amd64.whl", hash = "sha256:ab7c5684ff3538b67df3f93d66bd3369b749087871ae3786e70ef39e601345b0"},
{file = "regex-2021.10.23-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:de557502c3bec8e634246588a94e82f1ee1b9dfcfdc453267c4fb652ff531570"},
{file = "regex-2021.10.23-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee684f139c91e69fe09b8e83d18b4d63bf87d9440c1eb2eeb52ee851883b1b29"},
{file = "regex-2021.10.23-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5095a411c8479e715784a0c9236568ae72509450ee2226b649083730f3fadfc6"},
{file = "regex-2021.10.23-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b568809dca44cb75c8ebb260844ea98252c8c88396f9d203f5094e50a70355f"},
{file = "regex-2021.10.23-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:eb672217f7bd640411cfc69756ce721d00ae600814708d35c930930f18e8029f"},
{file = "regex-2021.10.23-cp36-cp36m-win32.whl", hash = "sha256:a7a986c45d1099a5de766a15de7bee3840b1e0e1a344430926af08e5297cf666"},
{file = "regex-2021.10.23-cp36-cp36m-win_amd64.whl", hash = "sha256:6d7722136c6ed75caf84e1788df36397efdc5dbadab95e59c2bba82d4d808a4c"},
{file = "regex-2021.10.23-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9f665677e46c5a4d288ece12fdedf4f4204a422bb28ff05f0e6b08b7447796d1"},
{file = "regex-2021.10.23-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:450dc27483548214314640c89a0f275dbc557968ed088da40bde7ef8fb52829e"},
{file = "regex-2021.10.23-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:129472cd06062fb13e7b4670a102951a3e655e9b91634432cfbdb7810af9d710"},
{file = "regex-2021.10.23-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a940ca7e7189d23da2bfbb38973832813eab6bd83f3bf89a977668c2f813deae"},
{file = "regex-2021.10.23-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:530fc2bbb3dc1ebb17f70f7b234f90a1dd43b1b489ea38cea7be95fb21cdb5c7"},
{file = "regex-2021.10.23-cp37-cp37m-win32.whl", hash = "sha256:ded0c4a3eee56b57fcb2315e40812b173cafe79d2f992d50015f4387445737fa"},
{file = "regex-2021.10.23-cp37-cp37m-win_amd64.whl", hash = "sha256:391703a2abf8013d95bae39145d26b4e21531ab82e22f26cd3a181ee2644c234"},
{file = "regex-2021.10.23-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be04739a27be55631069b348dda0c81d8ea9822b5da10b8019b789e42d1fe452"},
{file = "regex-2021.10.23-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13ec99df95003f56edcd307db44f06fbeb708c4ccdcf940478067dd62353181e"},
{file = "regex-2021.10.23-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8d1cdcda6bd16268316d5db1038965acf948f2a6f43acc2e0b1641ceab443623"},
{file = "regex-2021.10.23-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c186691a7995ef1db61205e00545bf161fb7b59cdb8c1201c89b333141c438a"},
{file = "regex-2021.10.23-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2b20f544cbbeffe171911f6ce90388ad36fe3fad26b7c7a35d4762817e9ea69c"},
{file = "regex-2021.10.23-cp38-cp38-win32.whl", hash = "sha256:c0938ddd60cc04e8f1faf7a14a166ac939aac703745bfcd8e8f20322a7373019"},
{file = "regex-2021.10.23-cp38-cp38-win_amd64.whl", hash = "sha256:56f0c81c44638dfd0e2367df1a331b4ddf2e771366c4b9c5d9a473de75e3e1c7"},
{file = "regex-2021.10.23-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:80bb5d2e92b2258188e7dcae5b188c7bf868eafdf800ea6edd0fbfc029984a88"},
{file = "regex-2021.10.23-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1dae12321b31059a1a72aaa0e6ba30156fe7e633355e445451e4021b8e122b6"},
{file = "regex-2021.10.23-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1f2b59c28afc53973d22e7bc18428721ee8ca6079becf1b36571c42627321c65"},
{file = "regex-2021.10.23-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d134757a37d8640f3c0abb41f5e68b7cf66c644f54ef1cb0573b7ea1c63e1509"},
{file = "regex-2021.10.23-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0dcc0e71118be8c69252c207630faf13ca5e1b8583d57012aae191e7d6d28b84"},
{file = "regex-2021.10.23-cp39-cp39-win32.whl", hash = "sha256:a30513828180264294953cecd942202dfda64e85195ae36c265daf4052af0464"},
{file = "regex-2021.10.23-cp39-cp39-win_amd64.whl", hash = "sha256:0f7552429dd39f70057ac5d0e897e5bfe211629652399a21671e53f2a9693a4e"},
{file = "regex-2021.10.23.tar.gz", hash = "sha256:f3f9a91d3cc5e5b0ddf1043c0ae5fa4852f18a1c0050318baf5fc7930ecc1f9c"},
{file = "regex-2021.11.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:897c539f0f3b2c3a715be651322bef2167de1cdc276b3f370ae81a3bda62df71"},
{file = "regex-2021.11.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:886f459db10c0f9d17c87d6594e77be915f18d343ee138e68d259eb385f044a8"},
{file = "regex-2021.11.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:075b0fdbaea81afcac5a39a0d1bb91de887dd0d93bf692a5dd69c430e7fc58cb"},
{file = "regex-2021.11.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c6238d30dcff141de076344cf7f52468de61729c2f70d776fce12f55fe8df790"},
{file = "regex-2021.11.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7fab29411d75c2eb48070020a40f80255936d7c31357b086e5931c107d48306e"},
{file = "regex-2021.11.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0148988af0182a0a4e5020e7c168014f2c55a16d11179610f7883dd48ac0ebe"},
{file = "regex-2021.11.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be30cd315db0168063a1755fa20a31119da91afa51da2907553493516e165640"},
{file = "regex-2021.11.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e9cec3a62d146e8e122d159ab93ac32c988e2ec0dcb1e18e9e53ff2da4fbd30c"},
{file = "regex-2021.11.2-cp310-cp310-win32.whl", hash = "sha256:41c66bd6750237a8ed23028a6c9173dc0c92dc24c473e771d3bfb9ee817700c3"},
{file = "regex-2021.11.2-cp310-cp310-win_amd64.whl", hash = "sha256:0075fe4e2c2720a685fef0f863edd67740ff78c342cf20b2a79bc19388edf5db"},
{file = "regex-2021.11.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:0ed3465acf8c7c10aa2e0f3d9671da410ead63b38a77283ef464cbb64275df58"},
{file = "regex-2021.11.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab1fea8832976ad0bebb11f652b692c328043057d35e9ebc78ab0a7a30cf9a70"},
{file = "regex-2021.11.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cb1e44d860345ab5d4f533b6c37565a22f403277f44c4d2d5e06c325da959883"},
{file = "regex-2021.11.2-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9486ebda015913909bc28763c6b92fcc3b5e5a67dee4674bceed112109f5dfb8"},
{file = "regex-2021.11.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20605bfad484e1341b2cbfea0708e4b211d233716604846baa54b94821f487cb"},
{file = "regex-2021.11.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f20f9f430c33597887ba9bd76635476928e76cad2981643ca8be277b8e97aa96"},
{file = "regex-2021.11.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1d85ca137756d62c8138c971453cafe64741adad1f6a7e63a22a5a8abdbd19fa"},
{file = "regex-2021.11.2-cp36-cp36m-win32.whl", hash = "sha256:af23b9ca9a874ef0ec20e44467b8edd556c37b0f46f93abfa93752ea7c0e8d1e"},
{file = "regex-2021.11.2-cp36-cp36m-win_amd64.whl", hash = "sha256:070336382ca92c16c45b4066c4ba9fa83fb0bd13d5553a82e07d344df8d58a84"},
{file = "regex-2021.11.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:ef4e53e2fdc997d91f5b682f81f7dc9661db9a437acce28745d765d251902d85"},
{file = "regex-2021.11.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35ed5714467fc606551db26f80ee5d6aa1f01185586a7bccd96f179c4b974a11"},
{file = "regex-2021.11.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ee36d5113b6506b97f45f2e8447cb9af146e60e3f527d93013d19f6d0405f3b"},
{file = "regex-2021.11.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4fba661a4966adbd2c3c08d3caad6822ecb6878f5456588e2475ae23a6e47929"},
{file = "regex-2021.11.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77f9d16f7970791f17ecce7e7f101548314ed1ee2583d4268601f30af3170856"},
{file = "regex-2021.11.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f6a28e87ba69f3a4f30d775b179aac55be1ce59f55799328a0d9b6df8f16b39d"},
{file = "regex-2021.11.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9267e4fba27e6dd1008c4f2983cc548c98b4be4444e3e342db11296c0f45512f"},
{file = "regex-2021.11.2-cp37-cp37m-win32.whl", hash = "sha256:d4bfe3bc3976ccaeb4ae32f51e631964e2f0e85b2b752721b7a02de5ce3b7f27"},
{file = "regex-2021.11.2-cp37-cp37m-win_amd64.whl", hash = "sha256:2bb7cae741de1aa03e3dd3a7d98c304871eb155921ca1f0d7cc11f5aade913fd"},
{file = "regex-2021.11.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:23f93e74409c210de4de270d4bf88fb8ab736a7400f74210df63a93728cf70d6"},
{file = "regex-2021.11.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d8ee91e1c295beb5c132ebd78616814de26fedba6aa8687ea460c7f5eb289b72"},
{file = "regex-2021.11.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e3ff69ab203b54ce5c480c3ccbe959394ea5beef6bd5ad1785457df7acea92e"},
{file = "regex-2021.11.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e3c00cb5c71da655e1e5161481455479b613d500dd1bd252aa01df4f037c641f"},
{file = "regex-2021.11.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4abf35e16f4b639daaf05a2602c1b1d47370e01babf9821306aa138924e3fe92"},
{file = "regex-2021.11.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb11c982a849dc22782210b01d0c1b98eb3696ce655d58a54180774e4880ac66"},
{file = "regex-2021.11.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e3755e0f070bc31567dfe447a02011bfa8444239b3e9e5cca6773a22133839"},
{file = "regex-2021.11.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0621c90f28d17260b41838b22c81a79ff436141b322960eb49c7b3f91d1cbab6"},
{file = "regex-2021.11.2-cp38-cp38-win32.whl", hash = "sha256:8fbe1768feafd3d0156556677b8ff234c7bf94a8110e906b2d73506f577a3269"},
{file = "regex-2021.11.2-cp38-cp38-win_amd64.whl", hash = "sha256:f9ee98d658a146cb6507be720a0ce1b44f2abef8fb43c2859791d91aace17cd5"},
{file = "regex-2021.11.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b3794cea825f101fe0df9af8a00f9fad8e119c91e39a28636b95ee2b45b6c2e5"},
{file = "regex-2021.11.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3576e173e7b4f88f683b4de7db0c2af1b209bb48b2bf1c827a6f3564fad59a97"},
{file = "regex-2021.11.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48b4f4810117a9072a5aa70f7fea5f86fa9efbe9a798312e0a05044bd707cc33"},
{file = "regex-2021.11.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f5930d334c2f607711d54761956aedf8137f83f1b764b9640be21d25a976f3a4"},
{file = "regex-2021.11.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:956187ff49db7014ceb31e88fcacf4cf63371e6e44d209cf8816cd4a2d61e11a"},
{file = "regex-2021.11.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17e095f7f96a4b9f24b93c2c915f31a5201a6316618d919b0593afb070a5270e"},
{file = "regex-2021.11.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a56735c35a3704603d9d7b243ee06139f0837bcac2171d9ba1d638ce1df0742a"},
{file = "regex-2021.11.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:adf35d88d9cffc202e6046e4c32e1e11a1d0238b2fcf095c94f109e510ececea"},
{file = "regex-2021.11.2-cp39-cp39-win32.whl", hash = "sha256:30fe317332de0e50195665bc61a27d46e903d682f94042c36b3f88cb84bd7958"},
{file = "regex-2021.11.2-cp39-cp39-win_amd64.whl", hash = "sha256:85289c25f658e3260b00178757c87f033f3d4b3e40aa4abdd4dc875ff11a94fb"},
{file = "regex-2021.11.2.tar.gz", hash = "sha256:5e85dcfc5d0f374955015ae12c08365b565c6f1eaf36dd182476a4d8e5a1cdb7"},
]
requests = [
{file = "requests-2.26.0-py2.py3-none-any.whl", hash = "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24"},
{file = "requests-2.26.0.tar.gz", hash = "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"},
]
six = [
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
@@ -723,6 +914,10 @@ typing-extensions = [
{file = "typing_extensions-3.10.0.2-py3-none-any.whl", hash = "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"},
{file = "typing_extensions-3.10.0.2.tar.gz", hash = "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e"},
]
urllib3 = [
{file = "urllib3-1.26.7-py2.py3-none-any.whl", hash = "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"},
{file = "urllib3-1.26.7.tar.gz", hash = "sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece"},
]
uvloop = [
{file = "uvloop-0.16.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6224f1401025b748ffecb7a6e2652b17768f30b1a6a3f7b44660e5b5b690b12d"},
{file = "uvloop-0.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:30ba9dcbd0965f5c812b7c2112a1ddf60cf904c1c160f398e7eed3a6b82dcd9c"},
@@ -741,6 +936,33 @@ uvloop = [
{file = "uvloop-0.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e5f2e2ff51aefe6c19ee98af12b4ae61f5be456cd24396953244a30880ad861"},
{file = "uvloop-0.16.0.tar.gz", hash = "sha256:f74bc20c7b67d1c27c72601c78cf95be99d5c2cdd4514502b4f3eb0933ff1228"},
]
websockets = [
{file = "websockets-10.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cd8c6f2ec24aedace251017bc7a414525171d4e6578f914acab9349362def4da"},
{file = "websockets-10.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:1f6b814cff6aadc4288297cb3a248614829c6e4ff5556593c44a115e9dd49939"},
{file = "websockets-10.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:01db0ecd1a0ca6702d02a5ed40413e18b7d22f94afb3bbe0d323bac86c42c1c8"},
{file = "websockets-10.0-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:82b17524b1ce6ae7f7dd93e4d18e9b9474071e28b65dbf1dfe9b5767778db379"},
{file = "websockets-10.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:8bbf8660c3f833ddc8b1afab90213f2e672a9ddac6eecb3cde968e6b2807c1c7"},
{file = "websockets-10.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b8176deb6be540a46695960a765a77c28ac8b2e3ef2ec95d50a4f5df901edb1c"},
{file = "websockets-10.0-cp37-cp37m-win32.whl", hash = "sha256:706e200fc7f03bed99ad0574cd1ea8b0951477dd18cc978ccb190683c69dba76"},
{file = "websockets-10.0-cp37-cp37m-win_amd64.whl", hash = "sha256:5b2600e01c7ca6f840c42c747ffbe0254f319594ed108db847eb3d75f4aacb80"},
{file = "websockets-10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:085bb8a6e780d30eaa1ba48ac7f3a6707f925edea787cfb761ce5a39e77ac09b"},
{file = "websockets-10.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:9a4d889162bd48588e80950e07fa5e039eee9deb76a58092e8c3ece96d7ef537"},
{file = "websockets-10.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:b4ade7569b6fd17912452f9c3757d96f8e4044016b6d22b3b8391e641ca50456"},
{file = "websockets-10.0-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:2a43072e434c041a99f2e1eb9b692df0232a38c37c61d00e9f24db79474329e4"},
{file = "websockets-10.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:7f79f02c7f9a8320aff7d3321cd1c7e3a7dbc15d922ac996cca827301ee75238"},
{file = "websockets-10.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:1ac35426fe3e7d3d0fac3d63c8965c76ed67a8fd713937be072bf0ce22808539"},
{file = "websockets-10.0-cp38-cp38-win32.whl", hash = "sha256:ff59c6bdb87b31f7e2d596f09353d5a38c8c8ff571b0e2238e8ee2d55ad68465"},
{file = "websockets-10.0-cp38-cp38-win_amd64.whl", hash = "sha256:d67646ddd17a86117ae21c27005d83c1895c0cef5d7be548b7549646372f868a"},
{file = "websockets-10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:82bd921885231f4a30d9bc550552495b3fc36b1235add6d374e7c65c3babd805"},
{file = "websockets-10.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:7d2e12e4f901f1bc062dfdf91831712c4106ed18a9a4cdb65e2e5f502124ca37"},
{file = "websockets-10.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:71358c7816e2762f3e4af3adf0040f268e219f5a38cb3487a9d0fc2e554fef6a"},
{file = "websockets-10.0-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:fe83b3ec9ef34063d86dfe1029160a85f24a5a94271036e5714a57acfdd089a1"},
{file = "websockets-10.0-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:eb282127e9c136f860c6068a4fba5756eb25e755baffb5940b6f1eae071928b2"},
{file = "websockets-10.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:62160772314920397f9d219147f958b33fa27a12c662d4455c9ccbba9a07e474"},
{file = "websockets-10.0-cp39-cp39-win32.whl", hash = "sha256:e42a1f1e03437b017af341e9bbfdc09252cd48ef32a8c3c3ead769eab3b17368"},
{file = "websockets-10.0-cp39-cp39-win_amd64.whl", hash = "sha256:c5880442f5fc268f1ef6d37b2c152c114deccca73f48e3a8c48004d2f16f4567"},
{file = "websockets-10.0.tar.gz", hash = "sha256:c4fc9a1d242317892590abe5b61a9127f1a61740477bfb121743f290b8054002"},
]
virtualenv = [
{file = "virtualenv-20.10.0-py2.py3-none-any.whl", hash = "sha256:4b02e52a624336eece99c96e3ab7111f469c24ba226a53ec474e8e787b365814"},
{file = "virtualenv-20.10.0.tar.gz", hash = "sha256:576d05b46eace16a9c348085f7d0dc8ef28713a2cabaa1cf0aea41e8f12c9218"},
@@ -764,3 +986,7 @@ yarl = [
{file = "yarl-1.5.1-cp38-cp38-win_amd64.whl", hash = "sha256:9102b59e8337f9874638fcfc9ac3734a0cfadb100e47d55c20d0dc6087fb4692"},
{file = "yarl-1.5.1.tar.gz", hash = "sha256:c22c75b5f394f3d47105045ea551e08a3e804dc7e01b37800ca35b58f856c3d6"},
]
yt-dlp = [
{file = "yt-dlp-2021.10.22.tar.gz", hash = "sha256:a24b9666bd2234149e4da8c4f16bb8e5f746c29428d12ee04fc1c11b5247a307"},
{file = "yt_dlp-2021.10.22-py2.py3-none-any.whl", hash = "sha256:4900fdfffa3de0b09a74f3d7fe98e4d5e21f3ce74db0c106c942614f2ebc3368"},
]
+8 -4
View File
@@ -1,8 +1,8 @@
[tool.poetry]
name = "christmasbot"
version = "0.1.0"
description = ""
authors = ["Your Name <you@example.com>"]
name = "tunebot"
version = "1.0.0"
description = "A very configurable Discord radio"
authors = ["StrNophix <curious@duck.com>", "Matthww <hello@exobot.site>"]
license = "GPL-v3.0"
[tool.poetry.dependencies]
@@ -15,6 +15,10 @@ aioredis = "^2.0.0"
[tool.poetry.dev-dependencies]
black = {version = "^21.9b0", allow-prereleases = true}
redis = "^3.5.3"
aiotube = "^1.3.5"
requests = "^2.26.0"
yt-dlp = "^2021.10.22"
pre-commit = "^2.15.0"
[build-system]
+28
View File
@@ -0,0 +1,28 @@
import json
import subprocess
import sys
import redis
from yt_dlp import YoutubeDL
if len(sys.argv) < 2:
raise Exception("Expected youtube playlist/channel/video")
config_path = sys.argv[1]
config = json.load(open("config.json", "r", encoding="utf-8"))
redis_prefix = config["redis_prefix"]
redis_client = redis.from_url(config["redis_url"])
file = open(config_path, "r")
youtube_dl_opts = {}
vid_urls = subprocess.run(["yt-dlp", "--print", "id", f"{sys.argv[1]}"], capture_output=True).stdout.decode("utf-8")
for vid_url in vid_urls.split("\n"):
if vid_url == "":
continue
vid_url = "https://www.youtube.com/watch?v=" + vid_url
redis_client.sadd(f"{redis_prefix}:playlist", vid_url)
print(vid_url)
+22
View File
@@ -0,0 +1,22 @@
import json
import sys
import redis
from aiotube import Playlist
if len(sys.argv) < 2:
raise Exception("Expected path to file as argument")
config_path = sys.argv[1]
config = json.load(open("config.json", "r", encoding="utf-8"))
redis_prefix = config["redis_prefix"]
redis_client = redis.from_url(config["redis_url"])
file = open(config_path, "r")
for line in file:
playlist = Playlist(line)
for vid in playlist.videos():
yt_url = vid.url
redis_client.sadd(f"{redis_prefix}:playlist", yt_url)
print(yt_url)
View File
+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
from discord.ext.commands import Context
from discord import Embed
from discord.ext.commands import Context
class EmbedGenerator:
+11 -2
View File
@@ -1,13 +1,22 @@
import asyncio
from typing import Dict
from discord.ext.commands import Cog
from bot import ChristmasBot
from bot import TuneBot
class BaseCog(Cog):
def __init__(self, bot: ChristmasBot) -> None:
def __init__(self, bot: TuneBot) -> None:
self.bot = bot
slash_descriptions: Dict[str, str] = self.bot.config["slash_descriptions"]
for command in self.walk_commands():
if brief := slash_descriptions.get(command.qualified_name):
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 context import CustomContext
from discord.ext.commands import CommandError
from context import CustomContext
class EmbeddedCommandException(CommandError):
def __init__(self, embed: Embed) -> None: