First commit - Bring everything from dev cog minus NSFW support

This commit is contained in:
Drapersniper
2020-09-25 16:58:31 +01:00
parent a6ff5b8e9c
commit 8e70b4cd59
39 changed files with 1321 additions and 243 deletions

View File

@@ -3,6 +3,7 @@ from .equalizer import EqualizerUtilities
from .formatting import FormattingUtilities
from .local_tracks import LocalTrackUtilities
from .miscellaneous import MiscellaneousUtilities
from .parsers import ParsingUtilities
from .player import PlayerUtilities
from .playlists import PlaylistUtilities
from .queue import QueueUtilities
@@ -18,6 +19,7 @@ class Utilities(
PlaylistUtilities,
QueueUtilities,
ValidationUtilities,
ParsingUtilities,
metaclass=CompositeMetaClass,
):
"""Class joining all utility subclasses"""

View File

@@ -1,6 +1,7 @@
import asyncio
import contextlib
import logging
from typing import List
import discord

View File

@@ -2,14 +2,16 @@ import datetime
import logging
import math
import re
import time
from typing import List, Optional
import discord
import lavalink
from discord.embeds import EmptyEmbed
from redbot.core.utils import AsyncIter
from discord.embeds import EmptyEmbed
from redbot.core import commands
from redbot.core.utils import AsyncIter
from redbot.core.utils.chat_formatting import box, escape
from ...audio_dataclasses import LocalPath, Query
@@ -98,6 +100,7 @@ class FormattingUtilities(MixinMeta, metaclass=CompositeMetaClass):
await lavalink.connect(ctx.author.voice.channel)
player = lavalink.get_player(ctx.guild.id)
player.store("connect", datetime.datetime.utcnow())
await self.self_deafen(player)
except AttributeError:
return await self.send_embed_msg(ctx, title=_("Connect to a voice channel first."))
except IndexError:
@@ -128,7 +131,9 @@ class FormattingUtilities(MixinMeta, metaclass=CompositeMetaClass):
except IndexError:
search_choice = tracks[-1]
if not hasattr(search_choice, "is_local") and getattr(search_choice, "uri", None):
description = self.get_track_description(search_choice, self.local_folder_current_path)
description = await self.get_track_description(
search_choice, self.local_folder_current_path
)
else:
search_choice = Query.process_input(search_choice, self.local_folder_current_path)
if search_choice.is_local:
@@ -148,14 +153,12 @@ class FormattingUtilities(MixinMeta, metaclass=CompositeMetaClass):
queue_dur = await self.queue_duration(ctx)
queue_total_duration = self.format_time(queue_dur)
before_queue_length = len(player.queue)
query = Query.process_input(search_choice, self.local_folder_current_path)
if not await self.is_query_allowed(
self.config,
ctx.guild,
(
f"{search_choice.title} {search_choice.author} {search_choice.uri} "
f"{str(Query.process_input(search_choice, self.local_folder_current_path))}"
),
ctx,
f"{search_choice.title} {search_choice.author} {search_choice.uri} " f"{str(query)}",
query_obj=query,
):
if IS_DEBUG:
log.debug(f"Query is not allowed in {ctx.guild} ({ctx.guild.id})")
@@ -166,6 +169,13 @@ class FormattingUtilities(MixinMeta, metaclass=CompositeMetaClass):
elif guild_data["maxlength"] > 0:
if self.is_track_length_allowed(search_choice, guild_data["maxlength"]):
search_choice.extras.update(
{
"enqueue_time": int(time.time()),
"vc": player.channel.id,
"requester": ctx.author.id,
}
)
player.add(ctx.author, search_choice)
player.maybe_shuffle()
self.bot.dispatch(
@@ -174,6 +184,13 @@ class FormattingUtilities(MixinMeta, metaclass=CompositeMetaClass):
else:
return await self.send_embed_msg(ctx, title=_("Track exceeds maximum length."))
else:
search_choice.extras.update(
{
"enqueue_time": int(time.time()),
"vc": player.channel.id,
"requester": ctx.author.id,
}
)
player.add(ctx.author, search_choice)
player.maybe_shuffle()
self.bot.dispatch(
@@ -191,9 +208,11 @@ class FormattingUtilities(MixinMeta, metaclass=CompositeMetaClass):
await player.play()
return await self.send_embed_msg(ctx, embed=songembed)
def _format_search_options(self, search_choice):
async def _format_search_options(self, search_choice):
query = Query.process_input(search_choice, self.local_folder_current_path)
description = self.get_track_description(search_choice, self.local_folder_current_path)
description = await self.get_track_description(
search_choice, self.local_folder_current_path
)
return description, query
async def _build_search_page(
@@ -259,10 +278,10 @@ class FormattingUtilities(MixinMeta, metaclass=CompositeMetaClass):
)
return embed
def get_track_description(
async def get_track_description(
self, track, local_folder_current_path, shorten=False
) -> Optional[str]:
"""Get the user facing formatted track name"""
"""Get the user facing formatted track name."""
string = None
if track and getattr(track, "uri", None):
query = Query.process_input(track.uri, local_folder_current_path)
@@ -299,7 +318,13 @@ class FormattingUtilities(MixinMeta, metaclass=CompositeMetaClass):
string = "{}...".format((string[:40]).rstrip(" "))
string = f'**{escape(f"{string}", formatting=True)}**'
else:
if track.author.lower() not in track.title.lower():
if track.is_stream:
icy = await self.icyparser(track.uri)
if icy:
title = icy
else:
title = f"{track.title} - {track.author}"
elif track.author.lower() not in track.title.lower():
title = f"{track.title} - {track.author}"
else:
title = track.title
@@ -315,8 +340,10 @@ class FormattingUtilities(MixinMeta, metaclass=CompositeMetaClass):
string = f'**{escape(f"{string}", formatting=True)}**'
return string
def get_track_description_unformatted(self, track, local_folder_current_path) -> Optional[str]:
"""Get the user facing unformatted track name"""
async def get_track_description_unformatted(
self, track, local_folder_current_path
) -> Optional[str]:
"""Get the user facing unformatted track name."""
if track and hasattr(track, "uri"):
query = Query.process_input(track.uri, local_folder_current_path)
if query.is_local or "localtracks/" in track.uri:
@@ -332,7 +359,13 @@ class FormattingUtilities(MixinMeta, metaclass=CompositeMetaClass):
else:
return query.to_string_user()
else:
if track.author.lower() not in track.title.lower():
if track.is_stream:
icy = await self.icyparser(track.uri)
if icy:
title = icy
else:
title = f"{track.title} - {track.author}"
elif track.author.lower() not in track.title.lower():
title = f"{track.title} - {track.author}"
else:
title = track.title
@@ -342,7 +375,7 @@ class FormattingUtilities(MixinMeta, metaclass=CompositeMetaClass):
return None
def format_playlist_picker_data(self, pid, pname, ptracks, pauthor, scope) -> str:
"""Format the values into a pretified codeblock"""
"""Format the values into a pretified codeblock."""
author = self.bot.get_user(pauthor) or pauthor or _("Unknown")
line = _(
" - Name: <{pname}>\n"
@@ -359,9 +392,9 @@ class FormattingUtilities(MixinMeta, metaclass=CompositeMetaClass):
player = lavalink.get_player(ctx.guild.id)
paused = player.paused
pos = player.position
dur = player.current.length
dur = getattr(player.current, "length", player.position or 1)
sections = 12
loc_time = round((pos / dur) * sections)
loc_time = round((pos / dur if dur != 0 else pos) * sections)
bar = "\N{BOX DRAWINGS HEAVY HORIZONTAL}"
seek = "\N{RADIO BUTTON}"
if paused:

View File

@@ -1,16 +1,17 @@
import contextlib
import logging
from pathlib import Path
from typing import List, Union
import lavalink
from fuzzywuzzy import process
from redbot.core.utils import AsyncIter
from redbot.core import commands
from redbot.core.utils import AsyncIter
from ...errors import TrackEnqueueError
from ...audio_dataclasses import LocalPath, Query
from ...errors import TrackEnqueueError
from ..abc import MixinMeta
from ..cog_utils import CompositeMetaClass, _
@@ -32,7 +33,7 @@ class LocalTrackUtilities(MixinMeta, metaclass=CompositeMetaClass):
)
async def get_localtrack_folder_list(self, ctx: commands.Context, query: Query) -> List[Query]:
"""Return a list of folders per the provided query"""
"""Return a list of folders per the provided query."""
if not await self.localtracks_folder_exists(ctx):
return []
query = Query.process_input(query, self.local_folder_current_path)
@@ -49,7 +50,7 @@ class LocalTrackUtilities(MixinMeta, metaclass=CompositeMetaClass):
async def get_localtrack_folder_tracks(
self, ctx, player: lavalink.player_manager.Player, query: Query
) -> List[lavalink.rest_api.Track]:
"""Return a list of tracks per the provided query"""
"""Return a list of tracks per the provided query."""
if not await self.localtracks_folder_exists(ctx) or self.api_interface is None:
return []

View File

@@ -5,21 +5,22 @@ import functools
import json
import logging
import re
from typing import Any, Final, MutableMapping, Union, cast, Mapping, Pattern
from typing import Any, Final, Mapping, MutableMapping, Pattern, Union, cast
import discord
import lavalink
from discord.embeds import EmptyEmbed
from redbot.core.utils import AsyncIter
from discord.embeds import EmptyEmbed
from redbot.core import bank, commands
from redbot.core.commands import Context
from redbot.core.utils import AsyncIter
from redbot.core.utils.chat_formatting import humanize_number
from ..abc import MixinMeta
from ..cog_utils import CompositeMetaClass, _, _SCHEMA_VERSION
from ...apis.playlist_interface import get_all_playlist_for_migration23
from ...utils import PlaylistScope
from ...utils import PlaylistScope, task_callback
from ..abc import MixinMeta
from ..cog_utils import CompositeMetaClass, _
log = logging.getLogger("red.cogs.Audio.cog.Utilities.miscellaneous")
@@ -32,7 +33,9 @@ class MiscellaneousUtilities(MixinMeta, metaclass=CompositeMetaClass):
self, message: discord.Message, emoji: MutableMapping = None
) -> asyncio.Task:
"""Non blocking version of clear_react."""
return self.bot.loop.create_task(self.clear_react(message, emoji))
task = self.bot.loop.create_task(self.clear_react(message, emoji))
task.add_done_callback(task_callback)
return task
async def maybe_charge_requester(self, ctx: commands.Context, jukebox_price: int) -> bool:
jukebox = await self.config.guild(ctx.guild).jukebox()

View File

@@ -0,0 +1,35 @@
import logging
import re
import struct
from typing import Final, Optional
import aiohttp
from ..abc import MixinMeta
from ..cog_utils import CompositeMetaClass
log = logging.getLogger("red.cogs.Audio.cog.Utilities.Parsing")
STREAM_TITLE: Final[re.Pattern] = re.compile(br"StreamTitle='([^']*)';")
class ParsingUtilities(MixinMeta, metaclass=CompositeMetaClass):
async def icyparser(self, url: str) -> Optional[str]:
try:
async with self.session.get(url, headers={"Icy-MetaData": "1"}) as resp:
metaint = int(resp.headers["icy-metaint"])
for _ in range(5):
await resp.content.readexactly(metaint)
metadata_length = struct.unpack("B", await resp.content.readexactly(1))[0] * 16
metadata = await resp.content.readexactly(metadata_length)
m = re.search(STREAM_TITLE, metadata.rstrip(b"\0"))
if m:
title = m.group(1)
if title:
title = title.decode("utf-8", errors="replace")
return title
else:
return None
except (KeyError, aiohttp.ClientConnectionError, aiohttp.ClientResponseError):
return None

View File

@@ -1,14 +1,15 @@
import logging
import time
from typing import List, Optional, Tuple, Union
import aiohttp
import discord
import lavalink
from discord.embeds import EmptyEmbed
from redbot.core.utils import AsyncIter
from discord.embeds import EmptyEmbed
from redbot.core import commands
from redbot.core.utils import AsyncIter
from redbot.core.utils.chat_formatting import bold, escape
from ...audio_dataclasses import _PARTIALLY_SUPPORTED_MUSIC_EXT, Query
@@ -42,7 +43,7 @@ class PlayerUtilities(MixinMeta, metaclass=CompositeMetaClass):
self._error_timer[guild] = now
return self._error_counter[guild] >= 5
def get_active_player_count(self) -> Tuple[Optional[str], int]:
async def get_active_player_count(self) -> Tuple[Optional[str], int]:
try:
current = next(
(
@@ -52,7 +53,7 @@ class PlayerUtilities(MixinMeta, metaclass=CompositeMetaClass):
),
None,
)
get_single_title = self.get_track_description_unformatted(
get_single_title = await self.get_track_description_unformatted(
current, self.local_folder_current_path
)
playing_servers = len(lavalink.active_players())
@@ -149,7 +150,7 @@ class PlayerUtilities(MixinMeta, metaclass=CompositeMetaClass):
elif autoplay and not player.queue:
embed = discord.Embed(
title=_("Track Skipped"),
description=self.get_track_description(
description=await self.get_track_description(
player.current, self.local_folder_current_path
),
)
@@ -184,7 +185,7 @@ class PlayerUtilities(MixinMeta, metaclass=CompositeMetaClass):
else:
embed = discord.Embed(
title=_("Track Skipped"),
description=self.get_track_description(
description=await self.get_track_description(
player.current, self.local_folder_current_path
),
)
@@ -208,6 +209,13 @@ class PlayerUtilities(MixinMeta, metaclass=CompositeMetaClass):
except (IndexError, KeyError):
return False
async def self_deafen(self, player: lavalink.Player) -> None:
guild_id = player.channel.guild.id
channel_id = player.channel.id
node = player.manager.node
voice_ws = node.get_voice_ws(guild_id)
await voice_ws.voice_state(guild_id, channel_id, self_deaf=True)
async def _get_spotify_tracks(
self, ctx: commands.Context, query: Query, forced: bool = False
) -> Union[discord.Message, List[lavalink.Track], lavalink.Track]:
@@ -354,9 +362,7 @@ class PlayerUtilities(MixinMeta, metaclass=CompositeMetaClass):
playlist_url = None
seek = 0
if type(query) is not list:
if not await self.is_query_allowed(
self.config, ctx.guild, f"{query}", query_obj=query
):
if not await self.is_query_allowed(self.config, ctx, f"{query}", query_obj=query):
raise QueryUnauthorized(
_("{query} is not an allowed query.").format(query=query.to_string_user())
)
@@ -423,13 +429,12 @@ class PlayerUtilities(MixinMeta, metaclass=CompositeMetaClass):
async for track in AsyncIter(tracks):
if len(player.queue) >= 10000:
continue
query = Query.process_input(track, self.local_folder_current_path)
if not await self.is_query_allowed(
self.config,
ctx.guild,
(
f"{track.title} {track.author} {track.uri} "
f"{str(Query.process_input(track, self.local_folder_current_path))}"
),
ctx,
f"{track.title} {track.author} {track.uri} " f"{str(query)}",
query_obj=query,
):
if IS_DEBUG:
log.debug(f"Query is not allowed in {ctx.guild} ({ctx.guild.id})")
@@ -437,6 +442,13 @@ class PlayerUtilities(MixinMeta, metaclass=CompositeMetaClass):
elif guild_data["maxlength"] > 0:
if self.is_track_length_allowed(track, guild_data["maxlength"]):
track_len += 1
track.extras.update(
{
"enqueue_time": int(time.time()),
"vc": player.channel.id,
"requester": ctx.author.id,
}
)
player.add(ctx.author, track)
self.bot.dispatch(
"red_audio_track_enqueue", player.channel.guild, track, ctx.author
@@ -444,6 +456,13 @@ class PlayerUtilities(MixinMeta, metaclass=CompositeMetaClass):
else:
track_len += 1
track.extras.update(
{
"enqueue_time": int(time.time()),
"vc": player.channel.id,
"requester": ctx.author.id,
}
)
player.add(ctx.author, track)
self.bot.dispatch(
"red_audio_track_enqueue", player.channel.guild, track, ctx.author
@@ -499,13 +518,15 @@ class PlayerUtilities(MixinMeta, metaclass=CompositeMetaClass):
)
if seek and seek > 0:
single_track.start_timestamp = seek * 1000
query = Query.process_input(single_track, self.local_folder_current_path)
if not await self.is_query_allowed(
self.config,
ctx.guild,
ctx,
(
f"{single_track.title} {single_track.author} {single_track.uri} "
f"{str(Query.process_input(single_track, self.local_folder_current_path))}"
f"{str(query)}"
),
query_obj=query,
):
if IS_DEBUG:
log.debug(f"Query is not allowed in {ctx.guild} ({ctx.guild.id})")
@@ -515,6 +536,13 @@ class PlayerUtilities(MixinMeta, metaclass=CompositeMetaClass):
)
elif guild_data["maxlength"] > 0:
if self.is_track_length_allowed(single_track, guild_data["maxlength"]):
single_track.extras.update(
{
"enqueue_time": int(time.time()),
"vc": player.channel.id,
"requester": ctx.author.id,
}
)
player.add(ctx.author, single_track)
player.maybe_shuffle()
self.bot.dispatch(
@@ -530,6 +558,13 @@ class PlayerUtilities(MixinMeta, metaclass=CompositeMetaClass):
)
else:
single_track.extras.update(
{
"enqueue_time": int(time.time()),
"vc": player.channel.id,
"requester": ctx.author.id,
}
)
player.add(ctx.author, single_track)
player.maybe_shuffle()
self.bot.dispatch(
@@ -542,7 +577,9 @@ class PlayerUtilities(MixinMeta, metaclass=CompositeMetaClass):
if await self.bot.is_owner(ctx.author):
desc = _("Please check your console or logs for details.")
return await self.send_embed_msg(ctx, title=title, description=desc)
description = self.get_track_description(single_track, self.local_folder_current_path)
description = await self.get_track_description(
single_track, self.local_folder_current_path
)
embed = discord.Embed(title=_("Track Enqueued"), description=description)
if not guild_data["shuffle"] and queue_dur > 0:
embed.set_footer(
@@ -588,6 +625,7 @@ class PlayerUtilities(MixinMeta, metaclass=CompositeMetaClass):
lock=self.update_player_lock,
notifier=notifier,
forced=forced,
query_global=await self.config.global_db_enabled(),
)
except SpotifyFetchError as error:
self.update_player_lock(ctx, False)
@@ -657,6 +695,7 @@ class PlayerUtilities(MixinMeta, metaclass=CompositeMetaClass):
and len(player.queue) == 0
):
await player.move_to(user_channel)
await self.self_deafen(player)
return True
else:
return False

View File

@@ -4,14 +4,15 @@ import datetime
import json
import logging
import math
from typing import List, MutableMapping, Optional, Tuple, Union
import discord
import lavalink
from discord.embeds import EmptyEmbed
from redbot.core.utils import AsyncIter
from discord.embeds import EmptyEmbed
from redbot.core import commands
from redbot.core.utils import AsyncIter
from redbot.core.utils.chat_formatting import box
from redbot.core.utils.menus import start_adding_reactions
from redbot.core.utils.predicates import ReactionPredicate
@@ -513,6 +514,7 @@ class PlaylistUtilities(MixinMeta, metaclass=CompositeMetaClass):
await lavalink.connect(ctx.author.voice.channel)
player = lavalink.get_player(ctx.guild.id)
player.store("connect", datetime.datetime.utcnow())
await self.self_deafen(player)
except IndexError:
await self.send_embed_msg(
ctx,

View File

@@ -1,13 +1,14 @@
import logging
import math
from typing import List, Tuple
import discord
import lavalink
from fuzzywuzzy import process
from redbot.core.utils import AsyncIter
from fuzzywuzzy import process
from redbot.core import commands
from redbot.core.utils import AsyncIter
from redbot.core.utils.chat_formatting import humanize_number
from ...audio_dataclasses import LocalPath, Query
@@ -46,7 +47,7 @@ class QueueUtilities(MixinMeta, metaclass=CompositeMetaClass):
dur = self.format_time(player.current.length)
query = Query.process_input(player.current, self.local_folder_current_path)
current_track_description = self.get_track_description(
current_track_description = await self.get_track_description(
player.current, self.local_folder_current_path
)
if query.is_stream:
@@ -65,7 +66,7 @@ class QueueUtilities(MixinMeta, metaclass=CompositeMetaClass):
):
req_user = track.requester
track_idx = i + 1
track_description = self.get_track_description(
track_description = await self.get_track_description(
track, self.local_folder_current_path, shorten=True
)
queue_list += f"`{track_idx}.` {track_description}, "
@@ -76,6 +77,7 @@ class QueueUtilities(MixinMeta, metaclass=CompositeMetaClass):
title=_("Queue for __{guild_name}__").format(guild_name=ctx.guild.name),
description=queue_list,
)
if await self.config.guild(ctx.guild).thumbnail() and player.current.thumbnail:
embed.set_thumbnail(url=player.current.thumbnail)
queue_dur = await self.queue_duration(ctx)

View File

@@ -1,11 +1,13 @@
import logging
import re
from typing import Final, List, Set, Pattern
from typing import Final, List, Optional, Pattern, Set, Union
from urllib.parse import urlparse
import discord
from redbot.core import Config
from redbot.core.commands import Context
from ...audio_dataclasses import Query
from ..abc import MixinMeta
@@ -54,11 +56,21 @@ class ValidationUtilities(MixinMeta, metaclass=CompositeMetaClass):
return not (channel.user_limit == 0 or channel.user_limit > len(channel.members))
async def is_query_allowed(
self, config: Config, guild: discord.Guild, query: str, query_obj: Query = None
self,
config: Config,
ctx_or_channel: Optional[Union[Context, discord.TextChannel]],
query: str,
query_obj: Query,
) -> bool:
"""Checks if the query is allowed in this server or globally"""
query = query.lower().strip()
"""Checks if the query is allowed in this server or globally."""
if ctx_or_channel:
guild = ctx_or_channel.guild
channel = (
ctx_or_channel.channel if isinstance(ctx_or_channel, Context) else ctx_or_channel
)
query = query.lower().strip()
else:
guild = None
if query_obj is not None:
query = query_obj.lavalink_query.replace("ytsearch:", "youtubesearch").replace(
"scsearch:", "soundcloudsearch"