mirror of
https://github.com/Cog-Creators/Red-DiscordBot.git
synced 2025-11-06 11:18:54 -05:00
Audio changes (#5593)
* Squash tested commits * remove the code jack is concerned about * Apply suggestions from code review * more log lines * more log lines * format * formatting * style(Rename Xms and Xmx mentions): Rename Xms and Xmx to more use friendly names - Change Xms to "Initial Heapsize" - Change Xmx to "Max Heapsize" Signed-off-by: Draper <27962761+Drapersniper@users.noreply.github.com>
This commit is contained in:
parent
5a5b22003f
commit
9ec85d4819
@ -76,7 +76,7 @@ class GlobalCacheWrapper:
|
|||||||
) as r:
|
) as r:
|
||||||
search_response = await r.json(loads=json.loads)
|
search_response = await r.json(loads=json.loads)
|
||||||
log.trace(
|
log.trace(
|
||||||
"GET || Ping %s || Status code %d || %s",
|
"GET || Ping %s || Status code %s || %s",
|
||||||
r.headers.get("x-process-time"),
|
r.headers.get("x-process-time"),
|
||||||
r.status,
|
r.status,
|
||||||
query,
|
query,
|
||||||
@ -107,7 +107,7 @@ class GlobalCacheWrapper:
|
|||||||
) as r:
|
) as r:
|
||||||
search_response = await r.json(loads=json.loads)
|
search_response = await r.json(loads=json.loads)
|
||||||
log.trace(
|
log.trace(
|
||||||
"GET/spotify || Ping %s || Status code %d || %s - %s",
|
"GET/spotify || Ping %s || Status code %s || %s - %s",
|
||||||
r.headers.get("x-process-time"),
|
r.headers.get("x-process-time"),
|
||||||
r.status,
|
r.status,
|
||||||
title,
|
title,
|
||||||
@ -143,7 +143,7 @@ class GlobalCacheWrapper:
|
|||||||
) as r:
|
) as r:
|
||||||
await r.read()
|
await r.read()
|
||||||
log.trace(
|
log.trace(
|
||||||
"GET || Ping %s || Status code %d || %s",
|
"GET || Ping %s || Status code %s || %s",
|
||||||
r.headers.get("x-process-time"),
|
r.headers.get("x-process-time"),
|
||||||
r.status,
|
r.status,
|
||||||
query,
|
query,
|
||||||
|
|||||||
@ -147,7 +147,7 @@ class AudioAPIInterface:
|
|||||||
lock_author = ctx.author if ctx else None
|
lock_author = ctx.author if ctx else None
|
||||||
async with self._lock:
|
async with self._lock:
|
||||||
if lock_id in self._tasks:
|
if lock_id in self._tasks:
|
||||||
log.trace("Running database writes for %d (%s)", lock_id, lock_author)
|
log.trace("Running database writes for %s (%s)", lock_id, lock_author)
|
||||||
try:
|
try:
|
||||||
tasks = self._tasks[lock_id]
|
tasks = self._tasks[lock_id]
|
||||||
tasks = [self.route_tasks(a, tasks[a]) for a in tasks]
|
tasks = [self.route_tasks(a, tasks[a]) for a in tasks]
|
||||||
@ -155,10 +155,10 @@ class AudioAPIInterface:
|
|||||||
del self._tasks[lock_id]
|
del self._tasks[lock_id]
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
log.verbose(
|
log.verbose(
|
||||||
"Failed database writes for %d (%s)", lock_id, lock_author, exc_info=exc
|
"Failed database writes for %s (%s)", lock_id, lock_author, exc_info=exc
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
log.trace("Completed database writes for %d (%s)", lock_id, lock_author)
|
log.trace("Completed database writes for %s (%s)", lock_id, lock_author)
|
||||||
|
|
||||||
async def run_all_pending_tasks(self) -> None:
|
async def run_all_pending_tasks(self) -> None:
|
||||||
"""Run all pending tasks left in the cache, called on cog_unload."""
|
"""Run all pending tasks left in the cache, called on cog_unload."""
|
||||||
@ -618,7 +618,7 @@ class AudioAPIInterface:
|
|||||||
query_obj=query,
|
query_obj=query,
|
||||||
):
|
):
|
||||||
has_not_allowed = True
|
has_not_allowed = True
|
||||||
log.debug("Query is not allowed in %r (%d)", ctx.guild.name, ctx.guild.id)
|
log.debug("Query is not allowed in %r (%s)", ctx.guild.name, ctx.guild.id)
|
||||||
continue
|
continue
|
||||||
track_list.append(single_track)
|
track_list.append(single_track)
|
||||||
if enqueue:
|
if enqueue:
|
||||||
@ -973,7 +973,7 @@ class AudioAPIInterface:
|
|||||||
and not query.local_track_path.exists()
|
and not query.local_track_path.exists()
|
||||||
):
|
):
|
||||||
continue
|
continue
|
||||||
notify_channel = self.bot.get_channel(notify_channel_id)
|
notify_channel = player.guild.get_channel(notify_channel_id)
|
||||||
if not await self.cog.is_query_allowed(
|
if not await self.cog.is_query_allowed(
|
||||||
self.config,
|
self.config,
|
||||||
notify_channel,
|
notify_channel,
|
||||||
@ -981,7 +981,7 @@ class AudioAPIInterface:
|
|||||||
query_obj=query,
|
query_obj=query,
|
||||||
):
|
):
|
||||||
log.debug(
|
log.debug(
|
||||||
"Query is not allowed in %r (%d)", player.guild.name, player.guild.id
|
"Query is not allowed in %r (%s)", player.guild.name, player.guild.id
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
valid = True
|
valid = True
|
||||||
|
|||||||
@ -175,7 +175,7 @@ class PlaylistWrapper:
|
|||||||
try:
|
try:
|
||||||
playlist_id = int(playlist_id)
|
playlist_id = int(playlist_id)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
log.verbose("Failed converting playlist_id to int", exc_info=exc)
|
log.trace("Failed converting playlist_id to int", exc_info=exc)
|
||||||
playlist_id = -1
|
playlist_id = -1
|
||||||
|
|
||||||
output = []
|
output = []
|
||||||
|
|||||||
@ -102,7 +102,7 @@ class SpotifyWrapper:
|
|||||||
async with self.session.request("GET", url, params=params, headers=headers) as r:
|
async with self.session.request("GET", url, params=params, headers=headers) as r:
|
||||||
data = await r.json(loads=json.loads)
|
data = await r.json(loads=json.loads)
|
||||||
if r.status != 200:
|
if r.status != 200:
|
||||||
log.verbose("Issue making GET request to %r: [%d] %r", url, r.status, data)
|
log.verbose("Issue making GET request to %r: [%s] %r", url, r.status, data)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
async def update_token(self, new_token: Mapping[str, str]):
|
async def update_token(self, new_token: Mapping[str, str]):
|
||||||
@ -156,7 +156,7 @@ class SpotifyWrapper:
|
|||||||
async with self.session.post(url, data=payload, headers=headers) as r:
|
async with self.session.post(url, data=payload, headers=headers) as r:
|
||||||
data = await r.json(loads=json.loads)
|
data = await r.json(loads=json.loads)
|
||||||
if r.status != 200:
|
if r.status != 200:
|
||||||
log.verbose("Issue making POST request to %r: [%d] %r", url, r.status, data)
|
log.verbose("Issue making POST request to %r: [%s] %r", url, r.status, data)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
async def make_get_call(self, url: str, params: MutableMapping) -> MutableMapping:
|
async def make_get_call(self, url: str, params: MutableMapping) -> MutableMapping:
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import json
|
|||||||
|
|
||||||
from collections import Counter, defaultdict
|
from collections import Counter, defaultdict
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Mapping
|
from typing import Mapping, Dict
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
import discord
|
import discord
|
||||||
@ -14,8 +14,14 @@ from redbot.core.bot import Red
|
|||||||
from redbot.core.commands import Cog
|
from redbot.core.commands import Cog
|
||||||
from redbot.core.data_manager import cog_data_path
|
from redbot.core.data_manager import cog_data_path
|
||||||
from redbot.core.i18n import Translator, cog_i18n
|
from redbot.core.i18n import Translator, cog_i18n
|
||||||
|
from redbot.core.utils.antispam import AntiSpam
|
||||||
|
|
||||||
from ..utils import CacheLevel, PlaylistScope
|
from ..utils import (
|
||||||
|
CacheLevel,
|
||||||
|
PlaylistScope,
|
||||||
|
DEFAULT_LAVALINK_YAML,
|
||||||
|
DEFAULT_LAVALINK_SETTINGS,
|
||||||
|
)
|
||||||
from . import abc, cog_utils, commands, events, tasks, utilities
|
from . import abc, cog_utils, commands, events, tasks, utilities
|
||||||
from .cog_utils import CompositeMetaClass
|
from .cog_utils import CompositeMetaClass
|
||||||
|
|
||||||
@ -33,12 +39,9 @@ class Audio(
|
|||||||
):
|
):
|
||||||
"""Play audio through voice channels."""
|
"""Play audio through voice channels."""
|
||||||
|
|
||||||
_default_lavalink_settings = {
|
llset_captcha_intervals = [
|
||||||
"host": "localhost",
|
(datetime.timedelta(days=1), 1),
|
||||||
"rest_port": 2333,
|
]
|
||||||
"ws_port": 2333,
|
|
||||||
"password": "youshallnotpass",
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, bot: Red):
|
def __init__(self, bot: Red):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@ -46,7 +49,7 @@ class Audio(
|
|||||||
self.config = Config.get_conf(self, 2711759130, force_registration=True)
|
self.config = Config.get_conf(self, 2711759130, force_registration=True)
|
||||||
|
|
||||||
self.api_interface = None
|
self.api_interface = None
|
||||||
self.player_manager = None
|
self.managed_node_controller = None
|
||||||
self.playlist_api = None
|
self.playlist_api = None
|
||||||
self.local_folder_current_path = None
|
self.local_folder_current_path = None
|
||||||
self.db_conn = None
|
self.db_conn = None
|
||||||
@ -61,6 +64,7 @@ class Audio(
|
|||||||
self._dj_role_cache = {}
|
self._dj_role_cache = {}
|
||||||
self.skip_votes = {}
|
self.skip_votes = {}
|
||||||
self.play_lock = {}
|
self.play_lock = {}
|
||||||
|
self.antispam: Dict[int, Dict[str, AntiSpam]] = defaultdict(lambda: defaultdict(AntiSpam))
|
||||||
|
|
||||||
self.lavalink_connect_task = None
|
self.lavalink_connect_task = None
|
||||||
self._restore_task = None
|
self._restore_task = None
|
||||||
@ -88,7 +92,7 @@ class Audio(
|
|||||||
"can_delete": False,
|
"can_delete": False,
|
||||||
}
|
}
|
||||||
self._ll_guild_updates = set()
|
self._ll_guild_updates = set()
|
||||||
self._diconnected_shard = set()
|
self._disconnected_shard = set()
|
||||||
self._last_ll_update = datetime.datetime.now(datetime.timezone.utc)
|
self._last_ll_update = datetime.datetime.now(datetime.timezone.utc)
|
||||||
|
|
||||||
default_global = dict(
|
default_global = dict(
|
||||||
@ -107,7 +111,8 @@ class Audio(
|
|||||||
url_keyword_blacklist=[],
|
url_keyword_blacklist=[],
|
||||||
url_keyword_whitelist=[],
|
url_keyword_whitelist=[],
|
||||||
java_exc_path="java",
|
java_exc_path="java",
|
||||||
**self._default_lavalink_settings,
|
**DEFAULT_LAVALINK_YAML,
|
||||||
|
**DEFAULT_LAVALINK_SETTINGS,
|
||||||
)
|
)
|
||||||
|
|
||||||
default_guild = dict(
|
default_guild = dict(
|
||||||
|
|||||||
@ -6,7 +6,18 @@ import datetime
|
|||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from collections import Counter, defaultdict
|
from collections import Counter, defaultdict
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Set, TYPE_CHECKING, Any, List, Mapping, MutableMapping, Optional, Tuple, Union
|
from typing import (
|
||||||
|
Set,
|
||||||
|
TYPE_CHECKING,
|
||||||
|
Any,
|
||||||
|
List,
|
||||||
|
Mapping,
|
||||||
|
MutableMapping,
|
||||||
|
Optional,
|
||||||
|
Tuple,
|
||||||
|
Union,
|
||||||
|
Dict,
|
||||||
|
)
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
import discord
|
import discord
|
||||||
@ -15,6 +26,7 @@ import lavalink
|
|||||||
from redbot.core import Config, commands
|
from redbot.core import Config, commands
|
||||||
from redbot.core.bot import Red
|
from redbot.core.bot import Red
|
||||||
from redbot.core.commands import Context
|
from redbot.core.commands import Context
|
||||||
|
from redbot.core.utils.antispam import AntiSpam
|
||||||
from redbot.core.utils.dbtools import APSWConnectionWrapper
|
from redbot.core.utils.dbtools import APSWConnectionWrapper
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@ -35,11 +47,13 @@ class MixinMeta(ABC):
|
|||||||
bot: Red
|
bot: Red
|
||||||
config: Config
|
config: Config
|
||||||
api_interface: Optional["AudioAPIInterface"]
|
api_interface: Optional["AudioAPIInterface"]
|
||||||
player_manager: Optional["ServerManager"]
|
managed_node_controller: Optional["ServerManager"]
|
||||||
playlist_api: Optional["PlaylistWrapper"]
|
playlist_api: Optional["PlaylistWrapper"]
|
||||||
local_folder_current_path: Optional[Path]
|
local_folder_current_path: Optional[Path]
|
||||||
db_conn: Optional[APSWConnectionWrapper]
|
db_conn: Optional[APSWConnectionWrapper]
|
||||||
session: aiohttp.ClientSession
|
session: aiohttp.ClientSession
|
||||||
|
antispam: Dict[int, Dict[str, AntiSpam]]
|
||||||
|
llset_captcha_intervals: List[Tuple[datetime.timedelta, int]]
|
||||||
|
|
||||||
skip_votes: MutableMapping[int, Set[int]]
|
skip_votes: MutableMapping[int, Set[int]]
|
||||||
play_lock: MutableMapping[int, bool]
|
play_lock: MutableMapping[int, bool]
|
||||||
@ -64,17 +78,21 @@ class MixinMeta(ABC):
|
|||||||
cog_ready_event: asyncio.Event
|
cog_ready_event: asyncio.Event
|
||||||
_ws_resume: defaultdict[Any, asyncio.Event]
|
_ws_resume: defaultdict[Any, asyncio.Event]
|
||||||
_ws_op_codes: defaultdict[int, asyncio.LifoQueue]
|
_ws_op_codes: defaultdict[int, asyncio.LifoQueue]
|
||||||
_default_lavalink_settings: Mapping
|
|
||||||
permission_cache = discord.Permissions
|
permission_cache = discord.Permissions
|
||||||
|
|
||||||
_last_ll_update: datetime.datetime
|
_last_ll_update: datetime.datetime
|
||||||
_ll_guild_updates: Set[int]
|
_ll_guild_updates: Set[int]
|
||||||
_diconnected_shard: Set[int]
|
_disconnected_shard: Set[int]
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
async def command_llsetup(self, ctx: commands.Context):
|
async def command_llsetup(self, ctx: commands.Context):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
@commands.command()
|
||||||
|
@abstractmethod
|
||||||
|
async def command_audioset_restart(self, ctx: commands.Context):
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
async def maybe_reset_error_counter(self, player: lavalink.Player) -> None:
|
async def maybe_reset_error_counter(self, player: lavalink.Player) -> None:
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
@ -112,7 +130,7 @@ class MixinMeta(ABC):
|
|||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def lavalink_restart_connect(self) -> None:
|
def lavalink_restart_connect(self, manual: bool = False) -> None:
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
|
|||||||
@ -9,48 +9,14 @@ from redbot.core import commands
|
|||||||
|
|
||||||
from ..converters import get_lazy_converter, get_playlist_converter
|
from ..converters import get_lazy_converter, get_playlist_converter
|
||||||
|
|
||||||
__version__ = VersionInfo.from_json({"major": 2, "minor": 4, "micro": 0, "releaselevel": "final"})
|
__version__ = VersionInfo.from_json({"major": 2, "minor": 5, "micro": 0, "releaselevel": "final"})
|
||||||
|
|
||||||
__author__ = ["aikaterna", "Draper"]
|
__author__ = ["aikaterna", "Draper"]
|
||||||
|
|
||||||
_SCHEMA_VERSION: Final[int] = 3
|
_SCHEMA_VERSION: Final[int] = 3
|
||||||
_OWNER_NOTIFICATION: Final[int] = 1
|
_OWNER_NOTIFICATION: Final[int] = 1
|
||||||
|
|
||||||
LazyGreedyConverter = get_lazy_converter("--")
|
LazyGreedyConverter = get_lazy_converter("--")
|
||||||
PlaylistConverter = get_playlist_converter()
|
PlaylistConverter = get_playlist_converter()
|
||||||
HUMANIZED_PERM = {
|
|
||||||
"create_instant_invite": "Create Instant Invite",
|
|
||||||
"kick_members": "Kick Members",
|
|
||||||
"ban_members": "Ban Members",
|
|
||||||
"administrator": "Administrator",
|
|
||||||
"manage_channels": "Manage Channels",
|
|
||||||
"manage_guild": "Manage Server",
|
|
||||||
"add_reactions": "Add Reactions",
|
|
||||||
"view_audit_log": "View Audit Log",
|
|
||||||
"priority_speaker": "Priority Speaker",
|
|
||||||
"stream": "Go Live",
|
|
||||||
"read_messages": "Read Text Channels & See Voice Channels",
|
|
||||||
"send_messages": "Send Messages",
|
|
||||||
"send_tts_messages": "Send TTS Messages",
|
|
||||||
"manage_messages": "Manage Messages",
|
|
||||||
"embed_links": "Embed Links",
|
|
||||||
"attach_files": "Attach Files",
|
|
||||||
"read_message_history": "Read Message History",
|
|
||||||
"mention_everyone": "Mention @everyone, @here, and All Roles",
|
|
||||||
"external_emojis": "Use External Emojis",
|
|
||||||
"view_guild_insights": "View Server Insights",
|
|
||||||
"connect": "Connect",
|
|
||||||
"speak": "Speak",
|
|
||||||
"mute_members": "Mute Members",
|
|
||||||
"deafen_members": "Deafen Members",
|
|
||||||
"move_members": "Move Members",
|
|
||||||
"use_voice_activation": "Use Voice Activity",
|
|
||||||
"change_nickname": "Change Nickname",
|
|
||||||
"manage_nicknames": "Manage Nicknames",
|
|
||||||
"manage_roles": "Manage Roles",
|
|
||||||
"manage_webhooks": "Manage Webhooks",
|
|
||||||
"manage_emojis": "Manage Emojis",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class CompositeMetaClass(type(commands.Cog), type(ABC)):
|
class CompositeMetaClass(type(commands.Cog), type(ABC)):
|
||||||
|
|||||||
@ -20,7 +20,7 @@ from redbot.core.utils.predicates import MessagePredicate, ReactionPredicate
|
|||||||
from ...audio_dataclasses import LocalPath
|
from ...audio_dataclasses import LocalPath
|
||||||
from ...converters import ScopeParser
|
from ...converters import ScopeParser
|
||||||
from ...errors import MissingGuild, TooManyMatches
|
from ...errors import MissingGuild, TooManyMatches
|
||||||
from ...utils import CacheLevel, PlaylistScope, has_internal_server
|
from ...utils import CacheLevel, PlaylistScope, has_managed_server
|
||||||
from ..abc import MixinMeta
|
from ..abc import MixinMeta
|
||||||
from ..cog_utils import CompositeMetaClass, PlaylistConverter, __version__
|
from ..cog_utils import CompositeMetaClass, PlaylistConverter, __version__
|
||||||
|
|
||||||
@ -1117,7 +1117,11 @@ class AudioSetCommands(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
if global_data["use_external_lavalink"]
|
if global_data["use_external_lavalink"]
|
||||||
else _("Disabled"),
|
else _("Disabled"),
|
||||||
)
|
)
|
||||||
if is_owner and not global_data["use_external_lavalink"] and self.player_manager.ll_build:
|
if (
|
||||||
|
is_owner
|
||||||
|
and not global_data["use_external_lavalink"]
|
||||||
|
and self.managed_node_controller.ll_build
|
||||||
|
):
|
||||||
msg += _(
|
msg += _(
|
||||||
"Lavalink build: [{llbuild}]\n"
|
"Lavalink build: [{llbuild}]\n"
|
||||||
"Lavalink branch: [{llbranch}]\n"
|
"Lavalink branch: [{llbranch}]\n"
|
||||||
@ -1125,13 +1129,17 @@ class AudioSetCommands(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
"Lavaplayer version: [{lavaplayer}]\n"
|
"Lavaplayer version: [{lavaplayer}]\n"
|
||||||
"Java version: [{jvm}]\n"
|
"Java version: [{jvm}]\n"
|
||||||
"Java Executable: [{jv_exec}]\n"
|
"Java Executable: [{jv_exec}]\n"
|
||||||
|
"Initial Heapsize: [{xms}]\n"
|
||||||
|
"Max Heapsize: [{xmx}]\n"
|
||||||
).format(
|
).format(
|
||||||
build_time=self.player_manager.build_time,
|
build_time=self.managed_node_controller.build_time,
|
||||||
llbuild=self.player_manager.ll_build,
|
llbuild=self.managed_node_controller.ll_build,
|
||||||
llbranch=self.player_manager.ll_branch,
|
llbranch=self.managed_node_controller.ll_branch,
|
||||||
lavaplayer=self.player_manager.lavaplayer,
|
lavaplayer=self.managed_node_controller.lavaplayer,
|
||||||
jvm=self.player_manager.jvm,
|
jvm=self.managed_node_controller.jvm,
|
||||||
jv_exec=self.player_manager.path,
|
jv_exec=self.managed_node_controller.path,
|
||||||
|
xms=global_data["java"]["Xms"],
|
||||||
|
xmx=global_data["java"]["Xmx"],
|
||||||
)
|
)
|
||||||
if is_owner:
|
if is_owner:
|
||||||
msg += _("Localtracks path: [{localpath}]\n").format(**global_data)
|
msg += _("Localtracks path: [{localpath}]\n").format(**global_data)
|
||||||
@ -1140,10 +1148,10 @@ class AudioSetCommands(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
|
|
||||||
@command_audioset.command(name="logs")
|
@command_audioset.command(name="logs")
|
||||||
@commands.is_owner()
|
@commands.is_owner()
|
||||||
@has_internal_server()
|
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
|
@has_managed_server()
|
||||||
async def command_audioset_logs(self, ctx: commands.Context):
|
async def command_audioset_logs(self, ctx: commands.Context):
|
||||||
"""Sends the Lavalink server logs to your DMs."""
|
"""Sends the managed Lavalink node logs to your DMs."""
|
||||||
datapath = cog_data_path(raw_name="Audio")
|
datapath = cog_data_path(raw_name="Audio")
|
||||||
logs = datapath / "logs" / "spring.log"
|
logs = datapath / "logs" / "spring.log"
|
||||||
zip_name = None
|
zip_name = None
|
||||||
@ -1448,11 +1456,17 @@ class AudioSetCommands(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
async def command_audioset_restart(self, ctx: commands.Context):
|
async def command_audioset_restart(self, ctx: commands.Context):
|
||||||
"""Restarts the lavalink connection."""
|
"""Restarts the lavalink connection."""
|
||||||
async with ctx.typing():
|
async with ctx.typing():
|
||||||
|
try:
|
||||||
await lavalink.close(self.bot)
|
await lavalink.close(self.bot)
|
||||||
if self.player_manager is not None:
|
self.lavalink_restart_connect(manual=True)
|
||||||
await self.player_manager.shutdown()
|
except ProcessLookupError:
|
||||||
|
await self.send_embed_msg(
|
||||||
self.lavalink_restart_connect()
|
ctx,
|
||||||
|
title=_("Failed To Shutdown Lavalink Node"),
|
||||||
|
description=_("Please reload Audio (`{prefix}reload audio`).").format(
|
||||||
|
prefix=ctx.prefix
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
await self.send_embed_msg(
|
await self.send_embed_msg(
|
||||||
ctx,
|
ctx,
|
||||||
|
|||||||
@ -7,6 +7,7 @@ from typing import Optional, Union
|
|||||||
|
|
||||||
import discord
|
import discord
|
||||||
import lavalink
|
import lavalink
|
||||||
|
from lavalink import NodeNotFound
|
||||||
from red_commons.logging import getLogger
|
from red_commons.logging import getLogger
|
||||||
|
|
||||||
from redbot.core import commands
|
from redbot.core import commands
|
||||||
@ -683,12 +684,12 @@ class PlayerControllerCommands(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
title=_("Unable To Join Voice Channel"),
|
title=_("Unable To Join Voice Channel"),
|
||||||
description=_("Connect to a voice channel first."),
|
description=_("Connect to a voice channel first."),
|
||||||
)
|
)
|
||||||
except IndexError:
|
except NodeNotFound:
|
||||||
ctx.command.reset_cooldown(ctx)
|
ctx.command.reset_cooldown(ctx)
|
||||||
return await self.send_embed_msg(
|
return await self.send_embed_msg(
|
||||||
ctx,
|
ctx,
|
||||||
title=_("Unable To Join Voice Channel"),
|
title=_("Unable To Join Voice Channel"),
|
||||||
description=_("Connection to Lavalink has not yet been established."),
|
description=_("Connection to the Lavalink node has not yet been established."),
|
||||||
)
|
)
|
||||||
|
|
||||||
@commands.command(name="volume")
|
@commands.command(name="volume")
|
||||||
|
|||||||
@ -1,14 +1,29 @@
|
|||||||
|
import re
|
||||||
|
from io import BytesIO
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
|
import lavalink
|
||||||
|
import yaml
|
||||||
from red_commons.logging import getLogger
|
from red_commons.logging import getLogger
|
||||||
|
|
||||||
from redbot.core import commands
|
from redbot.core import commands
|
||||||
|
from redbot.core.data_manager import cog_data_path
|
||||||
from redbot.core.i18n import Translator
|
from redbot.core.i18n import Translator
|
||||||
from redbot.core.utils.chat_formatting import box
|
from redbot.core.utils.chat_formatting import box, inline
|
||||||
|
|
||||||
from ..abc import MixinMeta
|
from ..abc import MixinMeta
|
||||||
from ..cog_utils import CompositeMetaClass
|
from ..cog_utils import CompositeMetaClass
|
||||||
|
from ...utils import (
|
||||||
|
MAX_JAVA_RAM,
|
||||||
|
DEFAULT_LAVALINK_YAML,
|
||||||
|
DEFAULT_LAVALINK_SETTINGS,
|
||||||
|
change_dict_naming_convention,
|
||||||
|
has_managed_server,
|
||||||
|
has_unmanaged_server,
|
||||||
|
sizeof_fmt,
|
||||||
|
get_max_allocation_size,
|
||||||
|
)
|
||||||
|
|
||||||
log = getLogger("red.cogs.Audio.cog.Commands.lavalink_setup")
|
log = getLogger("red.cogs.Audio.cog.Commands.lavalink_setup")
|
||||||
_ = Translator("Audio", Path(__file__))
|
_ = Translator("Audio", Path(__file__))
|
||||||
@ -19,30 +34,36 @@ class LavalinkSetupCommands(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
@commands.is_owner()
|
@commands.is_owner()
|
||||||
@commands.bot_has_permissions(embed_links=True)
|
@commands.bot_has_permissions(embed_links=True)
|
||||||
async def command_llsetup(self, ctx: commands.Context):
|
async def command_llsetup(self, ctx: commands.Context):
|
||||||
"""Lavalink server configuration options."""
|
"""`Dangerous commands` Manage Lavalink node configuration settings.
|
||||||
|
|
||||||
|
This command block holds all commands to manage an external or managed Lavalink node.
|
||||||
|
|
||||||
|
You should not mess with any command in here unless you have a valid reason to,
|
||||||
|
i.e. been told by someone in the Red-Discord Bot support server to do so.
|
||||||
|
All the commands in here have the potential to break the Audio cog.
|
||||||
|
"""
|
||||||
|
|
||||||
@command_llsetup.command(name="java")
|
@command_llsetup.command(name="java")
|
||||||
async def command_llsetup_java(self, ctx: commands.Context, *, java_path: str = None):
|
@has_managed_server()
|
||||||
"""Change your Java executable path
|
async def command_llsetup_java(self, ctx: commands.Context, *, java_path: str = "java"):
|
||||||
|
"""Change your Java executable path.
|
||||||
|
|
||||||
Enter nothing to reset to default.
|
This command shouldn't need to be used most of the time, and is only useful if the host machine has conflicting Java versions.
|
||||||
|
|
||||||
|
If changing this make sure that the java you set is supported by Audio.
|
||||||
|
The current supported version is Java 11.
|
||||||
|
|
||||||
|
Enter nothing or "java" to reset it back to default.
|
||||||
"""
|
"""
|
||||||
external = await self.config.use_external_lavalink()
|
if java_path == "java":
|
||||||
if external:
|
|
||||||
return await self.send_embed_msg(
|
|
||||||
ctx,
|
|
||||||
title=_("Invalid Environment"),
|
|
||||||
description=_(
|
|
||||||
"You cannot changed the Java executable path of "
|
|
||||||
"external Lavalink instances from the Audio Cog."
|
|
||||||
),
|
|
||||||
)
|
|
||||||
if java_path is None:
|
|
||||||
await self.config.java_exc_path.clear()
|
await self.config.java_exc_path.clear()
|
||||||
await self.send_embed_msg(
|
await self.send_embed_msg(
|
||||||
ctx,
|
ctx,
|
||||||
title=_("Java Executable Reset"),
|
title=_("Java Executable Reset"),
|
||||||
description=_("Audio will now use `java` to run your Lavalink.jar"),
|
description=_(
|
||||||
|
"Audio will now use `java` to run your managed Lavalink node. "
|
||||||
|
"Run `{p}{cmd}` for it to take effect."
|
||||||
|
).format(p=ctx.prefix, cmd=self.command_audioset_restart.qualified_name),
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
exc = Path(java_path)
|
exc = Path(java_path)
|
||||||
@ -59,76 +80,106 @@ class LavalinkSetupCommands(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
await self.send_embed_msg(
|
await self.send_embed_msg(
|
||||||
ctx,
|
ctx,
|
||||||
title=_("Java Executable Changed"),
|
title=_("Java Executable Changed"),
|
||||||
description=_("Audio will now use `{exc}` to run your Lavalink.jar").format(
|
description=_(
|
||||||
exc=exc_absolute
|
"Audio will now use `{exc}` to run your managed Lavalink node. "
|
||||||
|
"Run `{p}{cmd}` for it to take effect."
|
||||||
|
).format(
|
||||||
|
exc=exc_absolute,
|
||||||
|
p=ctx.prefix,
|
||||||
|
cmd=self.command_audioset_restart.qualified_name,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
try:
|
|
||||||
if self.player_manager is not None:
|
@command_llsetup.command(name="heapsize", aliases=["hs", "ram", "memory"])
|
||||||
await self.player_manager.shutdown()
|
@has_managed_server()
|
||||||
except ProcessLookupError:
|
async def command_llsetup_heapsize(self, ctx: commands.Context, size: str = MAX_JAVA_RAM):
|
||||||
await self.send_embed_msg(
|
"""Set the managed Lavalink node maximum heap-size.
|
||||||
ctx,
|
|
||||||
title=_("Failed To Shutdown Lavalink"),
|
By default, this value is 50% of available RAM in the host machine represented by [1-1024][M|G] (256M, 256G for example)
|
||||||
description=_(
|
|
||||||
"For it to take effect please reload Audio (`{prefix}reload audio`)."
|
This value only represents the maximum amount of RAM allowed to be used at any given point, and does not mean that the managed Lavalink node will always use this amount of RAM.
|
||||||
).format(
|
|
||||||
prefix=ctx.prefix,
|
To reset this value to the default, run the command without any input.
|
||||||
),
|
"""
|
||||||
|
|
||||||
|
async def validate_input(cog, arg):
|
||||||
|
match = re.match(r"^(\d+)([MG])$", arg, flags=re.IGNORECASE)
|
||||||
|
if not match:
|
||||||
|
await ctx.send(_("Heap-size must be a valid measure of size, e.g. 256M, 256G"))
|
||||||
|
return 0
|
||||||
|
input_in_bytes = int(match.group(1)) * 1024 ** (
|
||||||
|
2 if match.group(2).lower() == "m" else 3
|
||||||
|
)
|
||||||
|
if input_in_bytes < 64 * 1024**2:
|
||||||
|
await ctx.send(
|
||||||
|
_(
|
||||||
|
"Heap-size must be at least 64M, however it is recommended to have it set to at least 1G."
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return 0
|
||||||
|
elif (
|
||||||
|
input_in_bytes
|
||||||
|
> (meta := get_max_allocation_size(cog.managed_node_controller._java_exc))[0]
|
||||||
|
):
|
||||||
|
if meta[1]:
|
||||||
|
await ctx.send(
|
||||||
|
_(
|
||||||
|
"Heap-size must be less than your system RAM, "
|
||||||
|
"You currently have {ram_in_bytes} of RAM available."
|
||||||
|
).format(ram_in_bytes=inline(sizeof_fmt(meta[0])))
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
try:
|
await ctx.send(
|
||||||
self.lavalink_restart_connect()
|
_(
|
||||||
except ProcessLookupError:
|
"Heap-size must be less than {limit} due to your system limitations."
|
||||||
|
).format(limit=inline(sizeof_fmt(meta[0])))
|
||||||
|
)
|
||||||
|
return 0
|
||||||
|
return 1
|
||||||
|
|
||||||
|
if not (await validate_input(self, size)):
|
||||||
|
return
|
||||||
|
size = size.upper()
|
||||||
|
await self.config.java.Xmx.set(size)
|
||||||
await self.send_embed_msg(
|
await self.send_embed_msg(
|
||||||
ctx,
|
ctx,
|
||||||
title=_("Failed To Shutdown Lavalink"),
|
title=_("Setting Changed"),
|
||||||
description=_("Please reload Audio (`{prefix}reload audio`).").format(
|
description=_(
|
||||||
prefix=ctx.prefix
|
"Managed node's heap-size set to {bytes}.\n\n"
|
||||||
|
"Run `{p}{cmd}` for it to take effect."
|
||||||
|
).format(
|
||||||
|
bytes=inline(size), p=ctx.prefix, cmd=self.command_audioset_restart.qualified_name
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@command_llsetup.command(name="external")
|
@command_llsetup.command(name="external")
|
||||||
async def command_llsetup_external(self, ctx: commands.Context):
|
async def command_llsetup_external(self, ctx: commands.Context):
|
||||||
"""Toggle using external Lavalink servers."""
|
"""Toggle using external Lavalink nodes - requires an existing external Lavalink node for Audio to work, if enabled.
|
||||||
|
|
||||||
|
This command disables the managed Lavalink server, if you do not have an external Lavalink node you will be unable to use Audio while this is enabled.
|
||||||
|
"""
|
||||||
external = await self.config.use_external_lavalink()
|
external = await self.config.use_external_lavalink()
|
||||||
await self.config.use_external_lavalink.set(not external)
|
await self.config.use_external_lavalink.set(not external)
|
||||||
|
async with ctx.typing():
|
||||||
if external:
|
if external:
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
title=_("Setting Changed"),
|
title=_("Setting Changed"),
|
||||||
description=_("External Lavalink server: {true_or_false}.").format(
|
description=_("External Lavalink server: {true_or_false}.").format(
|
||||||
true_or_false=_("Enabled") if not external else _("Disabled")
|
true_or_false=inline(_("Enabled") if not external else _("Disabled"))
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
await self.send_embed_msg(ctx, embed=embed)
|
await self.send_embed_msg(ctx, embed=embed)
|
||||||
else:
|
|
||||||
try:
|
|
||||||
if self.player_manager is not None:
|
|
||||||
await self.player_manager.shutdown()
|
|
||||||
except ProcessLookupError:
|
|
||||||
await self.send_embed_msg(
|
|
||||||
ctx,
|
|
||||||
title=_("Failed To Shutdown Lavalink"),
|
|
||||||
description=_(
|
|
||||||
"External Lavalink server: {true_or_false}\n"
|
|
||||||
"For it to take effect please reload "
|
|
||||||
"Audio (`{prefix}reload audio`)."
|
|
||||||
).format(
|
|
||||||
true_or_false=_("Enabled") if not external else _("Disabled"),
|
|
||||||
prefix=ctx.prefix,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
await self.send_embed_msg(
|
await self.send_embed_msg(
|
||||||
ctx,
|
ctx,
|
||||||
title=_("Setting Changed"),
|
title=_("Setting Changed"),
|
||||||
description=_("External Lavalink server: {true_or_false}.").format(
|
description=_("External Lavalink server: {true_or_false}.").format(
|
||||||
true_or_false=_("Enabled") if not external else _("Disabled")
|
true_or_false=inline(_("Enabled") if not external else _("Disabled"))
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
self.lavalink_restart_connect()
|
await lavalink.close(self.bot)
|
||||||
|
self.lavalink_restart_connect(manual=True)
|
||||||
except ProcessLookupError:
|
except ProcessLookupError:
|
||||||
await self.send_embed_msg(
|
await self.send_embed_msg(
|
||||||
ctx,
|
ctx,
|
||||||
@ -139,94 +190,541 @@ class LavalinkSetupCommands(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@command_llsetup.command(name="host")
|
@command_llsetup.command(name="host")
|
||||||
async def command_llsetup_host(self, ctx: commands.Context, host: str):
|
@has_unmanaged_server()
|
||||||
"""Set the Lavalink server host."""
|
async def command_llsetup_host(
|
||||||
|
self, ctx: commands.Context, host: str = DEFAULT_LAVALINK_SETTINGS["host"]
|
||||||
|
):
|
||||||
|
"""Set the Lavalink node host.
|
||||||
|
|
||||||
|
This command sets the connection host which Audio will use to connect to an external Lavalink node.
|
||||||
|
"""
|
||||||
await self.config.host.set(host)
|
await self.config.host.set(host)
|
||||||
footer = None
|
|
||||||
if await self.update_external_status():
|
|
||||||
footer = _("External Lavalink server set to True.")
|
|
||||||
await self.send_embed_msg(
|
await self.send_embed_msg(
|
||||||
ctx,
|
ctx,
|
||||||
title=_("Setting Changed"),
|
title=_("Setting Changed"),
|
||||||
description=_("Host set to {host}.").format(host=host),
|
description=_(
|
||||||
footer=footer,
|
"External Lavalink node host set to {host}. "
|
||||||
)
|
"Run `{p}{cmd}` for it to take effect."
|
||||||
try:
|
).format(
|
||||||
self.lavalink_restart_connect()
|
host=inline(host), p=ctx.prefix, cmd=self.command_audioset_restart.qualified_name
|
||||||
except ProcessLookupError:
|
|
||||||
await self.send_embed_msg(
|
|
||||||
ctx,
|
|
||||||
title=_("Failed To Shutdown Lavalink"),
|
|
||||||
description=_("Please reload Audio (`{prefix}reload audio`).").format(
|
|
||||||
prefix=ctx.prefix
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@command_llsetup.command(name="password")
|
@command_llsetup.command(name="password", aliases=["pass", "token"])
|
||||||
async def command_llsetup_password(self, ctx: commands.Context, password: str):
|
@has_unmanaged_server()
|
||||||
"""Set the Lavalink server password."""
|
async def command_llsetup_password(
|
||||||
|
self, ctx: commands.Context, *, password: str = DEFAULT_LAVALINK_SETTINGS["password"]
|
||||||
|
):
|
||||||
|
"""Set the Lavalink node password.
|
||||||
|
|
||||||
|
This command sets the connection password which Audio will use to connect to an external Lavalink node.
|
||||||
|
"""
|
||||||
|
|
||||||
await self.config.password.set(str(password))
|
await self.config.password.set(str(password))
|
||||||
footer = None
|
|
||||||
if await self.update_external_status():
|
|
||||||
footer = _("External Lavalink server set to True.")
|
|
||||||
await self.send_embed_msg(
|
await self.send_embed_msg(
|
||||||
ctx,
|
ctx,
|
||||||
title=_("Setting Changed"),
|
title=_("Setting Changed"),
|
||||||
description=_("Server password set to {password}.").format(password=password),
|
description=_(
|
||||||
footer=footer,
|
"External Lavalink node password set to {password}. "
|
||||||
)
|
"Run `{p}{cmd}` for it to take effect."
|
||||||
|
).format(
|
||||||
try:
|
password=inline(password),
|
||||||
self.lavalink_restart_connect()
|
p=ctx.prefix,
|
||||||
except ProcessLookupError:
|
cmd=self.command_audioset_restart.qualified_name,
|
||||||
await self.send_embed_msg(
|
|
||||||
ctx,
|
|
||||||
title=_("Failed To Shutdown Lavalink"),
|
|
||||||
description=_("Please reload Audio (`{prefix}reload audio`).").format(
|
|
||||||
prefix=ctx.prefix
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@command_llsetup.command(name="wsport")
|
@command_llsetup.command(name="port")
|
||||||
async def command_llsetup_wsport(self, ctx: commands.Context, ws_port: int):
|
@has_unmanaged_server()
|
||||||
"""Set the Lavalink websocket server port."""
|
async def command_llsetup_wsport(
|
||||||
await self.config.ws_port.set(ws_port)
|
self, ctx: commands.Context, port: int = DEFAULT_LAVALINK_SETTINGS["ws_port"]
|
||||||
footer = None
|
):
|
||||||
if await self.update_external_status():
|
"""Set the Lavalink node port.
|
||||||
footer = _("External Lavalink server set to True.")
|
|
||||||
|
This command sets the connection port which Audio will use to connect to an external Lavalink node.
|
||||||
|
"""
|
||||||
|
if port < 0 or port > 65535:
|
||||||
|
return await self.send_embed_msg(
|
||||||
|
ctx,
|
||||||
|
title=_("Setting Not Changed"),
|
||||||
|
description=_("A port must be between 0 and 65535 "),
|
||||||
|
)
|
||||||
|
await self.config.ws_port.set(port)
|
||||||
await self.send_embed_msg(
|
await self.send_embed_msg(
|
||||||
ctx,
|
ctx,
|
||||||
title=_("Setting Changed"),
|
title=_("Setting Changed"),
|
||||||
description=_("Websocket port set to {port}.").format(port=ws_port),
|
description=_(
|
||||||
footer=footer,
|
"External Lavalink node port set to {port}. "
|
||||||
|
"Run `{p}{cmd}` for it to take effect."
|
||||||
|
).format(
|
||||||
|
port=inline(str(port)),
|
||||||
|
p=ctx.prefix,
|
||||||
|
cmd=self.command_audioset_restart.qualified_name,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
@command_llsetup.command(name="secured", aliases=["wss"])
|
||||||
self.lavalink_restart_connect()
|
@has_unmanaged_server()
|
||||||
except ProcessLookupError:
|
async def command_llsetup_secured(self, ctx: commands.Context):
|
||||||
|
"""Set the Lavalink node connection to secured.
|
||||||
|
|
||||||
|
This command sets the connection type to secured when connecting to an external Lavalink node.
|
||||||
|
"""
|
||||||
|
state = await self.config.secured_ws()
|
||||||
|
await self.config.secured_ws.set(not state)
|
||||||
|
|
||||||
|
if not state:
|
||||||
await self.send_embed_msg(
|
await self.send_embed_msg(
|
||||||
ctx,
|
ctx,
|
||||||
title=_("Failed To Shutdown Lavalink"),
|
title=_("Setting Changed"),
|
||||||
description=_("Please reload Audio (`{prefix}reload audio`).").format(
|
description=_(
|
||||||
prefix=ctx.prefix
|
"Managed Lavalink node will now connect using the secured {secured_protocol} protocol.\n\n"
|
||||||
|
"Run `{p}{cmd}` for it to take effect."
|
||||||
|
).format(
|
||||||
|
p=ctx.prefix,
|
||||||
|
cmd=self.command_audioset_restart.qualified_name,
|
||||||
|
secured_protocol=inline("wss://"),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
await self.send_embed_msg(
|
||||||
|
ctx,
|
||||||
|
title=_("Setting Changed"),
|
||||||
|
description=_(
|
||||||
|
"Managed Lavalink node will no longer connect using the secured "
|
||||||
|
"{secured_protocol} protocol and wil use {unsecured_protocol} instead .\n\n"
|
||||||
|
"Run `{p}{cmd}` for it to take effect."
|
||||||
|
).format(p=ctx.prefix, cmd=self.command_audioset_restart.qualified_name),
|
||||||
|
unsecured_protocol=inline("ws://"),
|
||||||
|
secured_protocol=inline("wss://"),
|
||||||
|
)
|
||||||
|
|
||||||
@command_llsetup.command(name="info", aliases=["settings"])
|
@command_llsetup.command(name="info", aliases=["settings"])
|
||||||
async def command_llsetup_info(self, ctx: commands.Context):
|
async def command_llsetup_info(self, ctx: commands.Context):
|
||||||
"""Display Lavalink connection settings."""
|
"""Display Lavalink connection settings."""
|
||||||
configs = await self.config.all()
|
configs = await self.config.all()
|
||||||
host = configs["host"]
|
|
||||||
password = configs["password"]
|
if configs["use_external_lavalink"]:
|
||||||
rest_port = configs["rest_port"]
|
|
||||||
ws_port = configs["ws_port"]
|
|
||||||
msg = "----" + _("Connection Settings") + "---- \n"
|
msg = "----" + _("Connection Settings") + "---- \n"
|
||||||
msg += _("Host: [{host}]\n").format(host=host)
|
msg += _("Host: [{host}]\n").format(host=configs["host"])
|
||||||
msg += _("WS Port: [{port}]\n").format(port=ws_port)
|
msg += _("Port: [{port}]\n").format(port=configs["ws_port"])
|
||||||
if ws_port != rest_port and rest_port != 2333:
|
msg += _("Password: [{password}]\n").format(password=configs["password"])
|
||||||
msg += _("Rest Port: [{port}]\n").format(port=rest_port)
|
msg += _("Secured: [{state}]\n").format(state=configs["secured_ws"])
|
||||||
msg += _("Password: [{password}]\n").format(password=password)
|
|
||||||
|
else:
|
||||||
|
msg = "----" + _("Lavalink Node Settings") + "---- \n"
|
||||||
|
msg += _("Host: [{host}]\n").format(
|
||||||
|
host=configs["yaml"]["server"]["address"]
|
||||||
|
)
|
||||||
|
msg += _("Port: [{port}]\n").format(port=configs["yaml"]["server"]["port"])
|
||||||
|
msg += _("Password: [{password}]\n").format(
|
||||||
|
password=configs["yaml"]["lavalink"]["server"]["password"]
|
||||||
|
)
|
||||||
|
msg += _("Initial Heapsize: [{xms}]\n").format(xms=configs["java"]["Xms"])
|
||||||
|
msg += _("Max Heapsize: [{xmx}]\n").format(xmx=configs["java"]["Xmx"])
|
||||||
|
msg += _("Java exec: [{java_exc_path}]\n").format(
|
||||||
|
java_exc_path=configs["java_exc_path"]
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await self.send_embed_msg(ctx.author, description=box(msg, lang="ini"))
|
await self.send_embed_msg(ctx.author, description=box(msg, lang="ini"))
|
||||||
|
await ctx.tick()
|
||||||
except discord.HTTPException:
|
except discord.HTTPException:
|
||||||
await ctx.send(_("I need to be able to DM you to send you this info."))
|
await ctx.send(_("I need to be able to DM you to send you this info."))
|
||||||
|
|
||||||
|
@command_llsetup.command(name="yaml", aliases=["yml"])
|
||||||
|
@has_managed_server()
|
||||||
|
async def command_llsetup_yaml(self, ctx: commands.Context):
|
||||||
|
"""Uploads a copy of the application.yml file used by the managed Lavalink node."""
|
||||||
|
configs = change_dict_naming_convention(await self.config.yaml.all())
|
||||||
|
data = yaml.safe_dump(configs)
|
||||||
|
playlist_data = data.encode("utf-8")
|
||||||
|
to_write = BytesIO()
|
||||||
|
to_write.write(playlist_data)
|
||||||
|
to_write.seek(0)
|
||||||
|
datapath = cog_data_path(raw_name="Audio")
|
||||||
|
temp_file = datapath / f"application.dump.yaml"
|
||||||
|
try:
|
||||||
|
with temp_file.open("wb") as application_file:
|
||||||
|
application_file.write(to_write.read())
|
||||||
|
await ctx.author.send(
|
||||||
|
file=discord.File(str(temp_file)),
|
||||||
|
)
|
||||||
|
await ctx.tick()
|
||||||
|
except discord.HTTPException:
|
||||||
|
await ctx.send(_("I need to be able to DM you to send you this info."))
|
||||||
|
finally:
|
||||||
|
temp_file.unlink()
|
||||||
|
|
||||||
|
@command_llsetup.group(name="config", aliases=["conf"])
|
||||||
|
@has_managed_server()
|
||||||
|
async def command_llsetup_config(self, ctx: commands.Context):
|
||||||
|
"""Configure the managed Lavalink node runtime options.
|
||||||
|
|
||||||
|
All settings under this group will likely cause Audio to malfunction if changed from their defaults, only change settings here if you have been advised to by support.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@command_llsetup_config.group(name="server")
|
||||||
|
async def command_llsetup_config_server(self, ctx: commands.Context):
|
||||||
|
"""Configure the managed node authorization and connection settings."""
|
||||||
|
|
||||||
|
@command_llsetup_config.command(name="bind", aliases=["host", "address"])
|
||||||
|
async def command_llsetup_config_host(
|
||||||
|
self, ctx: commands.Context, *, host: str = DEFAULT_LAVALINK_YAML["yaml__server__address"]
|
||||||
|
):
|
||||||
|
"""`Dangerous command` Set the managed Lavalink node's binding IP address.
|
||||||
|
|
||||||
|
This value by default is `localhost` which will restrict the server to only localhost apps by default, changing this will likely break the managed Lavalink node if you don't know what you are doing.
|
||||||
|
"""
|
||||||
|
|
||||||
|
await self.config.yaml.server.address.set(host)
|
||||||
|
await self.send_embed_msg(
|
||||||
|
ctx,
|
||||||
|
title=_("Setting Changed"),
|
||||||
|
description=_(
|
||||||
|
"Managed node will now accept connection on {host}.\n\n"
|
||||||
|
"Run `{p}{cmd}` for it to take effect."
|
||||||
|
).format(
|
||||||
|
host=inline(host), p=ctx.prefix, cmd=self.command_audioset_restart.qualified_name
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
@command_llsetup_config.command(name="token", aliases=["password", "pass"])
|
||||||
|
async def command_llsetup_config_token(
|
||||||
|
self,
|
||||||
|
ctx: commands.Context,
|
||||||
|
*,
|
||||||
|
password: str = DEFAULT_LAVALINK_YAML["yaml__lavalink__server__password"],
|
||||||
|
):
|
||||||
|
"""Set the managed Lavalink node's connection password.
|
||||||
|
|
||||||
|
This is the password required for Audio to connect to the managed Lavalink node.
|
||||||
|
The value by default is `youshallnotpass`.
|
||||||
|
"""
|
||||||
|
await self.config.yaml.lavalink.server.password.set(password)
|
||||||
|
await self.send_embed_msg(
|
||||||
|
ctx,
|
||||||
|
title=_("Setting Changed"),
|
||||||
|
description=_(
|
||||||
|
"Managed node will now accept {password} as the authorization token.\n\n"
|
||||||
|
"Run `{p}{cmd}` for it to take effect."
|
||||||
|
).format(
|
||||||
|
password=inline(password),
|
||||||
|
p=ctx.prefix,
|
||||||
|
cmd=self.command_audioset_restart.qualified_name,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
@command_llsetup_config.command(name="port")
|
||||||
|
async def command_llsetup_config_port(
|
||||||
|
self, ctx: commands.Context, *, port: int = DEFAULT_LAVALINK_YAML["yaml__server__port"]
|
||||||
|
):
|
||||||
|
"""`Dangerous command` Set the managed Lavalink node's connection port.
|
||||||
|
|
||||||
|
This port is the port the managed Lavalink node binds to, you should only change this if there is a conflict with the default port because you already have an application using port 2333 on this device.
|
||||||
|
|
||||||
|
The value by default is `2333`.
|
||||||
|
"""
|
||||||
|
if 1024 > port or port > 49151:
|
||||||
|
return await self.send_embed_msg(
|
||||||
|
ctx,
|
||||||
|
title=_("Setting Not Changed"),
|
||||||
|
description=_("The port must be between 1024 and 49151."),
|
||||||
|
)
|
||||||
|
|
||||||
|
await self.config.yaml.server.port.set(port)
|
||||||
|
await self.send_embed_msg(
|
||||||
|
ctx,
|
||||||
|
title=_("Setting Changed"),
|
||||||
|
description=_(
|
||||||
|
"Managed node will now accept connections on {port}.\n\n"
|
||||||
|
"Run `{p}{cmd}` for it to take effect."
|
||||||
|
).format(
|
||||||
|
port=inline(str(port)),
|
||||||
|
p=ctx.prefix,
|
||||||
|
cmd=self.command_audioset_restart.qualified_name,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
@command_llsetup_config.group(name="source")
|
||||||
|
async def command_llsetup_config_source(self, ctx: commands.Context):
|
||||||
|
"""`Dangerous command` Toggle audio sources on/off.
|
||||||
|
|
||||||
|
By default, all sources are enabled, you should only use commands here to disable a specific source if you have been advised to, disabling sources without background knowledge can cause Audio to break.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@command_llsetup_config_source.command(name="http")
|
||||||
|
async def command_llsetup_config_source_http(self, ctx: commands.Context):
|
||||||
|
"""Toggle HTTP direct URL usage on or off.
|
||||||
|
|
||||||
|
This source is used to allow playback from direct http streams (This does not affect direct url playback for the other sources)
|
||||||
|
"""
|
||||||
|
state = await self.config.yaml.lavalink.server.sources.http()
|
||||||
|
await self.config.yaml.lavalink.server.sources.http.set(not state)
|
||||||
|
if not state:
|
||||||
|
await self.send_embed_msg(
|
||||||
|
ctx,
|
||||||
|
title=_("Setting Changed"),
|
||||||
|
description=_(
|
||||||
|
"Managed node will allow playback from direct URLs.\n\n"
|
||||||
|
"Run `{p}{cmd}` for it to take effect."
|
||||||
|
).format(p=ctx.prefix, cmd=self.command_audioset_restart.qualified_name),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await self.send_embed_msg(
|
||||||
|
ctx,
|
||||||
|
title=_("Setting Changed"),
|
||||||
|
description=_(
|
||||||
|
"Managed node will not play from direct URLs anymore.\n\n"
|
||||||
|
"Run `{p}{cmd}` for it to take effect."
|
||||||
|
).format(p=ctx.prefix, cmd=self.command_audioset_restart.qualified_name),
|
||||||
|
)
|
||||||
|
|
||||||
|
@command_llsetup_config_source.command(name="bandcamp", aliases=["bc"])
|
||||||
|
async def command_llsetup_config_source_bandcamp(self, ctx: commands.Context):
|
||||||
|
"""Toggle Bandcamp source on or off.
|
||||||
|
|
||||||
|
This toggle controls the playback of all Bandcamp related content.
|
||||||
|
"""
|
||||||
|
state = await self.config.yaml.bandcamp.http()
|
||||||
|
await self.config.yaml.lavalink.server.sources.bandcamp.set(not state)
|
||||||
|
if not state:
|
||||||
|
await self.send_embed_msg(
|
||||||
|
ctx,
|
||||||
|
title=_("Setting Changed"),
|
||||||
|
description=_(
|
||||||
|
"Managed node will allow playback from Bandcamp.\n\n"
|
||||||
|
"Run `{p}{cmd}` for it to take effect."
|
||||||
|
).format(p=ctx.prefix, cmd=self.command_audioset_restart.qualified_name),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await self.send_embed_msg(
|
||||||
|
ctx,
|
||||||
|
title=_("Setting Changed"),
|
||||||
|
description=_(
|
||||||
|
"Managed node will not play from Bandcamp anymore.\n\n"
|
||||||
|
"Run `{p}{cmd}` for it to take effect."
|
||||||
|
).format(p=ctx.prefix, cmd=self.command_audioset_restart.qualified_name),
|
||||||
|
)
|
||||||
|
|
||||||
|
@command_llsetup_config_source.command(name="local")
|
||||||
|
async def command_llsetup_config_source_local(self, ctx: commands.Context):
|
||||||
|
"""Toggle local file usage on or off.
|
||||||
|
|
||||||
|
This toggle controls the playback of all local track content, usually found inside the `localtracks` folder.
|
||||||
|
"""
|
||||||
|
state = await self.config.yaml.lavalink.server.sources.local()
|
||||||
|
await self.config.yaml.lavalink.server.sources.local.set(not state)
|
||||||
|
if not state:
|
||||||
|
await self.send_embed_msg(
|
||||||
|
ctx,
|
||||||
|
title=_("Setting Changed"),
|
||||||
|
description=_(
|
||||||
|
"Managed node will allow playback from local files.\n\n"
|
||||||
|
"Run `{p}{cmd}` for it to take effect."
|
||||||
|
).format(p=ctx.prefix, cmd=self.command_audioset_restart.qualified_name),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await self.send_embed_msg(
|
||||||
|
ctx,
|
||||||
|
title=_("Setting Changed"),
|
||||||
|
description=_(
|
||||||
|
"Managed node will not play from local files anymore.\n\n"
|
||||||
|
"Run `{p}{cmd}` for it to take effect."
|
||||||
|
).format(p=ctx.prefix, cmd=self.command_audioset_restart.qualified_name),
|
||||||
|
)
|
||||||
|
|
||||||
|
@command_llsetup_config_source.command(name="soundcloud", aliases=["sc"])
|
||||||
|
async def command_llsetup_config_source_soundcloud(self, ctx: commands.Context):
|
||||||
|
"""Toggle Soundcloud source on or off.
|
||||||
|
|
||||||
|
This toggle controls the playback of all Soundcloud related content.
|
||||||
|
"""
|
||||||
|
state = await self.config.yaml.lavalink.server.sources.soundcloud()
|
||||||
|
await self.config.yaml.lavalink.server.sources.soundcloud.set(not state)
|
||||||
|
if not state:
|
||||||
|
await self.send_embed_msg(
|
||||||
|
ctx,
|
||||||
|
title=_("Setting Changed"),
|
||||||
|
description=_(
|
||||||
|
"Managed node will allow playback from Soundcloud.\n\n"
|
||||||
|
"Run `{p}{cmd}` for it to take effect."
|
||||||
|
).format(p=ctx.prefix, cmd=self.command_audioset_restart.qualified_name),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await self.send_embed_msg(
|
||||||
|
ctx,
|
||||||
|
title=_("Setting Changed"),
|
||||||
|
description=_(
|
||||||
|
"Managed node will not play from Soundcloud anymore.\n\n"
|
||||||
|
"Run `{p}{cmd}` for it to take effect."
|
||||||
|
).format(p=ctx.prefix, cmd=self.command_audioset_restart.qualified_name),
|
||||||
|
)
|
||||||
|
|
||||||
|
@command_llsetup_config_source.command(name="youtube", aliases=["yt"])
|
||||||
|
async def command_llsetup_config_source_youtube(self, ctx: commands.Context):
|
||||||
|
"""`Dangerous command` Toggle YouTube source on or off (this includes Spotify).
|
||||||
|
|
||||||
|
This toggle controls the playback of all YouTube and Spotify related content.
|
||||||
|
"""
|
||||||
|
state = await self.config.yaml.lavalink.server.sources.youtube()
|
||||||
|
await self.config.yaml.lavalink.server.sources.youtube.set(not state)
|
||||||
|
if not state:
|
||||||
|
await self.send_embed_msg(
|
||||||
|
ctx,
|
||||||
|
title=_("Setting Changed"),
|
||||||
|
description=_(
|
||||||
|
"Managed node will allow playback from YouTube.\n\n"
|
||||||
|
"Run `{p}{cmd}` for it to take effect."
|
||||||
|
).format(p=ctx.prefix, cmd=self.command_audioset_restart.qualified_name),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await self.send_embed_msg(
|
||||||
|
ctx,
|
||||||
|
title=_("Setting Changed"),
|
||||||
|
description=_(
|
||||||
|
"Managed node will not play from YouTube anymore.\n\n"
|
||||||
|
"Run `{p}{cmd}` for it to take effect."
|
||||||
|
).format(p=ctx.prefix, cmd=self.command_audioset_restart.qualified_name),
|
||||||
|
)
|
||||||
|
|
||||||
|
@command_llsetup_config_source.command(name="twitch")
|
||||||
|
async def command_llsetup_config_source_twitch(self, ctx: commands.Context):
|
||||||
|
"""Toggle Twitch source on or off.
|
||||||
|
|
||||||
|
This toggle controls the playback of all Twitch related content.
|
||||||
|
"""
|
||||||
|
state = await self.config.yaml.lavalink.server.sources.twitch()
|
||||||
|
await self.config.yaml.lavalink.server.sources.twitch.set(not state)
|
||||||
|
if not state:
|
||||||
|
await self.send_embed_msg(
|
||||||
|
ctx,
|
||||||
|
title=_("Setting Changed"),
|
||||||
|
description=_(
|
||||||
|
"Managed node will allow playback from Twitch.\n\n"
|
||||||
|
"Run `{p}{cmd}` for it to take effect."
|
||||||
|
).format(p=ctx.prefix, cmd=self.command_audioset_restart.qualified_name),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await self.send_embed_msg(
|
||||||
|
ctx,
|
||||||
|
title=_("Setting Changed"),
|
||||||
|
description=_(
|
||||||
|
"Managed node will not play from Twitch anymore.\n\n"
|
||||||
|
"Run `{p}{cmd}` for it to take effect."
|
||||||
|
).format(p=ctx.prefix, cmd=self.command_audioset_restart.qualified_name),
|
||||||
|
)
|
||||||
|
|
||||||
|
@command_llsetup_config_source.command(name="vimeo")
|
||||||
|
async def command_llsetup_config_source_vimeo(self, ctx: commands.Context):
|
||||||
|
"""Toggle Vimeo source on or off.
|
||||||
|
|
||||||
|
This toggle controls the playback of all Vimeo related content.
|
||||||
|
"""
|
||||||
|
state = await self.config.yaml.lavalink.server.sources.vimeo()
|
||||||
|
await self.config.yaml.lavalink.server.sources.vimeo.set(not state)
|
||||||
|
if not state:
|
||||||
|
await self.send_embed_msg(
|
||||||
|
ctx,
|
||||||
|
title=_("Setting Changed"),
|
||||||
|
description=_(
|
||||||
|
"Managed node will allow playback from Vimeo.\n\n"
|
||||||
|
"Run `{p}{cmd}` for it to take effect."
|
||||||
|
).format(p=ctx.prefix, cmd=self.command_audioset_restart.qualified_name),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await self.send_embed_msg(
|
||||||
|
ctx,
|
||||||
|
title=_("Setting Changed"),
|
||||||
|
description=_(
|
||||||
|
"Managed node will not play from Vimeo anymore.\n\n"
|
||||||
|
"Run `{p}{cmd}` for it to take effect."
|
||||||
|
).format(p=ctx.prefix, cmd=self.command_audioset_restart.qualified_name),
|
||||||
|
)
|
||||||
|
|
||||||
|
@command_llsetup_config_server.command(name="framebuffer", aliases=["fb", "frame"])
|
||||||
|
async def command_llsetup_config_server_framebuffer(
|
||||||
|
self,
|
||||||
|
ctx: commands.Context,
|
||||||
|
*,
|
||||||
|
milliseconds: int = DEFAULT_LAVALINK_YAML["yaml__lavalink__server__frameBufferDurationMs"],
|
||||||
|
):
|
||||||
|
"""`Dangerous command` Set the managed Lavalink node framebuffer size.
|
||||||
|
|
||||||
|
Only change this if you have been directly advised to, changing it can cause significant playback issues.
|
||||||
|
"""
|
||||||
|
if milliseconds < 100:
|
||||||
|
return await self.send_embed_msg(
|
||||||
|
ctx,
|
||||||
|
title=_("Setting Not Changed"),
|
||||||
|
description=_("The lowest value the framebuffer can be set to is 100ms."),
|
||||||
|
)
|
||||||
|
await self.config.yaml.lavalink.server.frameBufferDurationMs.set(milliseconds)
|
||||||
|
await self.send_embed_msg(
|
||||||
|
ctx,
|
||||||
|
title=_("Setting Changed"),
|
||||||
|
description=_(
|
||||||
|
"Managed node's bufferDurationMs set to {milliseconds}.\n\n"
|
||||||
|
"Run `{p}{cmd}` for it to take effect."
|
||||||
|
).format(
|
||||||
|
milliseconds=inline(str(milliseconds)),
|
||||||
|
p=ctx.prefix,
|
||||||
|
cmd=self.command_audioset_restart.qualified_name,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
@command_llsetup_config_server.command(name="buffer", aliases=["b"])
|
||||||
|
async def command_llsetup_config_server_buffer(
|
||||||
|
self,
|
||||||
|
ctx: commands.Context,
|
||||||
|
*,
|
||||||
|
milliseconds: int = DEFAULT_LAVALINK_YAML["yaml__lavalink__server__bufferDurationMs"],
|
||||||
|
):
|
||||||
|
"""`Dangerous command` Set the managed Lavalink node NAS buffer size.
|
||||||
|
|
||||||
|
Only change this if you have been directly advised to, changing it can cause significant playback issues.
|
||||||
|
"""
|
||||||
|
if milliseconds < 100:
|
||||||
|
return await self.send_embed_msg(
|
||||||
|
ctx,
|
||||||
|
title=_("Setting Not Changed"),
|
||||||
|
description=_("The lowest value the buffer may be is 100ms."),
|
||||||
|
)
|
||||||
|
await self.config.yaml.lavalink.server.bufferDurationMs.set(milliseconds)
|
||||||
|
await self.send_embed_msg(
|
||||||
|
ctx,
|
||||||
|
title=_("Setting Changed"),
|
||||||
|
description=_(
|
||||||
|
"Managed node's bufferDurationMs set to {milliseconds}.\n\n"
|
||||||
|
"Run `{p}{cmd}` for it to take effect."
|
||||||
|
).format(
|
||||||
|
milliseconds=inline(str(milliseconds)),
|
||||||
|
p=ctx.prefix,
|
||||||
|
cmd=self.command_audioset_restart.qualified_name,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
@command_llsetup.command(name="reset")
|
||||||
|
async def command_llsetup_reset(self, ctx: commands.Context):
|
||||||
|
"""Reset all `llset` changes back to their default values."""
|
||||||
|
async with ctx.typing():
|
||||||
|
async with self.config.all() as global_data:
|
||||||
|
del global_data["yaml"]
|
||||||
|
for key in (*DEFAULT_LAVALINK_SETTINGS.keys(), *DEFAULT_LAVALINK_YAML.keys()):
|
||||||
|
if key in global_data:
|
||||||
|
del global_data[key]
|
||||||
|
del global_data["java"]
|
||||||
|
del global_data["java_exc_path"]
|
||||||
|
global_data["use_external_lavalink"] = False
|
||||||
|
|
||||||
|
try:
|
||||||
|
await lavalink.close(self.bot)
|
||||||
|
self.lavalink_restart_connect(manual=True)
|
||||||
|
except ProcessLookupError:
|
||||||
|
await self.send_embed_msg(
|
||||||
|
ctx,
|
||||||
|
title=_("Failed To Shutdown Lavalink Node"),
|
||||||
|
description=_("Please reload Audio (`{prefix}reload audio`).").format(
|
||||||
|
prefix=ctx.prefix
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|||||||
@ -10,6 +10,8 @@ import lavalink
|
|||||||
from red_commons.logging import getLogger
|
from red_commons.logging import getLogger
|
||||||
|
|
||||||
from discord.embeds import EmptyEmbed
|
from discord.embeds import EmptyEmbed
|
||||||
|
from lavalink import NodeNotFound
|
||||||
|
|
||||||
from redbot.core import commands
|
from redbot.core import commands
|
||||||
from redbot.core.commands import UserInputOptional
|
from redbot.core.commands import UserInputOptional
|
||||||
from redbot.core.i18n import Translator
|
from redbot.core.i18n import Translator
|
||||||
@ -64,7 +66,7 @@ class PlayerCommands(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
)
|
)
|
||||||
if not self._player_check(ctx):
|
if not self._player_check(ctx):
|
||||||
if self.lavalink_connection_aborted:
|
if self.lavalink_connection_aborted:
|
||||||
msg = _("Connection to Lavalink has failed")
|
msg = _("Connection to Lavalink node has failed")
|
||||||
desc = EmptyEmbed
|
desc = EmptyEmbed
|
||||||
if await self.bot.is_owner(ctx.author):
|
if await self.bot.is_owner(ctx.author):
|
||||||
desc = _("Please check your console or logs for details.")
|
desc = _("Please check your console or logs for details.")
|
||||||
@ -92,11 +94,11 @@ class PlayerCommands(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
title=_("Unable To Play Tracks"),
|
title=_("Unable To Play Tracks"),
|
||||||
description=_("Connect to a voice channel first."),
|
description=_("Connect to a voice channel first."),
|
||||||
)
|
)
|
||||||
except IndexError:
|
except NodeNotFound:
|
||||||
return await self.send_embed_msg(
|
return await self.send_embed_msg(
|
||||||
ctx,
|
ctx,
|
||||||
title=_("Unable To Play Tracks"),
|
title=_("Unable To Play Tracks"),
|
||||||
description=_("Connection to Lavalink has not yet been established."),
|
description=_("Connection to the Lavalink node has not yet been established."),
|
||||||
)
|
)
|
||||||
player = lavalink.get_player(ctx.guild.id)
|
player = lavalink.get_player(ctx.guild.id)
|
||||||
player.store("notify_channel", ctx.channel.id)
|
player.store("notify_channel", ctx.channel.id)
|
||||||
@ -172,7 +174,7 @@ class PlayerCommands(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
)
|
)
|
||||||
if not self._player_check(ctx):
|
if not self._player_check(ctx):
|
||||||
if self.lavalink_connection_aborted:
|
if self.lavalink_connection_aborted:
|
||||||
msg = _("Connection to Lavalink has failed")
|
msg = _("Connection to Lavalink node has failed")
|
||||||
desc = EmptyEmbed
|
desc = EmptyEmbed
|
||||||
if await self.bot.is_owner(ctx.author):
|
if await self.bot.is_owner(ctx.author):
|
||||||
desc = _("Please check your console or logs for details.")
|
desc = _("Please check your console or logs for details.")
|
||||||
@ -200,11 +202,11 @@ class PlayerCommands(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
title=_("Unable To Play Tracks"),
|
title=_("Unable To Play Tracks"),
|
||||||
description=_("Connect to a voice channel first."),
|
description=_("Connect to a voice channel first."),
|
||||||
)
|
)
|
||||||
except IndexError:
|
except NodeNotFound:
|
||||||
return await self.send_embed_msg(
|
return await self.send_embed_msg(
|
||||||
ctx,
|
ctx,
|
||||||
title=_("Unable To Play Tracks"),
|
title=_("Unable To Play Tracks"),
|
||||||
description=_("Connection to Lavalink has not yet been established."),
|
description=_("Connection to Lavalink node has not yet been established."),
|
||||||
)
|
)
|
||||||
player = lavalink.get_player(ctx.guild.id)
|
player = lavalink.get_player(ctx.guild.id)
|
||||||
player.store("notify_channel", ctx.channel.id)
|
player.store("notify_channel", ctx.channel.id)
|
||||||
@ -253,9 +255,9 @@ class PlayerCommands(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
if await self.config.use_external_lavalink() and query.is_local:
|
if await self.config.use_external_lavalink() and query.is_local:
|
||||||
embed.description = _(
|
embed.description = _(
|
||||||
"Local tracks will not work "
|
"Local tracks will not work "
|
||||||
"if the `Lavalink.jar` cannot see the track.\n"
|
"if the managed Lavalink node cannot see the track.\n"
|
||||||
"This may be due to permissions or because Lavalink.jar is being run "
|
"This may be due to permissions or you are using an external Lavalink node "
|
||||||
"in a different machine than the local tracks."
|
"in a different machine than the bot and the local tracks."
|
||||||
)
|
)
|
||||||
elif query.is_local and query.suffix in _PARTIALLY_SUPPORTED_MUSIC_EXT:
|
elif query.is_local and query.suffix in _PARTIALLY_SUPPORTED_MUSIC_EXT:
|
||||||
title = _("Track is not playable.")
|
title = _("Track is not playable.")
|
||||||
@ -285,7 +287,7 @@ class PlayerCommands(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
f"{single_track.title} {single_track.author} {single_track.uri} {str(query)}",
|
f"{single_track.title} {single_track.author} {single_track.uri} {str(query)}",
|
||||||
query_obj=query,
|
query_obj=query,
|
||||||
):
|
):
|
||||||
log.debug("Query is not allowed in %r (%d)", ctx.guild.name, ctx.guild.id)
|
log.debug("Query is not allowed in %r (%s)", ctx.guild.name, ctx.guild.id)
|
||||||
self.update_player_lock(ctx, False)
|
self.update_player_lock(ctx, False)
|
||||||
return await self.send_embed_msg(
|
return await self.send_embed_msg(
|
||||||
ctx,
|
ctx,
|
||||||
@ -435,7 +437,7 @@ class PlayerCommands(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
)
|
)
|
||||||
if not self._player_check(ctx):
|
if not self._player_check(ctx):
|
||||||
if self.lavalink_connection_aborted:
|
if self.lavalink_connection_aborted:
|
||||||
msg = _("Connection to Lavalink has failed")
|
msg = _("Connection to Lavalink node has failed")
|
||||||
desc = EmptyEmbed
|
desc = EmptyEmbed
|
||||||
if await self.bot.is_owner(ctx.author):
|
if await self.bot.is_owner(ctx.author):
|
||||||
desc = _("Please check your console or logs for details.")
|
desc = _("Please check your console or logs for details.")
|
||||||
@ -463,11 +465,11 @@ class PlayerCommands(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
title=_("Unable To Play Tracks"),
|
title=_("Unable To Play Tracks"),
|
||||||
description=_("Connect to a voice channel first."),
|
description=_("Connect to a voice channel first."),
|
||||||
)
|
)
|
||||||
except IndexError:
|
except NodeNotFound:
|
||||||
return await self.send_embed_msg(
|
return await self.send_embed_msg(
|
||||||
ctx,
|
ctx,
|
||||||
title=_("Unable To Play Tracks"),
|
title=_("Unable To Play Tracks"),
|
||||||
description=_("Connection to Lavalink has not yet been established."),
|
description=_("Connection to Lavalink node has not yet been established."),
|
||||||
)
|
)
|
||||||
player = lavalink.get_player(ctx.guild.id)
|
player = lavalink.get_player(ctx.guild.id)
|
||||||
player.store("notify_channel", ctx.channel.id)
|
player.store("notify_channel", ctx.channel.id)
|
||||||
@ -551,7 +553,7 @@ class PlayerCommands(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
)
|
)
|
||||||
if not self._player_check(ctx):
|
if not self._player_check(ctx):
|
||||||
if self.lavalink_connection_aborted:
|
if self.lavalink_connection_aborted:
|
||||||
msg = _("Connection to Lavalink has failed")
|
msg = _("Connection to Lavalink node has failed")
|
||||||
desc = EmptyEmbed
|
desc = EmptyEmbed
|
||||||
if await self.bot.is_owner(ctx.author):
|
if await self.bot.is_owner(ctx.author):
|
||||||
desc = _("Please check your console or logs for details.")
|
desc = _("Please check your console or logs for details.")
|
||||||
@ -579,11 +581,11 @@ class PlayerCommands(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
title=_("Unable To Play Tracks"),
|
title=_("Unable To Play Tracks"),
|
||||||
description=_("Connect to a voice channel first."),
|
description=_("Connect to a voice channel first."),
|
||||||
)
|
)
|
||||||
except IndexError:
|
except NodeNotFound:
|
||||||
return await self.send_embed_msg(
|
return await self.send_embed_msg(
|
||||||
ctx,
|
ctx,
|
||||||
title=_("Unable To Play Tracks"),
|
title=_("Unable To Play Tracks"),
|
||||||
description=_("Connection to Lavalink has not yet been established."),
|
description=_("Connection to Lavalink node has not yet been established."),
|
||||||
)
|
)
|
||||||
player = lavalink.get_player(ctx.guild.id)
|
player = lavalink.get_player(ctx.guild.id)
|
||||||
player.store("notify_channel", ctx.channel.id)
|
player.store("notify_channel", ctx.channel.id)
|
||||||
@ -608,7 +610,7 @@ class PlayerCommands(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
except DatabaseError:
|
except DatabaseError:
|
||||||
notify_channel = player.fetch("notify_channel")
|
notify_channel = player.fetch("notify_channel")
|
||||||
if notify_channel:
|
if notify_channel:
|
||||||
notify_channel = self.bot.get_channel(notify_channel)
|
notify_channel = ctx.guild.get_channel(notify_channel)
|
||||||
await self.send_embed_msg(notify_channel, title=_("Couldn't get a valid track."))
|
await self.send_embed_msg(notify_channel, title=_("Couldn't get a valid track."))
|
||||||
return
|
return
|
||||||
except TrackEnqueueError:
|
except TrackEnqueueError:
|
||||||
@ -703,11 +705,11 @@ class PlayerCommands(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
title=_("Unable To Search For Tracks"),
|
title=_("Unable To Search For Tracks"),
|
||||||
description=_("Connect to a voice channel first."),
|
description=_("Connect to a voice channel first."),
|
||||||
)
|
)
|
||||||
except IndexError:
|
except NodeNotFound:
|
||||||
return await self.send_embed_msg(
|
return await self.send_embed_msg(
|
||||||
ctx,
|
ctx,
|
||||||
title=_("Unable To Search For Tracks"),
|
title=_("Unable To Search For Tracks"),
|
||||||
description=_("Connection to Lavalink has not yet been established."),
|
description=_("Connection to Lavalink node has not yet been established."),
|
||||||
)
|
)
|
||||||
player = lavalink.get_player(ctx.guild.id)
|
player = lavalink.get_player(ctx.guild.id)
|
||||||
guild_data = await self.config.guild(ctx.guild).all()
|
guild_data = await self.config.guild(ctx.guild).all()
|
||||||
@ -815,7 +817,7 @@ class PlayerCommands(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
f"{track.title} {track.author} {track.uri} " f"{str(query)}",
|
f"{track.title} {track.author} {track.uri} " f"{str(query)}",
|
||||||
query_obj=query,
|
query_obj=query,
|
||||||
):
|
):
|
||||||
log.debug("Query is not allowed in %r (%d)", ctx.guild.name, ctx.guild.id)
|
log.debug("Query is not allowed in %r (%s)", ctx.guild.name, ctx.guild.id)
|
||||||
continue
|
continue
|
||||||
elif guild_data["maxlength"] > 0:
|
elif guild_data["maxlength"] > 0:
|
||||||
if self.is_track_length_allowed(track, guild_data["maxlength"]):
|
if self.is_track_length_allowed(track, guild_data["maxlength"]):
|
||||||
|
|||||||
@ -1521,7 +1521,7 @@ class PlaylistCommands(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
f"{track.title} {track.author} {track.uri} " f"{str(query)}",
|
f"{track.title} {track.author} {track.uri} " f"{str(query)}",
|
||||||
query_obj=query,
|
query_obj=query,
|
||||||
):
|
):
|
||||||
log.debug("Query is not allowed in %r (%d)", ctx.guild.name, ctx.guild.id)
|
log.debug("Query is not allowed in %r (%s)", ctx.guild.name, ctx.guild.id)
|
||||||
continue
|
continue
|
||||||
query = Query.process_input(track.uri, self.local_folder_current_path)
|
query = Query.process_input(track.uri, self.local_folder_current_path)
|
||||||
if query.is_local:
|
if query.is_local:
|
||||||
@ -1921,7 +1921,7 @@ class PlaylistCommands(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
ctx,
|
ctx,
|
||||||
title=_("Unable to Get Track"),
|
title=_("Unable to Get Track"),
|
||||||
description=_(
|
description=_(
|
||||||
"I'm unable to get a track from Lavalink at the moment, try again in a few "
|
"I'm unable to get a track from Lavalink node at the moment, try again in a few "
|
||||||
"minutes."
|
"minutes."
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@ -7,6 +7,7 @@ from typing import MutableMapping, Optional
|
|||||||
|
|
||||||
import discord
|
import discord
|
||||||
import lavalink
|
import lavalink
|
||||||
|
from lavalink import NodeNotFound, PlayerNotFound
|
||||||
from red_commons.logging import getLogger
|
from red_commons.logging import getLogger
|
||||||
|
|
||||||
from redbot.core import commands
|
from redbot.core import commands
|
||||||
@ -178,7 +179,7 @@ class QueueCommands(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
"""Clears the queue."""
|
"""Clears the queue."""
|
||||||
try:
|
try:
|
||||||
player = lavalink.get_player(ctx.guild.id)
|
player = lavalink.get_player(ctx.guild.id)
|
||||||
except KeyError:
|
except (NodeNotFound, PlayerNotFound):
|
||||||
return await self.send_embed_msg(ctx, title=_("There's nothing in the queue."))
|
return await self.send_embed_msg(ctx, title=_("There's nothing in the queue."))
|
||||||
dj_enabled = self._dj_status_cache.setdefault(
|
dj_enabled = self._dj_status_cache.setdefault(
|
||||||
ctx.guild.id, await self.config.guild(ctx.guild).dj_enabled()
|
ctx.guild.id, await self.config.guild(ctx.guild).dj_enabled()
|
||||||
@ -209,7 +210,7 @@ class QueueCommands(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
"""Removes songs from the queue if the requester is not in the voice channel."""
|
"""Removes songs from the queue if the requester is not in the voice channel."""
|
||||||
try:
|
try:
|
||||||
player = lavalink.get_player(ctx.guild.id)
|
player = lavalink.get_player(ctx.guild.id)
|
||||||
except KeyError:
|
except (NodeNotFound, PlayerNotFound):
|
||||||
return await self.send_embed_msg(ctx, title=_("There's nothing in the queue."))
|
return await self.send_embed_msg(ctx, title=_("There's nothing in the queue."))
|
||||||
dj_enabled = self._dj_status_cache.setdefault(
|
dj_enabled = self._dj_status_cache.setdefault(
|
||||||
ctx.guild.id, await self.config.guild(ctx.guild).dj_enabled()
|
ctx.guild.id, await self.config.guild(ctx.guild).dj_enabled()
|
||||||
@ -256,7 +257,7 @@ class QueueCommands(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
player = lavalink.get_player(ctx.guild.id)
|
player = lavalink.get_player(ctx.guild.id)
|
||||||
except KeyError:
|
except (NodeNotFound, PlayerNotFound):
|
||||||
return await self.send_embed_msg(ctx, title=_("There's nothing in the queue."))
|
return await self.send_embed_msg(ctx, title=_("There's nothing in the queue."))
|
||||||
if not self._player_check(ctx) or not player.queue:
|
if not self._player_check(ctx) or not player.queue:
|
||||||
return await self.send_embed_msg(ctx, title=_("There's nothing in the queue."))
|
return await self.send_embed_msg(ctx, title=_("There's nothing in the queue."))
|
||||||
@ -288,7 +289,7 @@ class QueueCommands(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
"""Search the queue."""
|
"""Search the queue."""
|
||||||
try:
|
try:
|
||||||
player = lavalink.get_player(ctx.guild.id)
|
player = lavalink.get_player(ctx.guild.id)
|
||||||
except KeyError:
|
except (NodeNotFound, PlayerNotFound):
|
||||||
return await self.send_embed_msg(ctx, title=_("There's nothing in the queue."))
|
return await self.send_embed_msg(ctx, title=_("There's nothing in the queue."))
|
||||||
if not self._player_check(ctx) or not player.queue:
|
if not self._player_check(ctx) or not player.queue:
|
||||||
return await self.send_embed_msg(ctx, title=_("There's nothing in the queue."))
|
return await self.send_embed_msg(ctx, title=_("There's nothing in the queue."))
|
||||||
@ -353,12 +354,12 @@ class QueueCommands(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
title=_("Unable To Shuffle Queue"),
|
title=_("Unable To Shuffle Queue"),
|
||||||
description=_("Connect to a voice channel first."),
|
description=_("Connect to a voice channel first."),
|
||||||
)
|
)
|
||||||
except IndexError:
|
except NodeNotFound:
|
||||||
ctx.command.reset_cooldown(ctx)
|
ctx.command.reset_cooldown(ctx)
|
||||||
return await self.send_embed_msg(
|
return await self.send_embed_msg(
|
||||||
ctx,
|
ctx,
|
||||||
title=_("Unable To Shuffle Queue"),
|
title=_("Unable To Shuffle Queue"),
|
||||||
description=_("Connection to Lavalink has not yet been established."),
|
description=_("Connection to Lavalink node has not yet been established."),
|
||||||
)
|
)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
ctx.command.reset_cooldown(ctx)
|
ctx.command.reset_cooldown(ctx)
|
||||||
|
|||||||
@ -123,7 +123,7 @@ class AudioEvents(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
bot=self.bot,
|
bot=self.bot,
|
||||||
)
|
)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
log.verbose("Failed to delete daily playlist ID: %d", too_old_id, exc_info=exc)
|
log.verbose("Failed to delete daily playlist ID: %s", too_old_id, exc_info=exc)
|
||||||
try:
|
try:
|
||||||
await delete_playlist(
|
await delete_playlist(
|
||||||
scope=PlaylistScope.GLOBAL.value,
|
scope=PlaylistScope.GLOBAL.value,
|
||||||
@ -135,7 +135,7 @@ class AudioEvents(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
)
|
)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
log.verbose(
|
log.verbose(
|
||||||
"Failed to delete global daily playlist ID: %d", too_old_id, exc_info=exc
|
"Failed to delete global daily playlist ID: %s", too_old_id, exc_info=exc
|
||||||
)
|
)
|
||||||
persist_cache = self._persist_queue_cache.setdefault(
|
persist_cache = self._persist_queue_cache.setdefault(
|
||||||
guild.id, await self.config.guild(guild).persist_queue()
|
guild.id, await self.config.guild(guild).persist_queue()
|
||||||
@ -195,7 +195,9 @@ class AudioEvents(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
requester: discord.Member,
|
requester: discord.Member,
|
||||||
player: lavalink.Player,
|
player: lavalink.Player,
|
||||||
):
|
):
|
||||||
notify_channel = self.bot.get_channel(player.fetch("notify_channel"))
|
if not guild:
|
||||||
|
return
|
||||||
|
notify_channel = guild.get_channel(player.fetch("notify_channel"))
|
||||||
has_perms = self._has_notify_perms(notify_channel)
|
has_perms = self._has_notify_perms(notify_channel)
|
||||||
tries = 0
|
tries = 0
|
||||||
while not player._is_playing:
|
while not player._is_playing:
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import contextlib
|
import contextlib
|
||||||
|
import random
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from string import ascii_letters, digits
|
||||||
from typing import Final, Pattern
|
from typing import Final, Pattern
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
@ -12,19 +14,145 @@ from red_commons.logging import getLogger
|
|||||||
|
|
||||||
from aiohttp import ClientConnectorError
|
from aiohttp import ClientConnectorError
|
||||||
from discord.ext.commands import CheckFailure
|
from discord.ext.commands import CheckFailure
|
||||||
|
from lavalink import NodeNotFound, PlayerNotFound
|
||||||
|
|
||||||
from redbot.core import commands
|
from redbot.core import commands
|
||||||
from redbot.core.i18n import Translator
|
from redbot.core.i18n import Translator
|
||||||
from redbot.core.utils.chat_formatting import box, humanize_list
|
from redbot.core.utils.antispam import AntiSpam
|
||||||
|
from redbot.core.utils.chat_formatting import box, humanize_list, underline, bold
|
||||||
|
|
||||||
from ...errors import TrackEnqueueError
|
from ...errors import TrackEnqueueError, AudioError
|
||||||
from ..abc import MixinMeta
|
from ..abc import MixinMeta
|
||||||
from ..cog_utils import HUMANIZED_PERM, CompositeMetaClass
|
from ..cog_utils import CompositeMetaClass
|
||||||
from ...utils import task_callback_trace
|
|
||||||
|
|
||||||
log = getLogger("red.cogs.Audio.cog.Events.dpy")
|
log = getLogger("red.cogs.Audio.cog.Events.dpy")
|
||||||
_ = Translator("Audio", Path(__file__))
|
_T = Translator("Audio", Path(__file__))
|
||||||
|
_ = lambda s: s
|
||||||
RE_CONVERSION: Final[Pattern] = re.compile('Converting to "(.*)" failed for parameter "(.*)".')
|
RE_CONVERSION: Final[Pattern] = re.compile('Converting to "(.*)" failed for parameter "(.*)".')
|
||||||
|
HUMANIZED_PERM = {
|
||||||
|
"create_instant_invite": _("Create Instant Invite"),
|
||||||
|
"kick_members": _("Kick Members"),
|
||||||
|
"ban_members": _("Ban Members"),
|
||||||
|
"administrator": _("Administrator"),
|
||||||
|
"manage_channels": _("Manage Channels"),
|
||||||
|
"manage_guild": _("Manage Server"),
|
||||||
|
"add_reactions": _("Add Reactions"),
|
||||||
|
"view_audit_log": _("View Audit Log"),
|
||||||
|
"priority_speaker": _("Priority Speaker"),
|
||||||
|
"stream": _("Go Live"),
|
||||||
|
"read_messages": _("Read Text Channels & See Voice Channels"),
|
||||||
|
"send_messages": _("Send Messages"),
|
||||||
|
"send_tts_messages": _("Send TTS Messages"),
|
||||||
|
"manage_messages": _("Manage Messages"),
|
||||||
|
"embed_links": _("Embed Links"),
|
||||||
|
"attach_files": _("Attach Files"),
|
||||||
|
"read_message_history": _("Read Message History"),
|
||||||
|
"mention_everyone": _("Mention @everyone, @here, and All Roles"),
|
||||||
|
"external_emojis": _("Use External Emojis"),
|
||||||
|
"view_guild_insights": _("View Server Insights"),
|
||||||
|
"connect": _("Connect"),
|
||||||
|
"speak": _("Speak"),
|
||||||
|
"mute_members": _("Mute Members"),
|
||||||
|
"deafen_members": _("Deafen Members"),
|
||||||
|
"move_members": _("Move Members"),
|
||||||
|
"use_voice_activation": _("Use Voice Activity"),
|
||||||
|
"change_nickname": _("Change Nickname"),
|
||||||
|
"manage_nicknames": _("Manage Nicknames"),
|
||||||
|
"manage_roles": _("Manage Roles"),
|
||||||
|
"manage_webhooks": _("Manage Webhooks"),
|
||||||
|
"manage_emojis": _("Manage Emojis"),
|
||||||
|
}
|
||||||
|
|
||||||
|
DANGEROUS_COMMANDS = {
|
||||||
|
"command_llsetup_java": _(
|
||||||
|
"This command will change the executable path of Java, "
|
||||||
|
"this is useful if you have multiple installations of Java and the default one is causing issues. "
|
||||||
|
"Please don't change this unless you are certain that the Java version you are specifying is supported by Red. "
|
||||||
|
"The default and supported version is currently Java 11."
|
||||||
|
),
|
||||||
|
"command_llsetup_heapsize": _(
|
||||||
|
"This command will change the maximum RAM allocation for the managed Lavalink node, "
|
||||||
|
"usually you will never have to change this, "
|
||||||
|
"before considering changing it please consult our support team."
|
||||||
|
),
|
||||||
|
"command_llsetup_external": _(
|
||||||
|
"This command will disable the managed Lavalink node, "
|
||||||
|
"if you toggle this command you must specify an external Lavalink node to connect to, "
|
||||||
|
"if you do not do so Audio will stop working."
|
||||||
|
),
|
||||||
|
"command_llsetup_host": _(
|
||||||
|
"This command is used to specify the IP which will be used by Red to connect to an external Lavalink node. "
|
||||||
|
),
|
||||||
|
"command_llsetup_password": _(
|
||||||
|
"This command is used to specify the authentication password used by Red to connect to an "
|
||||||
|
"external Lavalink node."
|
||||||
|
),
|
||||||
|
"command_llsetup_secured": _(
|
||||||
|
"This command is used toggle between secured and unsecured connections to an external Lavalink node."
|
||||||
|
),
|
||||||
|
"command_llsetup_wsport": _(
|
||||||
|
"This command is used to specify the connection port used by Red to connect to an external Lavalink node."
|
||||||
|
),
|
||||||
|
"command_llsetup_config_host": _(
|
||||||
|
"This command specifies which network interface and IP the managed Lavalink node will bind to, "
|
||||||
|
"by default this is 'localhost', "
|
||||||
|
"only change this if you want the managed Lavalink node to bind to a specific IP/interface."
|
||||||
|
),
|
||||||
|
"command_llsetup_config_token": _(
|
||||||
|
"This command changes the authentication password required to connect to this managed node."
|
||||||
|
"The default value is 'youshallnotpass'."
|
||||||
|
),
|
||||||
|
"command_llsetup_config_port": _(
|
||||||
|
"This command changes the connection port used to connect to this managed node, "
|
||||||
|
"only change this if the default port '2333' is causing conflicts with existing applications."
|
||||||
|
),
|
||||||
|
"command_llsetup_config_source_http": _(
|
||||||
|
"This command toggles the support of direct url streams like Icecast or Shoutcast streams. "
|
||||||
|
"An example is <http://ice6.somafm.com/gsclassic-128-mp3>; "
|
||||||
|
"Disabling this will make the bot unable to play any direct url steam content."
|
||||||
|
),
|
||||||
|
"command_llsetup_config_source_bandcamp": _(
|
||||||
|
"This command toggles the support of Bandcamp audio playback. "
|
||||||
|
"An example is <http://deaddiskdrive.bandcamp.com/track/crystal-glass>; "
|
||||||
|
"Disabling this will make the bot unable to play any Bandcamp content",
|
||||||
|
),
|
||||||
|
"command_llsetup_config_source_local": _(
|
||||||
|
"This command toggles the support of local track audio playback. "
|
||||||
|
"for example `/mnt/data/my_super_funky_track.mp3`; "
|
||||||
|
"Disabling this will make the bot unable to play any local track content."
|
||||||
|
),
|
||||||
|
"command_llsetup_config_source_soundcloud": _(
|
||||||
|
"This command toggles the support of Soundcloud playback. "
|
||||||
|
"An example is <https://soundcloud.com/user-103858850/tilla>; "
|
||||||
|
"Disabling this will make the bot unable to play any Soundcloud content."
|
||||||
|
),
|
||||||
|
"command_llsetup_config_source_youtube": _(
|
||||||
|
"This command toggles the support of YouTube playback (Spotify depends on YouTube). "
|
||||||
|
"Disabling this will make the bot unable to play any YouTube content, "
|
||||||
|
"this includes Spotify."
|
||||||
|
),
|
||||||
|
"command_llsetup_config_source_twitch": _(
|
||||||
|
"This command toggles the support of Twitch playback. "
|
||||||
|
"An example of this is <https://twitch.tv/monstercat>; "
|
||||||
|
"Disabling this will make the bot unable to play any Twitch content."
|
||||||
|
),
|
||||||
|
"command_llsetup_config_source_vimeo": _(
|
||||||
|
"This command toggles the support of Vimeo playback. "
|
||||||
|
"An example of this is <https://vimeo.com/157743578>; "
|
||||||
|
"Disabling this will make the bot unable to play any Vimeo content."
|
||||||
|
),
|
||||||
|
"command_llsetup_config_server_framebuffer": _(
|
||||||
|
"This setting controls the managed nodes framebuffer, "
|
||||||
|
"Do not change this unless instructed."
|
||||||
|
),
|
||||||
|
"command_llsetup_config_server_buffer": _(
|
||||||
|
"This setting controls the managed nodes NAS buffer, "
|
||||||
|
"Do not change this unless instructed."
|
||||||
|
),
|
||||||
|
"command_llsetup_reset": _("This command will reset every setting changed by `[p]llset`."),
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = _T
|
||||||
|
|
||||||
|
|
||||||
class DpyEvents(MixinMeta, metaclass=CompositeMetaClass):
|
class DpyEvents(MixinMeta, metaclass=CompositeMetaClass):
|
||||||
@ -39,12 +167,12 @@ class DpyEvents(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
elif self.lavalink_connect_task and self.lavalink_connect_task.cancelled():
|
elif self.lavalink_connect_task and self.lavalink_connect_task.cancelled():
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_(
|
_(
|
||||||
"You have attempted to run Audio's Lavalink server on an unsupported"
|
"You have attempted to run Audio's managed Lavalink node on an unsupported"
|
||||||
" architecture. Only settings related commands will be available."
|
" architecture. Only settings related commands will be available."
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
raise RuntimeError(
|
raise AudioError(
|
||||||
"Not running audio command due to invalid machine architecture for Lavalink."
|
"Not running Audio command due to invalid machine architecture for the managed Lavalink node."
|
||||||
)
|
)
|
||||||
|
|
||||||
current_perms = ctx.channel.permissions_for(ctx.me)
|
current_perms = ctx.channel.permissions_for(ctx.me)
|
||||||
@ -62,7 +190,7 @@ class DpyEvents(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
missing_perms = OrderedDict(sorted(missing_perms.items()))
|
missing_perms = OrderedDict(sorted(missing_perms.items()))
|
||||||
missing_permissions = missing_perms.keys()
|
missing_permissions = missing_perms.keys()
|
||||||
log.debug(
|
log.debug(
|
||||||
"Missing the following perms in %d, Owner ID: %d: %s",
|
"Missing the following perms in %s, Owner ID: %s: %s",
|
||||||
ctx.guild.id,
|
ctx.guild.id,
|
||||||
ctx.guild.owner.id,
|
ctx.guild.owner.id,
|
||||||
humanize_list(list(missing_permissions)),
|
humanize_list(list(missing_permissions)),
|
||||||
@ -76,14 +204,14 @@ class DpyEvents(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
for perm, value in missing_perms.items():
|
for perm, value in missing_perms.items():
|
||||||
text += "{perm}: [{status}]\n".format(
|
text += "{perm}: [{status}]\n".format(
|
||||||
status=_("Enabled") if value else _("Disabled"),
|
status=_("Enabled") if value else _("Disabled"),
|
||||||
perm=HUMANIZED_PERM.get(perm),
|
perm=_(HUMANIZED_PERM.get(perm, perm)),
|
||||||
)
|
)
|
||||||
text = text.strip()
|
text = text.strip()
|
||||||
if current_perms.send_messages and current_perms.read_messages:
|
if current_perms.send_messages and current_perms.read_messages:
|
||||||
await ctx.send(box(text=text, lang="ini"))
|
await ctx.send(box(text=text, lang="ini"))
|
||||||
else:
|
else:
|
||||||
log.info(
|
log.info(
|
||||||
"Missing write permission in %d, Owner ID: %d",
|
"Missing write permission in %s, Owner ID: %s",
|
||||||
ctx.guild.id,
|
ctx.guild.id,
|
||||||
ctx.guild.owner.id,
|
ctx.guild.owner.id,
|
||||||
)
|
)
|
||||||
@ -100,27 +228,65 @@ class DpyEvents(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
)
|
)
|
||||||
if self.local_folder_current_path is None:
|
if self.local_folder_current_path is None:
|
||||||
self.local_folder_current_path = Path(await self.config.localpath())
|
self.local_folder_current_path = Path(await self.config.localpath())
|
||||||
if not ctx.guild:
|
|
||||||
return
|
|
||||||
|
|
||||||
dj_enabled = self._dj_status_cache.setdefault(
|
if ctx.command.callback.__name__ in DANGEROUS_COMMANDS and await ctx.bot.is_owner(
|
||||||
ctx.guild.id, await self.config.guild(ctx.guild).dj_enabled()
|
ctx.author
|
||||||
|
):
|
||||||
|
if ctx.command.callback.__name__ not in self.antispam[ctx.author.id]:
|
||||||
|
self.antispam[ctx.author.id][ctx.command.callback.__name__] = AntiSpam(
|
||||||
|
self.llset_captcha_intervals
|
||||||
)
|
)
|
||||||
self._daily_playlist_cache.setdefault(
|
if not self.antispam[ctx.author.id][ctx.command.callback.__name__].spammy:
|
||||||
ctx.guild.id, await self.config.guild(ctx.guild).daily_playlists()
|
token = random.choices((*ascii_letters, *digits), k=4)
|
||||||
|
confirm_token = " ".join(i for i in token)
|
||||||
|
token = confirm_token.replace(" ", "")
|
||||||
|
message = bold(
|
||||||
|
underline(_("You should not be running this command.")),
|
||||||
|
escape_formatting=False,
|
||||||
)
|
)
|
||||||
self._persist_queue_cache.setdefault(
|
message += _(
|
||||||
ctx.guild.id, await self.config.guild(ctx.guild).persist_queue()
|
"\n{template}\n"
|
||||||
|
"If you wish to continue, enter this case sensitive token without spaces as your next message."
|
||||||
|
"\n\n{confirm_token}"
|
||||||
|
).format(
|
||||||
|
template=_(DANGEROUS_COMMANDS[ctx.command.callback.__name__]),
|
||||||
|
confirm_token=box(confirm_token, lang="py"),
|
||||||
)
|
)
|
||||||
|
sent = await ctx.send(message)
|
||||||
|
try:
|
||||||
|
message = await ctx.bot.wait_for(
|
||||||
|
"message",
|
||||||
|
check=lambda m: m.channel.id == ctx.channel.id
|
||||||
|
and m.author.id == ctx.author.id,
|
||||||
|
timeout=120,
|
||||||
|
)
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
with contextlib.suppress(discord.HTTPException):
|
||||||
|
await sent.add_reaction("\N{CROSS MARK}")
|
||||||
|
raise commands.CheckFailure
|
||||||
|
else:
|
||||||
|
if message.content.strip() != token:
|
||||||
|
with contextlib.suppress(discord.HTTPException):
|
||||||
|
await sent.add_reaction("\N{CROSS MARK}")
|
||||||
|
raise commands.CheckFailure
|
||||||
|
with contextlib.suppress(discord.HTTPException):
|
||||||
|
await sent.add_reaction("\N{WHITE HEAVY CHECK MARK}")
|
||||||
|
self.antispam[ctx.author.id][ctx.command.callback.__name__].stamp()
|
||||||
|
|
||||||
|
if not guild:
|
||||||
|
return
|
||||||
|
guild_data = await self.config.guild(ctx.guild).all()
|
||||||
|
dj_enabled = self._dj_status_cache.setdefault(ctx.guild.id, guild_data["dj_enabled"])
|
||||||
|
self._daily_playlist_cache.setdefault(ctx.guild.id, guild_data["daily_playlists"])
|
||||||
|
self._persist_queue_cache.setdefault(ctx.guild.id, guild_data["persist_queue"])
|
||||||
if dj_enabled:
|
if dj_enabled:
|
||||||
dj_role = self._dj_role_cache.setdefault(
|
dj_role = self._dj_role_cache.setdefault(ctx.guild.id, guild_data["dj_role"])
|
||||||
ctx.guild.id, await self.config.guild(ctx.guild).dj_role()
|
|
||||||
)
|
|
||||||
dj_role_obj = ctx.guild.get_role(dj_role)
|
dj_role_obj = ctx.guild.get_role(dj_role)
|
||||||
if not dj_role_obj:
|
if not dj_role_obj:
|
||||||
await self.config.guild(ctx.guild).dj_enabled.set(None)
|
async with self.config.guild(ctx.guild).all() as write_guild_data:
|
||||||
|
write_guild_data["dj_enabled"] = None
|
||||||
|
write_guild_data["dj_role"] = None
|
||||||
self._dj_status_cache[ctx.guild.id] = None
|
self._dj_status_cache[ctx.guild.id] = None
|
||||||
await self.config.guild(ctx.guild).dj_role.set(None)
|
|
||||||
self._dj_role_cache[ctx.guild.id] = None
|
self._dj_role_cache[ctx.guild.id] = None
|
||||||
await self.send_embed_msg(ctx, title=_("No DJ role found. Disabling DJ mode."))
|
await self.send_embed_msg(ctx, title=_("No DJ role found. Disabling DJ mode."))
|
||||||
|
|
||||||
@ -167,18 +333,16 @@ class DpyEvents(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
await ctx.send_help()
|
await ctx.send_help()
|
||||||
elif isinstance(error, (IndexError, ClientConnectorError)) and any(
|
elif isinstance(error, (NodeNotFound, ClientConnectorError)):
|
||||||
e in str(error).lower() for e in ["no nodes found.", "cannot connect to host"]
|
|
||||||
):
|
|
||||||
handled = True
|
handled = True
|
||||||
await self.send_embed_msg(
|
await self.send_embed_msg(
|
||||||
ctx,
|
ctx,
|
||||||
title=_("Invalid Environment"),
|
title=_("Invalid Environment"),
|
||||||
description=_("Connection to Lavalink has been lost."),
|
description=_("Connection to Lavalink node has been lost."),
|
||||||
error=True,
|
error=True,
|
||||||
)
|
)
|
||||||
log.trace("This is a handled error", exc_info=error)
|
log.trace("This is a handled error", exc_info=error)
|
||||||
elif isinstance(error, KeyError) and "such player for that guild" in str(error):
|
elif isinstance(error, PlayerNotFound):
|
||||||
handled = True
|
handled = True
|
||||||
await self.send_embed_msg(
|
await self.send_embed_msg(
|
||||||
ctx,
|
ctx,
|
||||||
@ -193,7 +357,7 @@ class DpyEvents(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
ctx,
|
ctx,
|
||||||
title=_("Unable to Get Track"),
|
title=_("Unable to Get Track"),
|
||||||
description=_(
|
description=_(
|
||||||
"I'm unable to get a track from Lavalink at the moment, "
|
"I'm unable to get a track from the Lavalink node at the moment, "
|
||||||
"try again in a few minutes."
|
"try again in a few minutes."
|
||||||
),
|
),
|
||||||
error=True,
|
error=True,
|
||||||
@ -230,7 +394,6 @@ class DpyEvents(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
if not self.cog_cleaned_up:
|
if not self.cog_cleaned_up:
|
||||||
self.bot.dispatch("red_audio_unload", self)
|
self.bot.dispatch("red_audio_unload", self)
|
||||||
self.session.detach()
|
self.session.detach()
|
||||||
asyncio.create_task(self._close_database()).add_done_callback(task_callback_trace)
|
|
||||||
if self.player_automated_timer_task:
|
if self.player_automated_timer_task:
|
||||||
self.player_automated_timer_task.cancel()
|
self.player_automated_timer_task.cancel()
|
||||||
|
|
||||||
@ -245,11 +408,10 @@ class DpyEvents(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
|
|
||||||
lavalink.unregister_event_listener(self.lavalink_event_handler)
|
lavalink.unregister_event_listener(self.lavalink_event_handler)
|
||||||
lavalink.unregister_update_listener(self.lavalink_update_handler)
|
lavalink.unregister_update_listener(self.lavalink_update_handler)
|
||||||
asyncio.create_task(lavalink.close(self.bot)).add_done_callback(task_callback_trace)
|
asyncio.create_task(lavalink.close(self.bot))
|
||||||
if self.player_manager is not None:
|
asyncio.create_task(self._close_database())
|
||||||
asyncio.create_task(self.player_manager.shutdown()).add_done_callback(
|
if self.managed_node_controller is not None:
|
||||||
task_callback_trace
|
asyncio.create_task(self.managed_node_controller.shutdown())
|
||||||
)
|
|
||||||
|
|
||||||
self.cog_cleaned_up = True
|
self.cog_cleaned_up = True
|
||||||
|
|
||||||
@ -275,7 +437,7 @@ class DpyEvents(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
player = lavalink.get_player(channel.guild.id)
|
player = lavalink.get_player(channel.guild.id)
|
||||||
except (KeyError, AttributeError):
|
except (NodeNotFound, PlayerNotFound, AttributeError):
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
if player.channel.id == channel.id:
|
if player.channel.id == channel.id:
|
||||||
@ -283,12 +445,12 @@ class DpyEvents(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
|
|
||||||
@commands.Cog.listener()
|
@commands.Cog.listener()
|
||||||
async def on_shard_disconnect(self, shard_id):
|
async def on_shard_disconnect(self, shard_id):
|
||||||
self._diconnected_shard.add(shard_id)
|
self._disconnected_shard.add(shard_id)
|
||||||
|
|
||||||
@commands.Cog.listener()
|
@commands.Cog.listener()
|
||||||
async def on_shard_ready(self, shard_id):
|
async def on_shard_ready(self, shard_id):
|
||||||
self._diconnected_shard.discard(shard_id)
|
self._disconnected_shard.discard(shard_id)
|
||||||
|
|
||||||
@commands.Cog.listener()
|
@commands.Cog.listener()
|
||||||
async def on_shard_resumed(self, shard_id):
|
async def on_shard_resumed(self, shard_id):
|
||||||
self._diconnected_shard.discard(shard_id)
|
self._disconnected_shard.discard(shard_id)
|
||||||
|
|||||||
@ -14,7 +14,6 @@ from redbot.core.i18n import Translator, set_contextual_locales_from_guild
|
|||||||
from ...errors import DatabaseError, TrackEnqueueError
|
from ...errors import DatabaseError, TrackEnqueueError
|
||||||
from ..abc import MixinMeta
|
from ..abc import MixinMeta
|
||||||
from ..cog_utils import CompositeMetaClass
|
from ..cog_utils import CompositeMetaClass
|
||||||
from ...utils import task_callback_trace
|
|
||||||
|
|
||||||
log = getLogger("red.cogs.Audio.cog.Events.lavalink")
|
log = getLogger("red.cogs.Audio.cog.Events.lavalink")
|
||||||
ws_audio_log = getLogger("red.Audio.WS.Audio")
|
ws_audio_log = getLogger("red.Audio.WS.Audio")
|
||||||
@ -51,7 +50,8 @@ class LavalinkEvents(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
guild_id = self.rgetattr(guild, "id", None)
|
guild_id = self.rgetattr(guild, "id", None)
|
||||||
if not guild:
|
if not guild:
|
||||||
return
|
return
|
||||||
log.debug("Received a new lavalink event for %d: %s: %r", guild_id, event_type, extra)
|
# This event is rather spammy during playback - specially if there's multiple player
|
||||||
|
# Lets move it to Verbose that way it still there if needed alongside the other more verbose content.
|
||||||
guild_data = await self.config.guild(guild).all()
|
guild_data = await self.config.guild(guild).all()
|
||||||
disconnect = guild_data["disconnect"]
|
disconnect = guild_data["disconnect"]
|
||||||
if event_type == lavalink.LavalinkEvents.FORCED_DISCONNECT:
|
if event_type == lavalink.LavalinkEvents.FORCED_DISCONNECT:
|
||||||
@ -70,7 +70,7 @@ class LavalinkEvents(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
ws_audio_log.info(
|
ws_audio_log.info(
|
||||||
"WS EVENT - SIMPLE RESUME (Healthy Socket) | "
|
"WS EVENT - SIMPLE RESUME (Healthy Socket) | "
|
||||||
"Voice websocket closed event "
|
"Voice websocket closed event "
|
||||||
"Code: %d -- Remote: %s -- %s",
|
"Code: %s -- Remote: %s -- %s",
|
||||||
extra.get("code"),
|
extra.get("code"),
|
||||||
by_remote,
|
by_remote,
|
||||||
reason,
|
reason,
|
||||||
@ -78,7 +78,7 @@ class LavalinkEvents(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
ws_audio_log.debug(
|
ws_audio_log.debug(
|
||||||
"WS EVENT - SIMPLE RESUME (Healthy Socket) | "
|
"WS EVENT - SIMPLE RESUME (Healthy Socket) | "
|
||||||
"Voice websocket closed event "
|
"Voice websocket closed event "
|
||||||
"Code: %d -- Remote: %s -- %s, %r",
|
"Code: %s -- Remote: %s -- %s, %r",
|
||||||
extra.get("code"),
|
extra.get("code"),
|
||||||
by_remote,
|
by_remote,
|
||||||
reason,
|
reason,
|
||||||
@ -100,7 +100,13 @@ class LavalinkEvents(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
exc_info=exc,
|
exc_info=exc,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
if not player.manager.node.ready:
|
||||||
|
log.debug("Player node is not ready discarding event")
|
||||||
|
log.verbose(
|
||||||
|
"Received a new discard lavalink event for %s: %s: %r", guild_id, event_type, extra
|
||||||
|
)
|
||||||
|
return
|
||||||
|
log.verbose("Received a new lavalink event for %s: %s: %r", guild_id, event_type, extra)
|
||||||
await set_contextual_locales_from_guild(self.bot, guild)
|
await set_contextual_locales_from_guild(self.bot, guild)
|
||||||
current_requester = self.rgetattr(current_track, "requester", None)
|
current_requester = self.rgetattr(current_track, "requester", None)
|
||||||
current_stream = self.rgetattr(current_track, "is_stream", None)
|
current_stream = self.rgetattr(current_track, "is_stream", None)
|
||||||
@ -160,27 +166,27 @@ class LavalinkEvents(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
try:
|
try:
|
||||||
await self.api_interface.autoplay(player, self.playlist_api)
|
await self.api_interface.autoplay(player, self.playlist_api)
|
||||||
except DatabaseError:
|
except DatabaseError:
|
||||||
notify_channel = self.bot.get_channel(notify_channel_id)
|
notify_channel = guild.get_channel(notify_channel_id)
|
||||||
if notify_channel and self._has_notify_perms(notify_channel):
|
if notify_channel and self._has_notify_perms(notify_channel):
|
||||||
await self.send_embed_msg(
|
await self.send_embed_msg(
|
||||||
notify_channel, title=_("Couldn't get a valid track.")
|
notify_channel, title=_("Couldn't get a valid track.")
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
except TrackEnqueueError:
|
except TrackEnqueueError:
|
||||||
notify_channel = self.bot.get_channel(notify_channel_id)
|
notify_channel = guild.get_channel(notify_channel_id)
|
||||||
if notify_channel and self._has_notify_perms(notify_channel):
|
if notify_channel and self._has_notify_perms(notify_channel):
|
||||||
await self.send_embed_msg(
|
await self.send_embed_msg(
|
||||||
notify_channel,
|
notify_channel,
|
||||||
title=_("Unable to Get Track"),
|
title=_("Unable to Get Track"),
|
||||||
description=_(
|
description=_(
|
||||||
"I'm unable to get a track from Lavalink at the moment, try again in a few "
|
"I'm unable to get a track from the Lavalink node at the moment, try again in a few "
|
||||||
"minutes."
|
"minutes."
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
if event_type == lavalink.LavalinkEvents.TRACK_START and notify:
|
if event_type == lavalink.LavalinkEvents.TRACK_START and notify:
|
||||||
notify_channel_id = player.fetch("notify_channel")
|
notify_channel_id = player.fetch("notify_channel")
|
||||||
notify_channel = self.bot.get_channel(notify_channel_id)
|
notify_channel = guild.get_channel(notify_channel_id)
|
||||||
if notify_channel and self._has_notify_perms(notify_channel):
|
if notify_channel and self._has_notify_perms(notify_channel):
|
||||||
if player.fetch("notify_message") is not None:
|
if player.fetch("notify_message") is not None:
|
||||||
with contextlib.suppress(discord.HTTPException):
|
with contextlib.suppress(discord.HTTPException):
|
||||||
@ -221,7 +227,7 @@ class LavalinkEvents(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
if event_type == lavalink.LavalinkEvents.QUEUE_END:
|
if event_type == lavalink.LavalinkEvents.QUEUE_END:
|
||||||
if not autoplay:
|
if not autoplay:
|
||||||
notify_channel_id = player.fetch("notify_channel")
|
notify_channel_id = player.fetch("notify_channel")
|
||||||
notify_channel = self.bot.get_channel(notify_channel_id)
|
notify_channel = guild.get_channel(notify_channel_id)
|
||||||
if notify_channel and notify and self._has_notify_perms(notify_channel):
|
if notify_channel and notify and self._has_notify_perms(notify_channel):
|
||||||
await self.send_embed_msg(notify_channel, title=_("Queue ended."))
|
await self.send_embed_msg(notify_channel, title=_("Queue ended."))
|
||||||
if disconnect:
|
if disconnect:
|
||||||
@ -277,7 +283,7 @@ class LavalinkEvents(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
self._ll_guild_updates.discard(guild_id)
|
self._ll_guild_updates.discard(guild_id)
|
||||||
self.bot.dispatch("red_audio_audio_disconnect", guild)
|
self.bot.dispatch("red_audio_audio_disconnect", guild)
|
||||||
if message_channel:
|
if message_channel:
|
||||||
message_channel = self.bot.get_channel(message_channel)
|
message_channel = guild.get_channel(message_channel)
|
||||||
if early_exit:
|
if early_exit:
|
||||||
log.warning(
|
log.warning(
|
||||||
"Audio detected multiple continuous errors during playback "
|
"Audio detected multiple continuous errors during playback "
|
||||||
@ -320,8 +326,9 @@ class LavalinkEvents(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
if current_id:
|
if current_id:
|
||||||
asyncio.create_task(
|
asyncio.create_task(
|
||||||
self.api_interface.global_cache_api.report_invalid(current_id)
|
self.api_interface.global_cache_api.report_invalid(current_id)
|
||||||
).add_done_callback(task_callback_trace)
|
)
|
||||||
await message_channel.send(embed=embed)
|
await message_channel.send(embed=embed)
|
||||||
|
if player.node.ready:
|
||||||
await player.skip()
|
await player.skip()
|
||||||
|
|
||||||
async def _websocket_closed_handler(
|
async def _websocket_closed_handler(
|
||||||
@ -357,8 +364,8 @@ class LavalinkEvents(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
code = 4014
|
code = 4014
|
||||||
if event_channel_id != channel_id:
|
if event_channel_id != channel_id:
|
||||||
ws_audio_log.debug(
|
ws_audio_log.debug(
|
||||||
"Received an op code for a channel that is no longer valid; %d "
|
"Received an op code for a channel that is no longer valid; %s "
|
||||||
"Reason: Error code %d & %s, %r",
|
"Reason: Error code %s & %s, %r",
|
||||||
event_channel_id,
|
event_channel_id,
|
||||||
code,
|
code,
|
||||||
reason,
|
reason,
|
||||||
@ -373,9 +380,9 @@ class LavalinkEvents(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
if code in (1000,) and has_perm and player.current and player.is_playing:
|
if code in (1000,) and has_perm and player.current and player.is_playing:
|
||||||
player.store("resumes", player.fetch("resumes", 0) + 1)
|
player.store("resumes", player.fetch("resumes", 0) + 1)
|
||||||
await player.resume(player.current, start=player.position, replace=True)
|
await player.resume(player.current, start=player.position, replace=True)
|
||||||
ws_audio_log.info("Player resumed | Reason: Error code %d & %s", code, reason)
|
ws_audio_log.info("Player resumed | Reason: Error code %s & %s", code, reason)
|
||||||
ws_audio_log.debug(
|
ws_audio_log.debug(
|
||||||
"Player resumed | Reason: Error code %d & %s, %r", code, reason, player
|
"Player resumed | Reason: Error code %s & %s, %r", code, reason, player
|
||||||
)
|
)
|
||||||
self._ws_op_codes[guild_id]._init(self._ws_op_codes[guild_id]._maxsize)
|
self._ws_op_codes[guild_id]._init(self._ws_op_codes[guild_id]._maxsize)
|
||||||
return
|
return
|
||||||
@ -388,9 +395,9 @@ class LavalinkEvents(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
delay = player._con_delay.delay()
|
delay = player._con_delay.delay()
|
||||||
ws_audio_log.debug(
|
ws_audio_log.debug(
|
||||||
"YOU CAN IGNORE THIS UNLESS IT'S CONSISTENTLY REPEATING FOR THE SAME GUILD - "
|
"YOU CAN IGNORE THIS UNLESS IT'S CONSISTENTLY REPEATING FOR THE SAME GUILD - "
|
||||||
"Voice websocket closed for guild %d -> "
|
"Voice websocket closed for guild %s -> "
|
||||||
"Socket Closed %s. "
|
"Socket Closed %s. "
|
||||||
"Code: %d -- Remote: %s -- %s, %r",
|
"Code: %s -- Remote: %s -- %s, %r",
|
||||||
guild_id,
|
guild_id,
|
||||||
voice_ws.socket._closing or voice_ws.socket.closed,
|
voice_ws.socket._closing or voice_ws.socket.closed,
|
||||||
code,
|
code,
|
||||||
@ -399,7 +406,7 @@ class LavalinkEvents(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
player,
|
player,
|
||||||
)
|
)
|
||||||
ws_audio_log.info(
|
ws_audio_log.info(
|
||||||
"Reconnecting to channel %d in guild: %d | %.2fs",
|
"Reconnecting to channel %s in guild: %s | %.2fs",
|
||||||
channel_id,
|
channel_id,
|
||||||
guild_id,
|
guild_id,
|
||||||
delay,
|
delay,
|
||||||
@ -414,12 +421,12 @@ class LavalinkEvents(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
await player.connect(deafen=deafen)
|
await player.connect(deafen=deafen)
|
||||||
await player.resume(player.current, start=player.position, replace=True)
|
await player.resume(player.current, start=player.position, replace=True)
|
||||||
ws_audio_log.info(
|
ws_audio_log.info(
|
||||||
"Voice websocket reconnected Reason: Error code %d & Currently playing",
|
"Voice websocket reconnected Reason: Error code %s & Currently playing",
|
||||||
code,
|
code,
|
||||||
)
|
)
|
||||||
ws_audio_log.debug(
|
ws_audio_log.debug(
|
||||||
"Voice websocket reconnected "
|
"Voice websocket reconnected "
|
||||||
"Reason: Error code %d & Currently playing, %r",
|
"Reason: Error code %s & Currently playing, %r",
|
||||||
code,
|
code,
|
||||||
player,
|
player,
|
||||||
)
|
)
|
||||||
@ -430,12 +437,12 @@ class LavalinkEvents(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
player.current, start=player.position, replace=True, pause=True
|
player.current, start=player.position, replace=True, pause=True
|
||||||
)
|
)
|
||||||
ws_audio_log.info(
|
ws_audio_log.info(
|
||||||
"Voice websocket reconnected Reason: Error code %d & Currently Paused",
|
"Voice websocket reconnected Reason: Error code %s & Currently Paused",
|
||||||
code,
|
code,
|
||||||
)
|
)
|
||||||
ws_audio_log.debug(
|
ws_audio_log.debug(
|
||||||
"Voice websocket reconnected "
|
"Voice websocket reconnected "
|
||||||
"Reason: Error code %d & Currently Paused, %r",
|
"Reason: Error code %s & Currently Paused, %r",
|
||||||
code,
|
code,
|
||||||
player,
|
player,
|
||||||
)
|
)
|
||||||
@ -444,12 +451,12 @@ class LavalinkEvents(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
await player.connect(deafen=deafen)
|
await player.connect(deafen=deafen)
|
||||||
ws_audio_log.info(
|
ws_audio_log.info(
|
||||||
"Voice websocket reconnected "
|
"Voice websocket reconnected "
|
||||||
"Reason: Error code %d & Not playing, but auto disconnect disabled",
|
"Reason: Error code %s & Not playing, but auto disconnect disabled",
|
||||||
code,
|
code,
|
||||||
)
|
)
|
||||||
ws_audio_log.debug(
|
ws_audio_log.debug(
|
||||||
"Voice websocket reconnected "
|
"Voice websocket reconnected "
|
||||||
"Reason: Error code %d & Not playing, but auto disconnect disabled, %r",
|
"Reason: Error code %s & Not playing, but auto disconnect disabled, %r",
|
||||||
code,
|
code,
|
||||||
player,
|
player,
|
||||||
)
|
)
|
||||||
@ -458,12 +465,12 @@ class LavalinkEvents(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
self.bot.dispatch("red_audio_audio_disconnect", guild)
|
self.bot.dispatch("red_audio_audio_disconnect", guild)
|
||||||
ws_audio_log.info(
|
ws_audio_log.info(
|
||||||
"Voice websocket disconnected "
|
"Voice websocket disconnected "
|
||||||
"Reason: Error code %d & Missing permissions",
|
"Reason: Error code %s & Missing permissions",
|
||||||
code,
|
code,
|
||||||
)
|
)
|
||||||
ws_audio_log.debug(
|
ws_audio_log.debug(
|
||||||
"Voice websocket disconnected "
|
"Voice websocket disconnected "
|
||||||
"Reason: Error code %d & Missing permissions, %r",
|
"Reason: Error code %s & Missing permissions, %r",
|
||||||
code,
|
code,
|
||||||
player,
|
player,
|
||||||
)
|
)
|
||||||
@ -477,10 +484,10 @@ class LavalinkEvents(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
else:
|
else:
|
||||||
self.bot.dispatch("red_audio_audio_disconnect", guild)
|
self.bot.dispatch("red_audio_audio_disconnect", guild)
|
||||||
ws_audio_log.info(
|
ws_audio_log.info(
|
||||||
"Voice websocket disconnected Reason: Error code %d & Unknown", code
|
"Voice websocket disconnected Reason: Error code %s & Unknown", code
|
||||||
)
|
)
|
||||||
ws_audio_log.debug(
|
ws_audio_log.debug(
|
||||||
"Voice websocket disconnected Reason: Error code %d & Unknown, %r",
|
"Voice websocket disconnected Reason: Error code %s & Unknown, %r",
|
||||||
code,
|
code,
|
||||||
player,
|
player,
|
||||||
)
|
)
|
||||||
@ -495,9 +502,9 @@ class LavalinkEvents(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
player.store("resumes", player.fetch("resumes", 0) + 1)
|
player.store("resumes", player.fetch("resumes", 0) + 1)
|
||||||
await player.connect(deafen=deafen)
|
await player.connect(deafen=deafen)
|
||||||
await player.resume(player.current, start=player.position, replace=True)
|
await player.resume(player.current, start=player.position, replace=True)
|
||||||
ws_audio_log.info("Player resumed - Reason: Error code %d & %s", code, reason)
|
ws_audio_log.info("Player resumed - Reason: Error code %s & %s", code, reason)
|
||||||
ws_audio_log.debug(
|
ws_audio_log.debug(
|
||||||
"Player resumed - Reason: Error code %d & %s, %r", code, reason, player
|
"Player resumed - Reason: Error code %s & %s, %r", code, reason, player
|
||||||
)
|
)
|
||||||
elif code in (4015, 4009, 4006, 4000, 1006):
|
elif code in (4015, 4009, 4006, 4000, 1006):
|
||||||
if player._con_delay:
|
if player._con_delay:
|
||||||
@ -506,19 +513,19 @@ class LavalinkEvents(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
player._con_delay = ExponentialBackoff(base=1)
|
player._con_delay = ExponentialBackoff(base=1)
|
||||||
delay = player._con_delay.delay()
|
delay = player._con_delay.delay()
|
||||||
ws_audio_log.debug(
|
ws_audio_log.debug(
|
||||||
"Reconnecting to channel %d in guild: %d | %.2fs", channel_id, guild_id, delay
|
"Reconnecting to channel %s in guild: %s | %.2fs", channel_id, guild_id, delay
|
||||||
)
|
)
|
||||||
await asyncio.sleep(delay)
|
await asyncio.sleep(delay)
|
||||||
if has_perm and player.current and player.is_playing:
|
if has_perm and player.current and player.is_playing:
|
||||||
await player.connect(deafen=deafen)
|
await player.connect(deafen=deafen)
|
||||||
await player.resume(player.current, start=player.position, replace=True)
|
await player.resume(player.current, start=player.position, replace=True)
|
||||||
ws_audio_log.info(
|
ws_audio_log.info(
|
||||||
"Voice websocket reconnected Reason: Error code %d & Player is active",
|
"Voice websocket reconnected Reason: Error code %s & Player is active",
|
||||||
code,
|
code,
|
||||||
)
|
)
|
||||||
ws_audio_log.debug(
|
ws_audio_log.debug(
|
||||||
"Voice websocket reconnected "
|
"Voice websocket reconnected "
|
||||||
"Reason: Error code %d & Player is active, %r",
|
"Reason: Error code %s & Player is active, %r",
|
||||||
code,
|
code,
|
||||||
player,
|
player,
|
||||||
)
|
)
|
||||||
@ -529,12 +536,12 @@ class LavalinkEvents(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
player.current, start=player.position, replace=True, pause=True
|
player.current, start=player.position, replace=True, pause=True
|
||||||
)
|
)
|
||||||
ws_audio_log.info(
|
ws_audio_log.info(
|
||||||
"Voice websocket reconnected Reason: Error code %d & Player is paused",
|
"Voice websocket reconnected Reason: Error code %s & Player is paused",
|
||||||
code,
|
code,
|
||||||
)
|
)
|
||||||
ws_audio_log.debug(
|
ws_audio_log.debug(
|
||||||
"Voice websocket reconnected "
|
"Voice websocket reconnected "
|
||||||
"Reason: Error code %d & Player is paused, %r",
|
"Reason: Error code %s & Player is paused, %r",
|
||||||
code,
|
code,
|
||||||
player,
|
player,
|
||||||
)
|
)
|
||||||
@ -543,16 +550,16 @@ class LavalinkEvents(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
await player.connect(deafen=deafen)
|
await player.connect(deafen=deafen)
|
||||||
ws_audio_log.info(
|
ws_audio_log.info(
|
||||||
"Voice websocket reconnected "
|
"Voice websocket reconnected "
|
||||||
"to channel %d in guild: %d | "
|
"to channel %s in guild: %s | "
|
||||||
"Reason: Error code %d & Not playing",
|
"Reason: Error code %s & Not playing",
|
||||||
channel_id,
|
channel_id,
|
||||||
guild_id,
|
guild_id,
|
||||||
code,
|
code,
|
||||||
)
|
)
|
||||||
ws_audio_log.debug(
|
ws_audio_log.debug(
|
||||||
"Voice websocket reconnected "
|
"Voice websocket reconnected "
|
||||||
"to channel %d in guild: %d | "
|
"to channel %s in guild: %s | "
|
||||||
"Reason: Error code %d & Not playing, %r",
|
"Reason: Error code %s & Not playing, %r",
|
||||||
channel_id,
|
channel_id,
|
||||||
guild_id,
|
guild_id,
|
||||||
code,
|
code,
|
||||||
@ -563,12 +570,12 @@ class LavalinkEvents(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
self.bot.dispatch("red_audio_audio_disconnect", guild)
|
self.bot.dispatch("red_audio_audio_disconnect", guild)
|
||||||
ws_audio_log.info(
|
ws_audio_log.info(
|
||||||
"Voice websocket disconnected "
|
"Voice websocket disconnected "
|
||||||
"Reason: Error code %d & Missing permissions",
|
"Reason: Error code %s & Missing permissions",
|
||||||
code,
|
code,
|
||||||
)
|
)
|
||||||
ws_audio_log.debug(
|
ws_audio_log.debug(
|
||||||
"Voice websocket disconnected "
|
"Voice websocket disconnected "
|
||||||
"Reason: Error code %d & Missing permissions, %r",
|
"Reason: Error code %s & Missing permissions, %r",
|
||||||
code,
|
code,
|
||||||
player,
|
player,
|
||||||
)
|
)
|
||||||
@ -586,7 +593,7 @@ class LavalinkEvents(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
ws_audio_log.info(
|
ws_audio_log.info(
|
||||||
"WS EVENT - SIMPLE RESUME (Healthy Socket) | "
|
"WS EVENT - SIMPLE RESUME (Healthy Socket) | "
|
||||||
"Voice websocket closed event "
|
"Voice websocket closed event "
|
||||||
"Code: %d -- Remote: %s -- %s",
|
"Code: %s -- Remote: %s -- %s",
|
||||||
code,
|
code,
|
||||||
by_remote,
|
by_remote,
|
||||||
reason,
|
reason,
|
||||||
@ -594,7 +601,7 @@ class LavalinkEvents(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
ws_audio_log.debug(
|
ws_audio_log.debug(
|
||||||
"WS EVENT - SIMPLE RESUME (Healthy Socket) | "
|
"WS EVENT - SIMPLE RESUME (Healthy Socket) | "
|
||||||
"Voice websocket closed event "
|
"Voice websocket closed event "
|
||||||
"Code: %d -- Remote: %s -- %s, %r",
|
"Code: %s -- Remote: %s -- %s, %r",
|
||||||
code,
|
code,
|
||||||
by_remote,
|
by_remote,
|
||||||
reason,
|
reason,
|
||||||
@ -604,7 +611,7 @@ class LavalinkEvents(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
ws_audio_log.info(
|
ws_audio_log.info(
|
||||||
"WS EVENT - IGNORED (Healthy Socket) | "
|
"WS EVENT - IGNORED (Healthy Socket) | "
|
||||||
"Voice websocket closed event "
|
"Voice websocket closed event "
|
||||||
"Code: %d -- Remote: %s -- %s",
|
"Code: %s -- Remote: %s -- %s",
|
||||||
code,
|
code,
|
||||||
by_remote,
|
by_remote,
|
||||||
reason,
|
reason,
|
||||||
@ -612,7 +619,7 @@ class LavalinkEvents(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
ws_audio_log.debug(
|
ws_audio_log.debug(
|
||||||
"WS EVENT - IGNORED (Healthy Socket) | "
|
"WS EVENT - IGNORED (Healthy Socket) | "
|
||||||
"Voice websocket closed event "
|
"Voice websocket closed event "
|
||||||
"Code: %d -- Remote: %s -- %s, %r",
|
"Code: %s -- Remote: %s -- %s, %r",
|
||||||
code,
|
code,
|
||||||
by_remote,
|
by_remote,
|
||||||
reason,
|
reason,
|
||||||
|
|||||||
@ -6,18 +6,16 @@ from red_commons.logging import getLogger
|
|||||||
|
|
||||||
from redbot.core import data_manager
|
from redbot.core import data_manager
|
||||||
from redbot.core.i18n import Translator
|
from redbot.core.i18n import Translator
|
||||||
from ...errors import LavalinkDownloadFailed
|
|
||||||
from ...manager import ServerManager
|
from ...manager import ServerManager
|
||||||
from ..abc import MixinMeta
|
from ..abc import MixinMeta
|
||||||
from ..cog_utils import CompositeMetaClass
|
from ..cog_utils import CompositeMetaClass
|
||||||
from ...utils import task_callback_debug
|
|
||||||
|
|
||||||
log = getLogger("red.cogs.Audio.cog.Tasks.lavalink")
|
log = getLogger("red.cogs.Audio.cog.Tasks.lavalink")
|
||||||
_ = Translator("Audio", Path(__file__))
|
_ = Translator("Audio", Path(__file__))
|
||||||
|
|
||||||
|
|
||||||
class LavalinkTasks(MixinMeta, metaclass=CompositeMetaClass):
|
class LavalinkTasks(MixinMeta, metaclass=CompositeMetaClass):
|
||||||
def lavalink_restart_connect(self) -> None:
|
def lavalink_restart_connect(self, manual: bool = False) -> None:
|
||||||
lavalink.unregister_event_listener(self.lavalink_event_handler)
|
lavalink.unregister_event_listener(self.lavalink_event_handler)
|
||||||
lavalink.unregister_update_listener(self.lavalink_update_handler)
|
lavalink.unregister_update_listener(self.lavalink_update_handler)
|
||||||
if self.lavalink_connect_task:
|
if self.lavalink_connect_task:
|
||||||
@ -28,93 +26,106 @@ class LavalinkTasks(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
self._restore_task = None
|
self._restore_task = None
|
||||||
lavalink.register_event_listener(self.lavalink_event_handler)
|
lavalink.register_event_listener(self.lavalink_event_handler)
|
||||||
lavalink.register_update_listener(self.lavalink_update_handler)
|
lavalink.register_update_listener(self.lavalink_update_handler)
|
||||||
self.lavalink_connect_task = asyncio.create_task(self.lavalink_attempt_connect())
|
self.lavalink_connect_task = asyncio.create_task(
|
||||||
self.lavalink_connect_task.add_done_callback(task_callback_debug)
|
self.lavalink_attempt_connect(manual=manual)
|
||||||
|
)
|
||||||
|
|
||||||
async def lavalink_attempt_connect(self, timeout: int = 50) -> None:
|
async def lavalink_attempt_connect(self, timeout: int = 50, manual: bool = False) -> None:
|
||||||
self.lavalink_connection_aborted = False
|
self.lavalink_connection_aborted = False
|
||||||
max_retries = 5
|
max_retries = 5
|
||||||
retry_count = 0
|
retry_count = 0
|
||||||
|
if nodes := lavalink.get_all_nodes():
|
||||||
|
for node in nodes:
|
||||||
|
await node.disconnect()
|
||||||
|
# This ensures that the restore task is ended before this connect attempt is started up.
|
||||||
|
if self._restore_task:
|
||||||
|
self._restore_task.cancel()
|
||||||
|
if self.managed_node_controller is not None:
|
||||||
|
if not self.managed_node_controller._shutdown:
|
||||||
|
await self.managed_node_controller.shutdown()
|
||||||
|
await asyncio.sleep(5)
|
||||||
|
await lavalink.close(self.bot)
|
||||||
while retry_count < max_retries:
|
while retry_count < max_retries:
|
||||||
configs = await self.config.all()
|
configs = await self.config.all()
|
||||||
external = configs["use_external_lavalink"]
|
external = configs["use_external_lavalink"]
|
||||||
java_exec = configs["java_exc_path"]
|
java_exec = configs["java_exc_path"]
|
||||||
if external is False:
|
if external is False:
|
||||||
settings = self._default_lavalink_settings
|
# Change these values to use whatever is set on the YAML
|
||||||
host = settings["host"]
|
host = configs["yaml"]["server"]["address"]
|
||||||
password = settings["password"]
|
port = configs["yaml"]["server"]["port"]
|
||||||
ws_port = settings["ws_port"]
|
password = configs["yaml"]["lavalink"]["server"]["password"]
|
||||||
if self.player_manager is not None:
|
secured = False
|
||||||
await self.player_manager.shutdown()
|
# Make this timeout customizable for lower powered machines?
|
||||||
self.player_manager = ServerManager()
|
self.managed_node_controller = ServerManager(self.config, timeout=60, cog=self)
|
||||||
try:
|
try:
|
||||||
await self.player_manager.start(java_exec)
|
await self.managed_node_controller.start(java_exec)
|
||||||
except LavalinkDownloadFailed as exc:
|
# timeout is the same as ServerManager.timeout -
|
||||||
await asyncio.sleep(1)
|
# 60s in case of ServerManager(self.config, timeout=60)
|
||||||
if exc.should_retry:
|
await self.managed_node_controller.wait_until_ready()
|
||||||
log.exception(
|
except asyncio.TimeoutError:
|
||||||
"Exception whilst starting internal Lavalink server, retrying...",
|
if self.managed_node_controller is not None:
|
||||||
exc_info=exc,
|
await self.managed_node_controller.shutdown()
|
||||||
)
|
if self.lavalink_connection_aborted is not True:
|
||||||
retry_count += 1
|
log.critical(
|
||||||
continue
|
"Managed node startup timeout, aborting managed node startup."
|
||||||
else:
|
|
||||||
log.exception(
|
|
||||||
"Fatal exception whilst starting internal Lavalink server, "
|
|
||||||
"aborting...",
|
|
||||||
exc_info=exc,
|
|
||||||
)
|
)
|
||||||
self.lavalink_connection_aborted = True
|
self.lavalink_connection_aborted = True
|
||||||
return
|
return
|
||||||
except asyncio.CancelledError:
|
|
||||||
log.critical("Invalid machine architecture, cannot run Lavalink.")
|
|
||||||
return
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
log.exception(
|
log.exception(
|
||||||
"Unhandled exception whilst starting internal Lavalink server, "
|
"Unhandled exception whilst starting managed Lavalink node, "
|
||||||
"aborting...",
|
"aborting...",
|
||||||
exc_info=exc,
|
exc_info=exc,
|
||||||
)
|
)
|
||||||
self.lavalink_connection_aborted = True
|
self.lavalink_connection_aborted = True
|
||||||
|
if self.managed_node_controller is not None:
|
||||||
|
await self.managed_node_controller.shutdown()
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
host = configs["host"]
|
host = configs["host"]
|
||||||
password = configs["password"]
|
password = configs["password"]
|
||||||
ws_port = configs["ws_port"]
|
port = configs["ws_port"]
|
||||||
|
secured = configs["secured_ws"]
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
log.critical(
|
log.critical(
|
||||||
"Setting up the Lavalink server failed after multiple attempts. "
|
"Setting up the managed Lavalink node failed after multiple attempts. "
|
||||||
"See above tracebacks for details."
|
"See above logs for details."
|
||||||
)
|
)
|
||||||
self.lavalink_connection_aborted = True
|
self.lavalink_connection_aborted = True
|
||||||
|
if self.managed_node_controller is not None:
|
||||||
|
await self.managed_node_controller.shutdown()
|
||||||
return
|
return
|
||||||
|
log.debug("Attempting to initialize Red-Lavalink")
|
||||||
retry_count = 0
|
retry_count = 0
|
||||||
while retry_count < max_retries:
|
while retry_count < max_retries:
|
||||||
if lavalink.node._nodes:
|
|
||||||
await lavalink.node.disconnect()
|
|
||||||
try:
|
try:
|
||||||
await lavalink.initialize(
|
await lavalink.initialize(
|
||||||
bot=self.bot,
|
bot=self.bot,
|
||||||
host=host,
|
host=host,
|
||||||
password=password,
|
password=password,
|
||||||
ws_port=ws_port,
|
ws_port=port,
|
||||||
timeout=timeout,
|
timeout=timeout,
|
||||||
resume_key=f"Red-Core-Audio-{self.bot.user.id}-{data_manager.instance_name}",
|
resume_key=f"Red-Core-Audio-{self.bot.user.id}-{data_manager.instance_name}",
|
||||||
|
secured=secured,
|
||||||
)
|
)
|
||||||
|
except lavalink.AbortingNodeConnection:
|
||||||
|
await lavalink.close(self.bot)
|
||||||
|
log.warning("Connection attempt to Lavalink node aborted")
|
||||||
|
return
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
log.warning("Connecting to Lavalink server timed out, retrying...")
|
await lavalink.close(self.bot)
|
||||||
if external is False and self.player_manager is not None:
|
log.warning("Connecting to Lavalink node timed out, retrying...")
|
||||||
await self.player_manager.shutdown()
|
|
||||||
retry_count += 1
|
retry_count += 1
|
||||||
await asyncio.sleep(1) # prevent busylooping
|
await asyncio.sleep(1) # prevent busylooping
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
log.exception(
|
log.exception(
|
||||||
"Unhandled exception whilst connecting to Lavalink, aborting...", exc_info=exc
|
"Unhandled exception whilst connecting to Lavalink node, aborting...",
|
||||||
|
exc_info=exc,
|
||||||
)
|
)
|
||||||
|
await lavalink.close(self.bot)
|
||||||
self.lavalink_connection_aborted = True
|
self.lavalink_connection_aborted = True
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
@ -122,11 +133,11 @@ class LavalinkTasks(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
else:
|
else:
|
||||||
self.lavalink_connection_aborted = True
|
self.lavalink_connection_aborted = True
|
||||||
log.critical(
|
log.critical(
|
||||||
"Connecting to the Lavalink server failed after multiple attempts. "
|
"Connecting to the Lavalink node failed after multiple attempts. "
|
||||||
"See above tracebacks for details."
|
"See above tracebacks for details."
|
||||||
)
|
)
|
||||||
|
await lavalink.close(self.bot)
|
||||||
return
|
return
|
||||||
if external:
|
if external:
|
||||||
await asyncio.sleep(5)
|
await asyncio.sleep(5)
|
||||||
self._restore_task = asyncio.create_task(self.restore_players())
|
self._restore_task = asyncio.create_task(self.restore_players())
|
||||||
self._restore_task.add_done_callback(task_callback_debug)
|
|
||||||
|
|||||||
@ -5,6 +5,7 @@ from pathlib import Path
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
import lavalink
|
import lavalink
|
||||||
|
from lavalink import NodeNotFound, PlayerNotFound
|
||||||
from red_commons.logging import getLogger
|
from red_commons.logging import getLogger
|
||||||
|
|
||||||
from redbot.core.data_manager import cog_data_path
|
from redbot.core.data_manager import cog_data_path
|
||||||
@ -15,7 +16,6 @@ from redbot.core.utils.dbtools import APSWConnectionWrapper
|
|||||||
from ...apis.interface import AudioAPIInterface
|
from ...apis.interface import AudioAPIInterface
|
||||||
from ...apis.playlist_wrapper import PlaylistWrapper
|
from ...apis.playlist_wrapper import PlaylistWrapper
|
||||||
from ...errors import DatabaseError, TrackEnqueueError
|
from ...errors import DatabaseError, TrackEnqueueError
|
||||||
from ...utils import task_callback_debug
|
|
||||||
from ..abc import MixinMeta
|
from ..abc import MixinMeta
|
||||||
from ..cog_utils import _SCHEMA_VERSION, CompositeMetaClass
|
from ..cog_utils import _SCHEMA_VERSION, CompositeMetaClass
|
||||||
|
|
||||||
@ -30,7 +30,6 @@ class StartUpTasks(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
# as initial load happens before the bot can ever be ready.
|
# as initial load happens before the bot can ever be ready.
|
||||||
lavalink.set_logging_level(self.bot._cli_flags.logging_level)
|
lavalink.set_logging_level(self.bot._cli_flags.logging_level)
|
||||||
self.cog_init_task = asyncio.create_task(self.initialize())
|
self.cog_init_task = asyncio.create_task(self.initialize())
|
||||||
self.cog_init_task.add_done_callback(task_callback_debug)
|
|
||||||
|
|
||||||
async def initialize(self) -> None:
|
async def initialize(self) -> None:
|
||||||
await self.bot.wait_until_red_ready()
|
await self.bot.wait_until_red_ready()
|
||||||
@ -54,7 +53,6 @@ class StartUpTasks(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
await self._build_bundled_playlist()
|
await self._build_bundled_playlist()
|
||||||
self.lavalink_restart_connect()
|
self.lavalink_restart_connect()
|
||||||
self.player_automated_timer_task = asyncio.create_task(self.player_automated_timer())
|
self.player_automated_timer_task = asyncio.create_task(self.player_automated_timer())
|
||||||
self.player_automated_timer_task.add_done_callback(task_callback_debug)
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
log.critical("Audio failed to start up, please report this issue.", exc_info=exc)
|
log.critical("Audio failed to start up, please report this issue.", exc_info=exc)
|
||||||
return
|
return
|
||||||
@ -62,13 +60,26 @@ class StartUpTasks(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
self.cog_ready_event.set()
|
self.cog_ready_event.set()
|
||||||
|
|
||||||
async def restore_players(self):
|
async def restore_players(self):
|
||||||
|
log.debug("Starting new restore player task")
|
||||||
tries = 0
|
tries = 0
|
||||||
tracks_to_restore = await self.api_interface.persistent_queue_api.fetch_all()
|
tracks_to_restore = await self.api_interface.persistent_queue_api.fetch_all()
|
||||||
while not lavalink.get_all_nodes():
|
while not lavalink.get_all_nodes():
|
||||||
await asyncio.sleep(1)
|
await asyncio.sleep(1)
|
||||||
|
log.trace("Waiting for node to be available")
|
||||||
tries += 1
|
tries += 1
|
||||||
if tries > 60:
|
if tries > 600: # Give 10 minutes from node creation date.
|
||||||
log.warning("Unable to restore players, couldn't connect to Lavalink.")
|
log.warning("Unable to restore players, couldn't connect to Lavalink node.")
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
for node in lavalink.get_all_nodes():
|
||||||
|
if not node.ready:
|
||||||
|
log.trace("Waiting for node: %r", node)
|
||||||
|
await node.wait_until_ready(timeout=60) # In theory this should be instant.
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
log.error(
|
||||||
|
"Restoring player task aborted due to a timeout waiting for Lavalink node to be ready."
|
||||||
|
)
|
||||||
|
log.warning("Audio will attempt queue restore on next restart.")
|
||||||
return
|
return
|
||||||
metadata = {}
|
metadata = {}
|
||||||
all_guilds = await self.config.all_guilds()
|
all_guilds = await self.config.all_guilds()
|
||||||
@ -77,7 +88,9 @@ class StartUpTasks(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
if guild_data["currently_auto_playing_in"]:
|
if guild_data["currently_auto_playing_in"]:
|
||||||
notify_channel, vc_id = guild_data["currently_auto_playing_in"]
|
notify_channel, vc_id = guild_data["currently_auto_playing_in"]
|
||||||
metadata[guild_id] = (notify_channel, vc_id)
|
metadata[guild_id] = (notify_channel, vc_id)
|
||||||
|
if self.lavalink_connection_aborted:
|
||||||
|
log.warning("Aborting player restore due to Lavalink connection being aborted.")
|
||||||
|
return
|
||||||
for guild_id, track_data in itertools.groupby(tracks_to_restore, key=lambda x: x.guild_id):
|
for guild_id, track_data in itertools.groupby(tracks_to_restore, key=lambda x: x.guild_id):
|
||||||
await asyncio.sleep(0)
|
await asyncio.sleep(0)
|
||||||
tries = 0
|
tries = 0
|
||||||
@ -86,21 +99,23 @@ class StartUpTasks(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
track_data = list(track_data)
|
track_data = list(track_data)
|
||||||
guild = self.bot.get_guild(guild_id)
|
guild = self.bot.get_guild(guild_id)
|
||||||
if not guild:
|
if not guild:
|
||||||
|
log.verbose(
|
||||||
|
"Skipping player restore - Bot is no longer in Guild (%s)", guild_id
|
||||||
|
)
|
||||||
continue
|
continue
|
||||||
persist_cache = self._persist_queue_cache.setdefault(
|
persist_cache = self._persist_queue_cache.setdefault(
|
||||||
guild_id, await self.config.guild(guild).persist_queue()
|
guild_id, await self.config.guild(guild).persist_queue()
|
||||||
)
|
)
|
||||||
if not persist_cache:
|
if not persist_cache:
|
||||||
|
log.verbose(
|
||||||
|
"Skipping player restore - Guild (%s) does not have a persist cache",
|
||||||
|
guild_id,
|
||||||
|
)
|
||||||
await self.api_interface.persistent_queue_api.drop(guild_id)
|
await self.api_interface.persistent_queue_api.drop(guild_id)
|
||||||
continue
|
continue
|
||||||
if self.lavalink_connection_aborted:
|
|
||||||
player = None
|
|
||||||
else:
|
|
||||||
try:
|
try:
|
||||||
player = lavalink.get_player(guild_id)
|
player = lavalink.get_player(guild_id)
|
||||||
except IndexError:
|
except (NodeNotFound, PlayerNotFound):
|
||||||
player = None
|
|
||||||
except KeyError:
|
|
||||||
player = None
|
player = None
|
||||||
vc = 0
|
vc = 0
|
||||||
guild_data = await self.config.guild_from_id(guild.id).all()
|
guild_data = await self.config.guild_from_id(guild.id).all()
|
||||||
@ -126,7 +141,7 @@ class StartUpTasks(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
player = await lavalink.connect(vc, deafen=auto_deafen)
|
player = await lavalink.connect(vc, deafen=auto_deafen)
|
||||||
player.store("notify_channel", notify_channel_id)
|
player.store("notify_channel", notify_channel_id)
|
||||||
break
|
break
|
||||||
except IndexError:
|
except NodeNotFound:
|
||||||
await asyncio.sleep(5)
|
await asyncio.sleep(5)
|
||||||
tries += 1
|
tries += 1
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
@ -139,7 +154,24 @@ class StartUpTasks(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
else:
|
else:
|
||||||
await asyncio.sleep(1)
|
await asyncio.sleep(1)
|
||||||
|
|
||||||
if tries >= 5 or guild is None or vc is None or player is None:
|
if tries >= 5 or vc is None or player is None:
|
||||||
|
if tries >= 5:
|
||||||
|
log.verbose(
|
||||||
|
"Skipping player restore - Guild (%s), 5 attempts to restore player failed.",
|
||||||
|
guild_id,
|
||||||
|
)
|
||||||
|
elif vc is None:
|
||||||
|
log.verbose(
|
||||||
|
"Skipping player restore - Guild (%s), VC (%s) does not exist.",
|
||||||
|
guild_id,
|
||||||
|
vc_id,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
log.verbose(
|
||||||
|
"Skipping player restore - Guild (%s), Unable to create player for VC (%s).",
|
||||||
|
guild_id,
|
||||||
|
vc_id,
|
||||||
|
)
|
||||||
await self.api_interface.persistent_queue_api.drop(guild_id)
|
await self.api_interface.persistent_queue_api.drop(guild_id)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@ -156,7 +188,7 @@ class StartUpTasks(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
await player.play()
|
await player.play()
|
||||||
log.debug("Restored %r", player)
|
log.debug("Restored %r", player)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
log.debug("Error restoring player in %d", guild_id, exc_info=exc)
|
log.debug("Error restoring player in %s", guild_id, exc_info=exc)
|
||||||
await self.api_interface.persistent_queue_api.drop(guild_id)
|
await self.api_interface.persistent_queue_api.drop(guild_id)
|
||||||
|
|
||||||
for guild_id, (notify_channel_id, vc_id) in metadata.items():
|
for guild_id, (notify_channel_id, vc_id) in metadata.items():
|
||||||
@ -171,9 +203,7 @@ class StartUpTasks(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
player = lavalink.get_player(guild_id)
|
player = lavalink.get_player(guild_id)
|
||||||
except IndexError:
|
except (NodeNotFound, PlayerNotFound):
|
||||||
player = None
|
|
||||||
except KeyError:
|
|
||||||
player = None
|
player = None
|
||||||
if player is None:
|
if player is None:
|
||||||
guild_data = await self.config.guild_from_id(guild.id).all()
|
guild_data = await self.config.guild_from_id(guild.id).all()
|
||||||
@ -195,7 +225,7 @@ class StartUpTasks(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
player = await lavalink.connect(vc, deafen=auto_deafen)
|
player = await lavalink.connect(vc, deafen=auto_deafen)
|
||||||
player.store("notify_channel", notify_channel_id)
|
player.store("notify_channel", notify_channel_id)
|
||||||
break
|
break
|
||||||
except IndexError:
|
except NodeNotFound:
|
||||||
await asyncio.sleep(5)
|
await asyncio.sleep(5)
|
||||||
tries += 1
|
tries += 1
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
@ -205,7 +235,24 @@ class StartUpTasks(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
await asyncio.sleep(1)
|
await asyncio.sleep(1)
|
||||||
if tries >= 5 or guild is None or vc is None or player is None:
|
if tries >= 5 or vc is None or player is None:
|
||||||
|
if tries >= 5:
|
||||||
|
log.verbose(
|
||||||
|
"Skipping player restore - Guild (%s), 5 attempts to restore player failed.",
|
||||||
|
guild_id,
|
||||||
|
)
|
||||||
|
elif vc is None:
|
||||||
|
log.verbose(
|
||||||
|
"Skipping player restore - Guild (%s), VC (%s) does not exist.",
|
||||||
|
guild_id,
|
||||||
|
vc_id,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
log.verbose(
|
||||||
|
"Skipping player restore - Guild (%s), Unable to create player for VC (%s).",
|
||||||
|
guild_id,
|
||||||
|
vc_id,
|
||||||
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
player.repeat = repeat
|
player.repeat = repeat
|
||||||
@ -220,23 +267,24 @@ class StartUpTasks(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
try:
|
try:
|
||||||
await self.api_interface.autoplay(player, self.playlist_api)
|
await self.api_interface.autoplay(player, self.playlist_api)
|
||||||
except DatabaseError:
|
except DatabaseError:
|
||||||
notify_channel = self.bot.get_channel(notify_channel)
|
notify_channel = guild.get_channel(notify_channel)
|
||||||
if notify_channel:
|
if notify_channel:
|
||||||
await self.send_embed_msg(
|
await self.send_embed_msg(
|
||||||
notify_channel, title=_("Couldn't get a valid track.")
|
notify_channel, title=_("Couldn't get a valid track.")
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
except TrackEnqueueError:
|
except TrackEnqueueError:
|
||||||
notify_channel = self.bot.get_channel(notify_channel)
|
notify_channel = guild.get_channel(notify_channel)
|
||||||
if notify_channel:
|
if notify_channel:
|
||||||
await self.send_embed_msg(
|
await self.send_embed_msg(
|
||||||
notify_channel,
|
notify_channel,
|
||||||
title=_("Unable to Get Track"),
|
title=_("Unable to Get Track"),
|
||||||
description=_(
|
description=_(
|
||||||
"I'm unable to get a track from Lavalink at the moment, "
|
"I'm unable to get a track from the Lavalink node at the moment, "
|
||||||
"try again in a few minutes."
|
"try again in a few minutes."
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
del metadata
|
del metadata
|
||||||
del all_guilds
|
del all_guilds
|
||||||
|
log.debug("Player restore task completed successfully")
|
||||||
|
|||||||
@ -5,6 +5,7 @@ from typing import List
|
|||||||
|
|
||||||
import discord
|
import discord
|
||||||
import lavalink
|
import lavalink
|
||||||
|
from lavalink import NodeNotFound, PlayerNotFound
|
||||||
from red_commons.logging import getLogger
|
from red_commons.logging import getLogger
|
||||||
|
|
||||||
from redbot.core import commands
|
from redbot.core import commands
|
||||||
@ -27,7 +28,7 @@ class EqualizerUtilities(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
await lavalink.get_player(guild_id).node.send({**const})
|
await lavalink.get_player(guild_id).node.send({**const})
|
||||||
except (KeyError, IndexError):
|
except (NodeNotFound, PlayerNotFound):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def _apply_gains(self, guild_id: int, gains: List[float]) -> None:
|
async def _apply_gains(self, guild_id: int, gains: List[float]) -> None:
|
||||||
@ -39,7 +40,7 @@ class EqualizerUtilities(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
await lavalink.get_player(guild_id).node.send({**const})
|
await lavalink.get_player(guild_id).node.send({**const})
|
||||||
except (KeyError, IndexError):
|
except (NodeNotFound, PlayerNotFound):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def _eq_check(self, ctx: commands.Context, player: lavalink.Player) -> None:
|
async def _eq_check(self, ctx: commands.Context, player: lavalink.Player) -> None:
|
||||||
|
|||||||
@ -10,6 +10,8 @@ import lavalink
|
|||||||
from red_commons.logging import getLogger
|
from red_commons.logging import getLogger
|
||||||
|
|
||||||
from discord.embeds import EmptyEmbed
|
from discord.embeds import EmptyEmbed
|
||||||
|
from lavalink import NodeNotFound
|
||||||
|
|
||||||
from redbot.core import commands
|
from redbot.core import commands
|
||||||
from redbot.core.i18n import Translator
|
from redbot.core.i18n import Translator
|
||||||
from redbot.core.utils import AsyncIter
|
from redbot.core.utils import AsyncIter
|
||||||
@ -91,7 +93,7 @@ class FormattingUtilities(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
):
|
):
|
||||||
if not self._player_check(ctx):
|
if not self._player_check(ctx):
|
||||||
if self.lavalink_connection_aborted:
|
if self.lavalink_connection_aborted:
|
||||||
msg = _("Connection to Lavalink has failed")
|
msg = _("Connection to Lavalink node has failed")
|
||||||
description = EmptyEmbed
|
description = EmptyEmbed
|
||||||
if await self.bot.is_owner(ctx.author):
|
if await self.bot.is_owner(ctx.author):
|
||||||
description = _("Please check your console or logs for details.")
|
description = _("Please check your console or logs for details.")
|
||||||
@ -103,9 +105,9 @@ class FormattingUtilities(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
)
|
)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
return await self.send_embed_msg(ctx, title=_("Connect to a voice channel first."))
|
return await self.send_embed_msg(ctx, title=_("Connect to a voice channel first."))
|
||||||
except IndexError:
|
except NodeNotFound:
|
||||||
return await self.send_embed_msg(
|
return await self.send_embed_msg(
|
||||||
ctx, title=_("Connection to Lavalink has not yet been established.")
|
ctx, title=_("Connection to Lavalink node has not yet been established.")
|
||||||
)
|
)
|
||||||
player = lavalink.get_player(ctx.guild.id)
|
player = lavalink.get_player(ctx.guild.id)
|
||||||
player.store("notify_channel", ctx.channel.id)
|
player.store("notify_channel", ctx.channel.id)
|
||||||
@ -161,7 +163,7 @@ class FormattingUtilities(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
f"{search_choice.title} {search_choice.author} {search_choice.uri} {str(query)}",
|
f"{search_choice.title} {search_choice.author} {search_choice.uri} {str(query)}",
|
||||||
query_obj=query,
|
query_obj=query,
|
||||||
):
|
):
|
||||||
log.debug("Query is not allowed in %r (%d)", ctx.guild.name, ctx.guild.id)
|
log.debug("Query is not allowed in %r (%s)", ctx.guild.name, ctx.guild.id)
|
||||||
self.update_player_lock(ctx, False)
|
self.update_player_lock(ctx, False)
|
||||||
return await self.send_embed_msg(
|
return await self.send_embed_msg(
|
||||||
ctx, title=_("This track is not allowed in this server.")
|
ctx, title=_("This track is not allowed in this server.")
|
||||||
|
|||||||
@ -20,7 +20,7 @@ from redbot.core.utils import AsyncIter
|
|||||||
from redbot.core.utils.chat_formatting import humanize_number
|
from redbot.core.utils.chat_formatting import humanize_number
|
||||||
|
|
||||||
from ...apis.playlist_interface import get_all_playlist_for_migration23
|
from ...apis.playlist_interface import get_all_playlist_for_migration23
|
||||||
from ...utils import PlaylistScope, task_callback_trace
|
from ...utils import PlaylistScope
|
||||||
from ..abc import MixinMeta
|
from ..abc import MixinMeta
|
||||||
from ..cog_utils import CompositeMetaClass, DataReader
|
from ..cog_utils import CompositeMetaClass, DataReader
|
||||||
|
|
||||||
@ -35,9 +35,7 @@ class MiscellaneousUtilities(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
self, message: discord.Message, emoji: MutableMapping = None
|
self, message: discord.Message, emoji: MutableMapping = None
|
||||||
) -> asyncio.Task:
|
) -> asyncio.Task:
|
||||||
"""Non blocking version of clear_react."""
|
"""Non blocking version of clear_react."""
|
||||||
task = asyncio.create_task(self.clear_react(message, emoji))
|
return asyncio.create_task(self.clear_react(message, emoji))
|
||||||
task.add_done_callback(task_callback_trace)
|
|
||||||
return task
|
|
||||||
|
|
||||||
async def maybe_charge_requester(self, ctx: commands.Context, jukebox_price: int) -> bool:
|
async def maybe_charge_requester(self, ctx: commands.Context, jukebox_price: int) -> bool:
|
||||||
jukebox = await self.config.guild(ctx.guild).jukebox()
|
jukebox = await self.config.guild(ctx.guild).jukebox()
|
||||||
@ -125,8 +123,8 @@ class MiscellaneousUtilities(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
async def update_external_status(self) -> bool:
|
async def update_external_status(self) -> bool:
|
||||||
external = await self.config.use_external_lavalink()
|
external = await self.config.use_external_lavalink()
|
||||||
if not external:
|
if not external:
|
||||||
if self.player_manager is not None:
|
if self.managed_node_controller is not None:
|
||||||
await self.player_manager.shutdown()
|
await self.managed_node_controller.shutdown()
|
||||||
await self.config.use_external_lavalink.set(True)
|
await self.config.use_external_lavalink.set(True)
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
|
|||||||
@ -9,6 +9,8 @@ import lavalink
|
|||||||
from red_commons.logging import getLogger
|
from red_commons.logging import getLogger
|
||||||
|
|
||||||
from discord.embeds import EmptyEmbed
|
from discord.embeds import EmptyEmbed
|
||||||
|
from lavalink import NodeNotFound, PlayerNotFound
|
||||||
|
|
||||||
from redbot.core import commands
|
from redbot.core import commands
|
||||||
from redbot.core.i18n import Translator
|
from redbot.core.i18n import Translator
|
||||||
from redbot.core.utils import AsyncIter
|
from redbot.core.utils import AsyncIter
|
||||||
@ -59,7 +61,7 @@ class PlayerUtilities(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
current, self.local_folder_current_path
|
current, self.local_folder_current_path
|
||||||
)
|
)
|
||||||
playing_servers = len(lavalink.active_players())
|
playing_servers = len(lavalink.active_players())
|
||||||
except IndexError:
|
except (IndexError, NodeNotFound, PlayerNotFound):
|
||||||
get_single_title = None
|
get_single_title = None
|
||||||
playing_servers = 0
|
playing_servers = 0
|
||||||
return get_single_title, playing_servers
|
return get_single_title, playing_servers
|
||||||
@ -208,7 +210,7 @@ class PlayerUtilities(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
try:
|
try:
|
||||||
lavalink.get_player(ctx.guild.id)
|
lavalink.get_player(ctx.guild.id)
|
||||||
return True
|
return True
|
||||||
except (IndexError, KeyError):
|
except (NodeNotFound, PlayerNotFound):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def self_deafen(self, player: lavalink.Player) -> None:
|
async def self_deafen(self, player: lavalink.Player) -> None:
|
||||||
@ -299,7 +301,7 @@ class PlayerUtilities(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
ctx,
|
ctx,
|
||||||
title=_("Unable to Get Track"),
|
title=_("Unable to Get Track"),
|
||||||
description=_(
|
description=_(
|
||||||
"I'm unable to get a track from Lavalink at the moment, "
|
"I'm unable to get a track from the Lavalink node at the moment, "
|
||||||
"try again in a few minutes."
|
"try again in a few minutes."
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -390,7 +392,7 @@ class PlayerUtilities(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
ctx,
|
ctx,
|
||||||
title=_("Unable to Get Track"),
|
title=_("Unable to Get Track"),
|
||||||
description=_(
|
description=_(
|
||||||
"I'm unable to get a track from Lavalink at the moment, "
|
"I'm unable to get a track from Lavalink node at the moment, "
|
||||||
"try again in a few minutes."
|
"try again in a few minutes."
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -450,7 +452,7 @@ class PlayerUtilities(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
f"{track.title} {track.author} {track.uri} " f"{str(query)}",
|
f"{track.title} {track.author} {track.uri} " f"{str(query)}",
|
||||||
query_obj=query,
|
query_obj=query,
|
||||||
):
|
):
|
||||||
log.debug("Query is not allowed in %r (%d)", ctx.guild.name, ctx.guild.id)
|
log.debug("Query is not allowed in %r (%s)", ctx.guild.name, ctx.guild.id)
|
||||||
continue
|
continue
|
||||||
elif guild_data["maxlength"] > 0:
|
elif guild_data["maxlength"] > 0:
|
||||||
if self.is_track_length_allowed(track, guild_data["maxlength"]):
|
if self.is_track_length_allowed(track, guild_data["maxlength"]):
|
||||||
@ -539,7 +541,7 @@ class PlayerUtilities(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
),
|
),
|
||||||
query_obj=query,
|
query_obj=query,
|
||||||
):
|
):
|
||||||
log.debug("Query is not allowed in %r (%d)", ctx.guild.name, ctx.guild.id)
|
log.debug("Query is not allowed in %r (%s)", ctx.guild.name, ctx.guild.id)
|
||||||
self.update_player_lock(ctx, False)
|
self.update_player_lock(ctx, False)
|
||||||
return await self.send_embed_msg(
|
return await self.send_embed_msg(
|
||||||
ctx, title=_("This track is not allowed in this server.")
|
ctx, title=_("This track is not allowed in this server.")
|
||||||
@ -687,7 +689,7 @@ class PlayerUtilities(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
async def maybe_move_player(self, ctx: commands.Context) -> bool:
|
async def maybe_move_player(self, ctx: commands.Context) -> bool:
|
||||||
try:
|
try:
|
||||||
player = lavalink.get_player(ctx.guild.id)
|
player = lavalink.get_player(ctx.guild.id)
|
||||||
except KeyError:
|
except PlayerNotFound:
|
||||||
return False
|
return False
|
||||||
try:
|
try:
|
||||||
in_channel = sum(
|
in_channel = sum(
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import aiohttp
|
|||||||
import discord
|
import discord
|
||||||
import lavalink
|
import lavalink
|
||||||
from discord.embeds import EmptyEmbed
|
from discord.embeds import EmptyEmbed
|
||||||
|
from lavalink import NodeNotFound
|
||||||
from red_commons.logging import getLogger
|
from red_commons.logging import getLogger
|
||||||
|
|
||||||
from redbot.core import commands
|
from redbot.core import commands
|
||||||
@ -424,7 +425,7 @@ class PlaylistUtilities(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
ctx,
|
ctx,
|
||||||
title=_("Unable to Get Track"),
|
title=_("Unable to Get Track"),
|
||||||
description=_(
|
description=_(
|
||||||
"I'm unable to get a track from Lavalink at the moment, "
|
"I'm unable to get a track from the Lavalink node at the moment, "
|
||||||
"try again in a few minutes."
|
"try again in a few minutes."
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -523,7 +524,7 @@ class PlaylistUtilities(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
async def _playlist_check(self, ctx: commands.Context) -> bool:
|
async def _playlist_check(self, ctx: commands.Context) -> bool:
|
||||||
if not self._player_check(ctx):
|
if not self._player_check(ctx):
|
||||||
if self.lavalink_connection_aborted:
|
if self.lavalink_connection_aborted:
|
||||||
msg = _("Connection to Lavalink has failed")
|
msg = _("Connection to Lavalink node has failed")
|
||||||
desc = EmptyEmbed
|
desc = EmptyEmbed
|
||||||
if await self.bot.is_owner(ctx.author):
|
if await self.bot.is_owner(ctx.author):
|
||||||
desc = _("Please check your console or logs for details.")
|
desc = _("Please check your console or logs for details.")
|
||||||
@ -547,11 +548,11 @@ class PlaylistUtilities(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
ctx.author.voice.channel,
|
ctx.author.voice.channel,
|
||||||
deafen=await self.config.guild_from_id(ctx.guild.id).auto_deafen(),
|
deafen=await self.config.guild_from_id(ctx.guild.id).auto_deafen(),
|
||||||
)
|
)
|
||||||
except IndexError:
|
except NodeNotFound:
|
||||||
await self.send_embed_msg(
|
await self.send_embed_msg(
|
||||||
ctx,
|
ctx,
|
||||||
title=_("Unable To Get Playlists"),
|
title=_("Unable To Get Playlists"),
|
||||||
description=_("Connection to Lavalink has not yet been established."),
|
description=_("Connection to Lavalink node has not yet been established."),
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
@ -625,7 +626,7 @@ class PlaylistUtilities(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
ctx,
|
ctx,
|
||||||
title=_("Unable to Get Track"),
|
title=_("Unable to Get Track"),
|
||||||
description=_(
|
description=_(
|
||||||
"I'm unable to get a track from Lavalink at the moment, try again in a few "
|
"I'm unable to get a track from Lavalink node at the moment, try again in a few "
|
||||||
"minutes."
|
"minutes."
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -654,7 +655,7 @@ class PlaylistUtilities(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
ctx,
|
ctx,
|
||||||
title=_("Unable to Get Track"),
|
title=_("Unable to Get Track"),
|
||||||
description=_(
|
description=_(
|
||||||
"I'm unable to get a track from Lavalink at the moment, try again in a few "
|
"I'm unable to get a track from Lavalink node at the moment, try again in a few "
|
||||||
"minutes."
|
"minutes."
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,48 +0,0 @@
|
|||||||
server: # REST and WS server
|
|
||||||
port: 2333
|
|
||||||
address: "localhost"
|
|
||||||
lavalink:
|
|
||||||
server:
|
|
||||||
password: "youshallnotpass"
|
|
||||||
sources:
|
|
||||||
youtube: true
|
|
||||||
bandcamp: true
|
|
||||||
soundcloud: true
|
|
||||||
twitch: true
|
|
||||||
vimeo: true
|
|
||||||
http: true
|
|
||||||
local: true
|
|
||||||
bufferDurationMs: 1000 # Length of track to buffer into memory in miliseconds.
|
|
||||||
youtubePlaylistLoadLimit: 10000 # Number of pages at 100 each
|
|
||||||
playerUpdateInterval: 1 # How frequently to send player updates to clients, in seconds
|
|
||||||
youtubeSearchEnabled: true
|
|
||||||
soundcloudSearchEnabled: true
|
|
||||||
gc-warnings: true
|
|
||||||
#ratelimit:
|
|
||||||
#ipBlocks: ["1.0.0.0/8", "..."] # list of ip blocks
|
|
||||||
#excludedIps: ["...", "..."] # ips which should be explicit excluded from usage by lavalink
|
|
||||||
#strategy: "RotateOnBan" # RotateOnBan | LoadBalance | NanoSwitch | RotatingNanoSwitch
|
|
||||||
#searchTriggersFail: true # Whether a search 429 should trigger marking the ip as failing
|
|
||||||
#retryLimit: -1 # -1 = use default lavaplayer value | 0 = infinity | >0 = retry will happen this numbers times
|
|
||||||
|
|
||||||
metrics:
|
|
||||||
prometheus:
|
|
||||||
enabled: false
|
|
||||||
endpoint: /metrics
|
|
||||||
|
|
||||||
sentry:
|
|
||||||
dsn: ""
|
|
||||||
environment: ""
|
|
||||||
# tags:
|
|
||||||
# some_key: some_value
|
|
||||||
# another_key: another_value
|
|
||||||
|
|
||||||
logging:
|
|
||||||
file:
|
|
||||||
max-history: 7
|
|
||||||
max-size: 1GB
|
|
||||||
path: ./logs/
|
|
||||||
|
|
||||||
level:
|
|
||||||
root: INFO
|
|
||||||
lavalink: INFO
|
|
||||||
@ -11,7 +11,55 @@ class AudioError(Exception):
|
|||||||
"""Base exception for errors in the Audio cog."""
|
"""Base exception for errors in the Audio cog."""
|
||||||
|
|
||||||
|
|
||||||
class LavalinkDownloadFailed(AudioError, RuntimeError):
|
class ManagedLavalinkNodeException(AudioError):
|
||||||
|
"""Base Exception for Managed Lavalink Node Exceptions"""
|
||||||
|
|
||||||
|
|
||||||
|
class NodeUnhealthy(ManagedLavalinkNodeException):
|
||||||
|
"""Exception Raised when the node health checks fail"""
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidArchitectureException(ManagedLavalinkNodeException):
|
||||||
|
"""Error thrown when the Managed Lavalink node is started on an invalid arch."""
|
||||||
|
|
||||||
|
|
||||||
|
class ManagedLavalinkAlreadyRunningException(ManagedLavalinkNodeException):
|
||||||
|
"""Exception thrown when a managed Lavalink node is already running"""
|
||||||
|
|
||||||
|
|
||||||
|
class ManagedLavalinkStartFailure(ManagedLavalinkNodeException):
|
||||||
|
"""Exception thrown when a managed Lavalink node fails to start"""
|
||||||
|
|
||||||
|
|
||||||
|
class ManagedLavalinkPreviouslyShutdownException(ManagedLavalinkNodeException):
|
||||||
|
"""Exception thrown when a managed Lavalink node already has been shutdown"""
|
||||||
|
|
||||||
|
|
||||||
|
class EarlyExitException(ManagedLavalinkNodeException):
|
||||||
|
"""some placeholder text I cannot be bothered to add a meaning message atm"""
|
||||||
|
|
||||||
|
|
||||||
|
class UnsupportedJavaException(ManagedLavalinkNodeException):
|
||||||
|
"""Exception thrown when a managed Lavalink node doesn't have a supported Java"""
|
||||||
|
|
||||||
|
|
||||||
|
class UnexpectedJavaResponseException(ManagedLavalinkNodeException):
|
||||||
|
"""Exception thrown when Java returns an unexpected response"""
|
||||||
|
|
||||||
|
|
||||||
|
class NoProcessFound(ManagedLavalinkNodeException):
|
||||||
|
"""Exception thrown when the managed node process is not found"""
|
||||||
|
|
||||||
|
|
||||||
|
class IncorrectProcessFound(ManagedLavalinkNodeException):
|
||||||
|
"""Exception thrown when the managed node process is incorrect"""
|
||||||
|
|
||||||
|
|
||||||
|
class TooManyProcessFound(ManagedLavalinkNodeException):
|
||||||
|
"""Exception thrown when zombie processes are suspected"""
|
||||||
|
|
||||||
|
|
||||||
|
class LavalinkDownloadFailed(ManagedLavalinkNodeException, RuntimeError):
|
||||||
"""Downloading the Lavalink jar failed.
|
"""Downloading the Lavalink jar failed.
|
||||||
|
|
||||||
Attributes
|
Attributes
|
||||||
|
|||||||
@ -1,24 +1,52 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import asyncio.subprocess # disables for # https://github.com/PyCQA/pylint/issues/1469
|
import asyncio.subprocess # disables for # https://github.com/PyCQA/pylint/issues/1469
|
||||||
|
import contextlib
|
||||||
import itertools
|
import itertools
|
||||||
import json
|
import json
|
||||||
import pathlib
|
import pathlib
|
||||||
import platform
|
import platform
|
||||||
import re
|
import re
|
||||||
|
import shlex
|
||||||
import shutil
|
import shutil
|
||||||
import tempfile
|
import tempfile
|
||||||
import time
|
from typing import ClassVar, Final, List, Optional, Pattern, Tuple, TYPE_CHECKING
|
||||||
from typing import ClassVar, Final, List, Optional, Pattern, Tuple
|
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
|
import lavalink
|
||||||
|
import psutil
|
||||||
import rich.progress
|
import rich.progress
|
||||||
|
import yaml
|
||||||
|
from discord.backoff import ExponentialBackoff
|
||||||
from red_commons.logging import getLogger
|
from red_commons.logging import getLogger
|
||||||
|
|
||||||
from redbot.core import data_manager
|
from redbot.core import data_manager, Config
|
||||||
from redbot.core.i18n import Translator
|
from redbot.core.i18n import Translator
|
||||||
|
|
||||||
from .errors import LavalinkDownloadFailed
|
from .errors import (
|
||||||
from .utils import task_callback_exception
|
LavalinkDownloadFailed,
|
||||||
|
InvalidArchitectureException,
|
||||||
|
ManagedLavalinkAlreadyRunningException,
|
||||||
|
ManagedLavalinkPreviouslyShutdownException,
|
||||||
|
UnsupportedJavaException,
|
||||||
|
ManagedLavalinkStartFailure,
|
||||||
|
UnexpectedJavaResponseException,
|
||||||
|
EarlyExitException,
|
||||||
|
ManagedLavalinkNodeException,
|
||||||
|
TooManyProcessFound,
|
||||||
|
IncorrectProcessFound,
|
||||||
|
NoProcessFound,
|
||||||
|
NodeUnhealthy,
|
||||||
|
)
|
||||||
|
from .utils import (
|
||||||
|
change_dict_naming_convention,
|
||||||
|
get_max_allocation_size,
|
||||||
|
replace_p_with_prefix,
|
||||||
|
)
|
||||||
|
from ...core.utils import AsyncIter
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from . import Audio
|
||||||
|
|
||||||
|
|
||||||
_ = Translator("Audio", pathlib.Path(__file__))
|
_ = Translator("Audio", pathlib.Path(__file__))
|
||||||
log = getLogger("red.Audio.manager")
|
log = getLogger("red.Audio.manager")
|
||||||
@ -31,7 +59,6 @@ LAVALINK_DOWNLOAD_URL: Final[str] = (
|
|||||||
)
|
)
|
||||||
LAVALINK_DOWNLOAD_DIR: Final[pathlib.Path] = data_manager.cog_data_path(raw_name="Audio")
|
LAVALINK_DOWNLOAD_DIR: Final[pathlib.Path] = data_manager.cog_data_path(raw_name="Audio")
|
||||||
LAVALINK_JAR_FILE: Final[pathlib.Path] = LAVALINK_DOWNLOAD_DIR / "Lavalink.jar"
|
LAVALINK_JAR_FILE: Final[pathlib.Path] = LAVALINK_DOWNLOAD_DIR / "Lavalink.jar"
|
||||||
BUNDLED_APP_YML: Final[pathlib.Path] = pathlib.Path(__file__).parent / "data" / "application.yml"
|
|
||||||
LAVALINK_APP_YML: Final[pathlib.Path] = LAVALINK_DOWNLOAD_DIR / "application.yml"
|
LAVALINK_APP_YML: Final[pathlib.Path] = LAVALINK_DOWNLOAD_DIR / "application.yml"
|
||||||
|
|
||||||
_RE_READY_LINE: Final[Pattern] = re.compile(rb"Started Launcher in \S+ seconds")
|
_RE_READY_LINE: Final[Pattern] = re.compile(rb"Started Launcher in \S+ seconds")
|
||||||
@ -98,12 +125,16 @@ class ServerManager:
|
|||||||
_buildtime: ClassVar[Optional[str]] = None
|
_buildtime: ClassVar[Optional[str]] = None
|
||||||
_java_exc: ClassVar[str] = "java"
|
_java_exc: ClassVar[str] = "java"
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self, config: Config, cog: "Audio", timeout: Optional[int] = None) -> None:
|
||||||
self.ready: asyncio.Event = asyncio.Event()
|
self.ready: asyncio.Event = asyncio.Event()
|
||||||
|
self._config = config
|
||||||
self._proc: Optional[asyncio.subprocess.Process] = None # pylint:disable=no-member
|
self._proc: Optional[asyncio.subprocess.Process] = None # pylint:disable=no-member
|
||||||
self._monitor_task: Optional[asyncio.Task] = None
|
self._node_pid: Optional[int] = None
|
||||||
self._shutdown: bool = False
|
self._shutdown: bool = False
|
||||||
|
self.start_monitor_task = None
|
||||||
|
self.timeout = timeout
|
||||||
|
self.cog = cog
|
||||||
|
self._args = []
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def path(self) -> Optional[str]:
|
def path(self) -> Optional[str]:
|
||||||
@ -129,70 +160,122 @@ class ServerManager:
|
|||||||
def build_time(self) -> Optional[str]:
|
def build_time(self) -> Optional[str]:
|
||||||
return self._buildtime
|
return self._buildtime
|
||||||
|
|
||||||
async def start(self, java_path: str) -> None:
|
async def _start(self, java_path: str) -> None:
|
||||||
arch_name = platform.machine()
|
arch_name = platform.machine()
|
||||||
self._java_exc = java_path
|
self._java_exc = java_path
|
||||||
if arch_name in self._blacklisted_archs:
|
if arch_name in self._blacklisted_archs:
|
||||||
raise asyncio.CancelledError(
|
raise InvalidArchitectureException(
|
||||||
"You are attempting to run Lavalink audio on an unsupported machine architecture."
|
"You are attempting to run the managed Lavalink node on an unsupported machine architecture."
|
||||||
)
|
)
|
||||||
|
|
||||||
if self._proc is not None:
|
if self._proc is not None:
|
||||||
if self._proc.returncode is None:
|
if self._proc.returncode is None:
|
||||||
raise RuntimeError("Internal Lavalink server is already running")
|
raise ManagedLavalinkAlreadyRunningException(
|
||||||
|
"Managed Lavalink node is already running"
|
||||||
|
)
|
||||||
elif self._shutdown:
|
elif self._shutdown:
|
||||||
raise RuntimeError("Server manager has already been used - create another one")
|
raise ManagedLavalinkPreviouslyShutdownException(
|
||||||
|
"Server manager has already been used - create another one"
|
||||||
|
)
|
||||||
|
await self.process_settings()
|
||||||
await self.maybe_download_jar()
|
await self.maybe_download_jar()
|
||||||
|
args, msg = await self._get_jar_args()
|
||||||
# Copy the application.yml across.
|
if msg is not None:
|
||||||
# For people to customise their Lavalink server configuration they need to run it
|
log.warning(msg)
|
||||||
# externally
|
command_string = shlex.join(args)
|
||||||
shutil.copyfile(BUNDLED_APP_YML, LAVALINK_APP_YML)
|
log.info("Managed Lavalink node startup command: %s", command_string)
|
||||||
|
if "-Xmx" not in command_string and msg is None:
|
||||||
args = await self._get_jar_args()
|
log.warning(
|
||||||
self._proc = await asyncio.subprocess.create_subprocess_exec( # pylint:disable=no-member
|
await replace_p_with_prefix(
|
||||||
|
self.cog.bot,
|
||||||
|
"Managed Lavalink node maximum allowed RAM not set or higher than available RAM, "
|
||||||
|
"please use '[p]llset heapsize' to set a maximum value to avoid out of RAM crashes.",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
self._proc = (
|
||||||
|
await asyncio.subprocess.create_subprocess_exec( # pylint:disable=no-member
|
||||||
*args,
|
*args,
|
||||||
cwd=str(LAVALINK_DOWNLOAD_DIR),
|
cwd=str(LAVALINK_DOWNLOAD_DIR),
|
||||||
stdout=asyncio.subprocess.PIPE,
|
stdout=asyncio.subprocess.PIPE,
|
||||||
stderr=asyncio.subprocess.STDOUT,
|
stderr=asyncio.subprocess.STDOUT,
|
||||||
)
|
)
|
||||||
|
)
|
||||||
log.info("Internal Lavalink server started. PID: %s", self._proc.pid)
|
self._node_pid = self._proc.pid
|
||||||
|
log.info("Managed Lavalink node started. PID: %s", self._node_pid)
|
||||||
try:
|
try:
|
||||||
await asyncio.wait_for(self._wait_for_launcher(), timeout=120)
|
await asyncio.wait_for(self._wait_for_launcher(), timeout=self.timeout)
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
log.warning("Timeout occurred whilst waiting for internal Lavalink server to be ready")
|
log.warning(
|
||||||
|
"Timeout occurred whilst waiting for managed Lavalink node to be ready"
|
||||||
|
)
|
||||||
|
raise
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
await self._partial_shutdown()
|
||||||
|
except Exception:
|
||||||
|
await self._partial_shutdown()
|
||||||
|
raise
|
||||||
|
|
||||||
self._monitor_task = asyncio.create_task(self._monitor())
|
async def process_settings(self):
|
||||||
self._monitor_task.add_done_callback(task_callback_exception)
|
data = change_dict_naming_convention(await self._config.yaml.all())
|
||||||
|
with open(LAVALINK_APP_YML, "w") as f:
|
||||||
|
yaml.safe_dump(data, f)
|
||||||
|
|
||||||
async def _get_jar_args(self) -> List[str]:
|
async def _get_jar_args(self) -> Tuple[List[str], Optional[str]]:
|
||||||
(java_available, java_version) = await self._has_java()
|
(java_available, java_version) = await self._has_java()
|
||||||
|
|
||||||
if not java_available:
|
if not java_available:
|
||||||
raise RuntimeError("You must install Java 11 for Lavalink to run.")
|
if self._java_version is None:
|
||||||
|
extras = ""
|
||||||
return [
|
else:
|
||||||
|
extras = f" however you have version {self._java_version} (executable: {self._java_exc})"
|
||||||
|
raise UnsupportedJavaException(
|
||||||
|
await replace_p_with_prefix(
|
||||||
|
self.cog.bot,
|
||||||
|
f"The managed Lavalink node requires Java 11 to run{extras};\n"
|
||||||
|
"Either install version 11 and restart the bot or connect to an external Lavalink node "
|
||||||
|
"(https://docs.discord.red/en/stable/install_guides/index.html)\n"
|
||||||
|
"If you already have Java 11 installed then then you will need to specify the executable path, "
|
||||||
|
"use '[p]llset java' to set the correct Java 11 executable.",
|
||||||
|
) # TODO: Replace with Audio docs when they are out
|
||||||
|
)
|
||||||
|
java_xms, java_xmx = list((await self._config.java.all()).values())
|
||||||
|
match = re.match(r"^(\d+)([MG])$", java_xmx, flags=re.IGNORECASE)
|
||||||
|
command_args = [
|
||||||
self._java_exc,
|
self._java_exc,
|
||||||
"-Djdk.tls.client.protocols=TLSv1.2",
|
"-Djdk.tls.client.protocols=TLSv1.2",
|
||||||
"-jar",
|
f"-Xms{java_xms}",
|
||||||
str(LAVALINK_JAR_FILE),
|
|
||||||
]
|
]
|
||||||
|
meta = 0, None
|
||||||
|
invalid = None
|
||||||
|
if match and (
|
||||||
|
(int(match.group(1)) * 1024 ** (2 if match.group(2).lower() == "m" else 3))
|
||||||
|
< (meta := get_max_allocation_size(self._java_exc))[0]
|
||||||
|
):
|
||||||
|
command_args.append(f"-Xmx{java_xmx}")
|
||||||
|
elif meta[0] is not None:
|
||||||
|
invalid = await replace_p_with_prefix(
|
||||||
|
self.cog.bot,
|
||||||
|
"Managed Lavalink node RAM allocation ignored due to system limitations, "
|
||||||
|
"please fix this by setting the correct value with '[p]llset heapsize'.",
|
||||||
|
)
|
||||||
|
|
||||||
|
command_args.extend(["-jar", str(LAVALINK_JAR_FILE)])
|
||||||
|
self._args = command_args
|
||||||
|
return command_args, invalid
|
||||||
|
|
||||||
async def _has_java(self) -> Tuple[bool, Optional[Tuple[int, int]]]:
|
async def _has_java(self) -> Tuple[bool, Optional[Tuple[int, int]]]:
|
||||||
if self._java_available is not None:
|
if self._java_available:
|
||||||
# Return cached value if we've checked this before
|
# Return cached value if we've checked this before
|
||||||
return self._java_available, self._java_version
|
return self._java_available, self._java_version
|
||||||
java_exec = shutil.which(self._java_exc)
|
java_exec = shutil.which(self._java_exc)
|
||||||
java_available = java_exec is not None
|
java_available = java_exec is not None
|
||||||
if not java_available:
|
if not java_available:
|
||||||
self.java_available = False
|
self._java_available = False
|
||||||
self.java_version = None
|
self._java_version = None
|
||||||
else:
|
else:
|
||||||
self._java_version = version = await self._get_java_version()
|
self._java_version = await self._get_java_version()
|
||||||
self._java_available = (11, 0) <= version < (12, 0)
|
self._java_available = (11, 0) <= self._java_version < (12, 0)
|
||||||
self._java_exc = java_exec
|
self._java_exc = java_exec
|
||||||
return self._java_available, self._java_version
|
return self._java_available, self._java_version
|
||||||
|
|
||||||
@ -224,58 +307,49 @@ class ServerManager:
|
|||||||
|
|
||||||
return major, minor
|
return major, minor
|
||||||
|
|
||||||
raise RuntimeError(f"The output of `{self._java_exc} -version` was unexpected.")
|
raise UnexpectedJavaResponseException(
|
||||||
|
f"The output of `{self._java_exc} -version` was unexpected\n{version_info}."
|
||||||
|
)
|
||||||
|
|
||||||
async def _wait_for_launcher(self) -> None:
|
async def _wait_for_launcher(self) -> None:
|
||||||
log.debug("Waiting for Lavalink server to be ready")
|
log.info("Waiting for Managed Lavalink node to be ready")
|
||||||
lastmessage = 0
|
|
||||||
for i in itertools.cycle(range(50)):
|
for i in itertools.cycle(range(50)):
|
||||||
line = await self._proc.stdout.readline()
|
line = await self._proc.stdout.readline()
|
||||||
if _RE_READY_LINE.search(line):
|
if _RE_READY_LINE.search(line):
|
||||||
self.ready.set()
|
self.ready.set()
|
||||||
log.info("Internal Lavalink server is ready to receive requests.")
|
log.info("Managed Lavalink node is ready to receive requests.")
|
||||||
break
|
break
|
||||||
if _FAILED_TO_START.search(line):
|
if _FAILED_TO_START.search(line):
|
||||||
raise RuntimeError(f"Lavalink failed to start: {line.decode().strip()}")
|
raise ManagedLavalinkStartFailure(
|
||||||
if self._proc.returncode is not None and lastmessage + 2 < time.time():
|
f"Lavalink failed to start: {line.decode().strip()}"
|
||||||
|
)
|
||||||
|
if self._proc.returncode is not None:
|
||||||
# Avoid Console spam only print once every 2 seconds
|
# Avoid Console spam only print once every 2 seconds
|
||||||
lastmessage = time.time()
|
raise EarlyExitException("Managed Lavalink node server exited early.")
|
||||||
log.critical("Internal lavalink server exited early")
|
|
||||||
if i == 49:
|
if i == 49:
|
||||||
# Sleep after 50 lines to prevent busylooping
|
# Sleep after 50 lines to prevent busylooping
|
||||||
await asyncio.sleep(0.1)
|
await asyncio.sleep(0.1)
|
||||||
|
|
||||||
async def _monitor(self) -> None:
|
|
||||||
while self._proc.returncode is None:
|
|
||||||
await asyncio.sleep(0.5)
|
|
||||||
|
|
||||||
# This task hasn't been cancelled - Lavalink was shut down by something else
|
|
||||||
log.warning("Internal Lavalink jar shutdown unexpectedly")
|
|
||||||
if not self._has_java_error():
|
|
||||||
log.info("Restarting internal Lavalink server")
|
|
||||||
await self.start(self._java_exc)
|
|
||||||
else:
|
|
||||||
log.critical(
|
|
||||||
"Your Java is borked. Please find the hs_err_pid%d.log file"
|
|
||||||
" in the Audio data folder and report this issue.",
|
|
||||||
self._proc.pid,
|
|
||||||
)
|
|
||||||
|
|
||||||
def _has_java_error(self) -> bool:
|
|
||||||
poss_error_file = LAVALINK_DOWNLOAD_DIR / "hs_err_pid{}.log".format(self._proc.pid)
|
|
||||||
return poss_error_file.exists()
|
|
||||||
|
|
||||||
async def shutdown(self) -> None:
|
async def shutdown(self) -> None:
|
||||||
if self._shutdown is True or self._proc is None:
|
if self.start_monitor_task is not None:
|
||||||
|
self.start_monitor_task.cancel()
|
||||||
|
await self._partial_shutdown()
|
||||||
|
|
||||||
|
async def _partial_shutdown(self) -> None:
|
||||||
|
self.ready.clear()
|
||||||
|
# In certain situations to await self._proc.wait() is invalid so waiting on it waits forever.
|
||||||
|
if self._shutdown is True:
|
||||||
# For convenience, calling this method more than once or calling it before starting it
|
# For convenience, calling this method more than once or calling it before starting it
|
||||||
# does nothing.
|
# does nothing.
|
||||||
return
|
return
|
||||||
log.info("Shutting down internal Lavalink server")
|
if self._node_pid:
|
||||||
if self._monitor_task is not None:
|
with contextlib.suppress(psutil.Error):
|
||||||
self._monitor_task.cancel()
|
p = psutil.Process(self._node_pid)
|
||||||
self._proc.terminate()
|
p.terminate()
|
||||||
await self._proc.wait()
|
p.kill()
|
||||||
|
self._proc = None
|
||||||
self._shutdown = True
|
self._shutdown = True
|
||||||
|
self._node_pid = None
|
||||||
|
|
||||||
async def _download_jar(self) -> None:
|
async def _download_jar(self) -> None:
|
||||||
log.info("Downloading Lavalink.jar...")
|
log.info("Downloading Lavalink.jar...")
|
||||||
@ -327,7 +401,7 @@ class ServerManager:
|
|||||||
if self._up_to_date is True:
|
if self._up_to_date is True:
|
||||||
# Return cached value if we've checked this before
|
# Return cached value if we've checked this before
|
||||||
return True
|
return True
|
||||||
args = await self._get_jar_args()
|
args, _ = await self._get_jar_args()
|
||||||
args.append("--version")
|
args.append("--version")
|
||||||
_proc = await asyncio.subprocess.create_subprocess_exec( # pylint:disable=no-member
|
_proc = await asyncio.subprocess.create_subprocess_exec( # pylint:disable=no-member
|
||||||
*args,
|
*args,
|
||||||
@ -366,3 +440,118 @@ class ServerManager:
|
|||||||
async def maybe_download_jar(self):
|
async def maybe_download_jar(self):
|
||||||
if not (LAVALINK_JAR_FILE.exists() and await self._is_up_to_date()):
|
if not (LAVALINK_JAR_FILE.exists() and await self._is_up_to_date()):
|
||||||
await self._download_jar()
|
await self._download_jar()
|
||||||
|
|
||||||
|
async def wait_until_ready(self, timeout: Optional[float] = None):
|
||||||
|
await asyncio.wait_for(self.ready.wait(), timeout=timeout or self.timeout)
|
||||||
|
|
||||||
|
async def start_monitor(self, java_path: str):
|
||||||
|
retry_count = 0
|
||||||
|
backoff = ExponentialBackoff(base=7)
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
self._shutdown = False
|
||||||
|
if self._node_pid is None or not psutil.pid_exists(self._node_pid):
|
||||||
|
self.ready.clear()
|
||||||
|
await self._start(java_path=java_path)
|
||||||
|
while True:
|
||||||
|
await self.wait_until_ready(timeout=self.timeout)
|
||||||
|
if not psutil.pid_exists(self._node_pid):
|
||||||
|
raise NoProcessFound
|
||||||
|
try:
|
||||||
|
node = lavalink.get_all_nodes()[0]
|
||||||
|
if node.ready:
|
||||||
|
# Hoping this throws an exception which will then trigger a restart
|
||||||
|
await node._ws.ping()
|
||||||
|
backoff = ExponentialBackoff(
|
||||||
|
base=7
|
||||||
|
) # Reassign Backoff to reset it on successful ping.
|
||||||
|
# ExponentialBackoff.reset() would be a nice method to have
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
else:
|
||||||
|
await asyncio.sleep(5)
|
||||||
|
except IndexError:
|
||||||
|
# In case lavalink.get_all_nodes() returns 0 Nodes
|
||||||
|
# (During a connect or multiple connect failures)
|
||||||
|
try:
|
||||||
|
log.debug(
|
||||||
|
"Managed node monitor detected RLL is not connected to any nodes"
|
||||||
|
)
|
||||||
|
await lavalink.wait_until_ready(timeout=60, wait_if_no_node=60)
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
self.cog.lavalink_restart_connect(manual=True)
|
||||||
|
return # lavalink_restart_connect will cause a new monitor task to be created.
|
||||||
|
except Exception as exc:
|
||||||
|
log.debug(exc, exc_info=exc)
|
||||||
|
raise NodeUnhealthy(str(exc))
|
||||||
|
except (TooManyProcessFound, IncorrectProcessFound, NoProcessFound):
|
||||||
|
await self._partial_shutdown()
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
delay = backoff.delay()
|
||||||
|
await self._partial_shutdown()
|
||||||
|
log.warning(
|
||||||
|
"Lavalink Managed node health check timeout, restarting in %s seconds",
|
||||||
|
delay,
|
||||||
|
)
|
||||||
|
await asyncio.sleep(delay)
|
||||||
|
except NodeUnhealthy:
|
||||||
|
delay = backoff.delay()
|
||||||
|
await self._partial_shutdown()
|
||||||
|
log.warning(
|
||||||
|
"Lavalink Managed node health check failed, restarting in %s seconds",
|
||||||
|
delay,
|
||||||
|
)
|
||||||
|
await asyncio.sleep(delay)
|
||||||
|
except LavalinkDownloadFailed as exc:
|
||||||
|
delay = backoff.delay()
|
||||||
|
if exc.should_retry:
|
||||||
|
log.warning(
|
||||||
|
"Lavalink Managed node download failed retrying in %s seconds\n%s",
|
||||||
|
delay,
|
||||||
|
exc.response,
|
||||||
|
)
|
||||||
|
retry_count += 1
|
||||||
|
await self._partial_shutdown()
|
||||||
|
await asyncio.sleep(delay)
|
||||||
|
else:
|
||||||
|
log.critical(
|
||||||
|
"Fatal exception whilst starting managed Lavalink node, "
|
||||||
|
"aborting...\n%s",
|
||||||
|
exc.response,
|
||||||
|
)
|
||||||
|
self.cog.lavalink_connection_aborted = True
|
||||||
|
return await self.shutdown()
|
||||||
|
except InvalidArchitectureException:
|
||||||
|
log.critical("Invalid machine architecture, cannot run a managed Lavalink node.")
|
||||||
|
self.cog.lavalink_connection_aborted = True
|
||||||
|
return await self.shutdown()
|
||||||
|
except (UnsupportedJavaException, UnexpectedJavaResponseException) as exc:
|
||||||
|
log.critical(exc)
|
||||||
|
self.cog.lavalink_connection_aborted = True
|
||||||
|
return await self.shutdown()
|
||||||
|
except ManagedLavalinkNodeException as exc:
|
||||||
|
delay = backoff.delay()
|
||||||
|
log.critical(
|
||||||
|
exc,
|
||||||
|
)
|
||||||
|
await self._partial_shutdown()
|
||||||
|
log.warning(
|
||||||
|
"Lavalink Managed node startup failed retrying in %s seconds",
|
||||||
|
delay,
|
||||||
|
)
|
||||||
|
await asyncio.sleep(delay)
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
return
|
||||||
|
except Exception as exc:
|
||||||
|
delay = backoff.delay()
|
||||||
|
log.warning(
|
||||||
|
"Lavalink Managed node startup failed retrying in %s seconds",
|
||||||
|
delay,
|
||||||
|
)
|
||||||
|
log.debug(exc, exc_info=exc)
|
||||||
|
await self._partial_shutdown()
|
||||||
|
await asyncio.sleep(delay)
|
||||||
|
|
||||||
|
async def start(self, java_path: str):
|
||||||
|
if self.start_monitor_task is not None:
|
||||||
|
await self.shutdown()
|
||||||
|
self.start_monitor_task = asyncio.create_task(self.start_monitor(java_path))
|
||||||
|
|||||||
@ -1,21 +1,128 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import contextlib
|
import contextlib
|
||||||
|
import math
|
||||||
|
import platform
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from enum import Enum, unique
|
from enum import Enum, unique
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import MutableMapping
|
from typing import MutableMapping, Tuple, Union
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
|
import psutil
|
||||||
from red_commons.logging import getLogger
|
from red_commons.logging import getLogger
|
||||||
|
|
||||||
from redbot.core import commands
|
from redbot.core import commands
|
||||||
|
from redbot.core.bot import Red
|
||||||
from redbot.core.i18n import Translator
|
from redbot.core.i18n import Translator
|
||||||
|
|
||||||
log = getLogger("red.cogs.Audio.task.callback")
|
log = getLogger("red.cogs.Audio.task.callback")
|
||||||
_ = Translator("Audio", Path(__file__))
|
_ = Translator("Audio", Path(__file__))
|
||||||
|
|
||||||
|
|
||||||
|
def get_max_allocation_size(exec) -> Tuple[int, bool]:
|
||||||
|
if platform.architecture(exec)[0] == "64bit":
|
||||||
|
max_heap_allowed = psutil.virtual_memory().total
|
||||||
|
thinks_is_64_bit = True
|
||||||
|
else:
|
||||||
|
max_heap_allowed = 4 * 1024**3
|
||||||
|
thinks_is_64_bit = False
|
||||||
|
return max_heap_allowed, thinks_is_64_bit
|
||||||
|
|
||||||
|
|
||||||
|
def get_jar_ram_defaults() -> Tuple[str, str]:
|
||||||
|
min_ram = 64 * 1024**2
|
||||||
|
# We don't know the java executable at this stage - not worth the extra work required here
|
||||||
|
max_allocation, is_64bit = get_max_allocation_size(sys.executable)
|
||||||
|
max_ram_allowed = max_allocation * 0.5 if is_64bit else max_allocation
|
||||||
|
max_ram = max(min_ram, max_ram_allowed)
|
||||||
|
size_name = ("", "K", "M", "G", "T")
|
||||||
|
i = int(math.floor(math.log(min_ram, 1024)))
|
||||||
|
p = math.pow(1024, i)
|
||||||
|
s = int(min_ram // p)
|
||||||
|
min_ram = f"{s}{size_name[i]}"
|
||||||
|
|
||||||
|
i = int(math.floor(math.log(max_ram, 1024)))
|
||||||
|
p = math.pow(1024, i)
|
||||||
|
s = int(max_ram // p)
|
||||||
|
max_ram = f"{s}{size_name[i]}"
|
||||||
|
|
||||||
|
return min_ram, max_ram
|
||||||
|
|
||||||
|
|
||||||
|
MIN_JAVA_RAM, MAX_JAVA_RAM = get_jar_ram_defaults()
|
||||||
|
|
||||||
|
DEFAULT_LAVALINK_YAML = {
|
||||||
|
# The nesting structure of this dict is very important, it's a 1:1 mirror of application.yaml in JSON
|
||||||
|
"yaml__server__address": "localhost",
|
||||||
|
"yaml__server__port": 2333,
|
||||||
|
"yaml__lavalink__server__password": "youshallnotpass",
|
||||||
|
"yaml__lavalink__server__sources__http": True,
|
||||||
|
"yaml__lavalink__server__sources__bandcamp": True,
|
||||||
|
"yaml__lavalink__server__sources__local": True,
|
||||||
|
"yaml__lavalink__server__sources__soundcloud": True,
|
||||||
|
"yaml__lavalink__server__sources__youtube": True,
|
||||||
|
"yaml__lavalink__server__sources__twitch": True,
|
||||||
|
"yaml__lavalink__server__sources__vimeo": True,
|
||||||
|
"yaml__lavalink__server__bufferDurationMs": 400,
|
||||||
|
"yaml__lavalink__server__frameBufferDurationMs": 1000,
|
||||||
|
# 100 pages - 100 entries per page = 10,000 tracks which is the Audio Limit for a single playlist.
|
||||||
|
"yaml__lavalink__server__youtubePlaylistLoadLimit": 100,
|
||||||
|
"yaml__lavalink__server__playerUpdateInterval": 1,
|
||||||
|
"yaml__lavalink__server__youtubeSearchEnabled": True,
|
||||||
|
"yaml__lavalink__server__soundcloudSearchEnabled": True,
|
||||||
|
"yaml__lavalink__server__gc_warnings": True,
|
||||||
|
"yaml__metrics__prometheus__enabled": False,
|
||||||
|
"yaml__metrics__prometheus__endpoint": "/metrics",
|
||||||
|
"yaml__sentry__dsn": "",
|
||||||
|
"yaml__sentry__environment": "",
|
||||||
|
"yaml__logging__file__max_history": 15,
|
||||||
|
"yaml__logging__file__max_size": "10MB",
|
||||||
|
"yaml__logging__path": "./logs/",
|
||||||
|
"yaml__logging__level__root": "INFO",
|
||||||
|
"yaml__logging__level__lavalink": "INFO",
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFAULT_LAVALINK_SETTINGS = {
|
||||||
|
"host": DEFAULT_LAVALINK_YAML["yaml__server__address"],
|
||||||
|
"rest_port": DEFAULT_LAVALINK_YAML["yaml__server__port"],
|
||||||
|
"ws_port": DEFAULT_LAVALINK_YAML["yaml__server__port"],
|
||||||
|
"password": DEFAULT_LAVALINK_YAML["yaml__lavalink__server__password"],
|
||||||
|
"secured_ws": False,
|
||||||
|
"java__Xms": MIN_JAVA_RAM,
|
||||||
|
"java__Xmx": MAX_JAVA_RAM,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def sizeof_fmt(num: Union[float, int]) -> str:
|
||||||
|
for unit in ["", "K", "M", "G", "T", "P", "E", "Z"]:
|
||||||
|
if abs(num) < 1024.0:
|
||||||
|
return f"{num:3.1f}{unit}"
|
||||||
|
num /= 1024.0
|
||||||
|
return f"{num:.1f}Y"
|
||||||
|
|
||||||
|
|
||||||
|
# This assumes all keys with `_` should be converted from `part1_part2` to `part1-part2`
|
||||||
|
def convert_function(key: str) -> str:
|
||||||
|
return key.replace("_", "-")
|
||||||
|
|
||||||
|
|
||||||
|
def change_dict_naming_convention(data) -> dict:
|
||||||
|
new = {}
|
||||||
|
for k, v in data.items():
|
||||||
|
new_v = v
|
||||||
|
if isinstance(v, dict):
|
||||||
|
new_v = change_dict_naming_convention(v)
|
||||||
|
elif isinstance(v, list):
|
||||||
|
new_v = list()
|
||||||
|
for x in v:
|
||||||
|
new_v.append(change_dict_naming_convention(x))
|
||||||
|
new[convert_function(k)] = new_v
|
||||||
|
return new
|
||||||
|
|
||||||
|
|
||||||
class CacheLevel:
|
class CacheLevel:
|
||||||
__slots__ = ("value",)
|
__slots__ = ("value",)
|
||||||
|
|
||||||
@ -216,33 +323,28 @@ class PlaylistScope(Enum):
|
|||||||
return list(map(lambda c: c.value, PlaylistScope))
|
return list(map(lambda c: c.value, PlaylistScope))
|
||||||
|
|
||||||
|
|
||||||
def task_callback_exception(task: asyncio.Task) -> None:
|
def has_managed_server():
|
||||||
with contextlib.suppress(asyncio.CancelledError, asyncio.InvalidStateError):
|
|
||||||
if exc := task.exception():
|
|
||||||
log.exception("%s raised an Exception", task.get_name(), exc_info=exc)
|
|
||||||
|
|
||||||
|
|
||||||
def task_callback_debug(task: asyncio.Task) -> None:
|
|
||||||
with contextlib.suppress(asyncio.CancelledError, asyncio.InvalidStateError):
|
|
||||||
if exc := task.exception():
|
|
||||||
log.debug("%s raised an Exception", task.get_name(), exc_info=exc)
|
|
||||||
|
|
||||||
|
|
||||||
def task_callback_verbose(task: asyncio.Task) -> None:
|
|
||||||
with contextlib.suppress(asyncio.CancelledError, asyncio.InvalidStateError):
|
|
||||||
if exc := task.exception():
|
|
||||||
log.verbose("%s raised an Exception", task.get_name(), exc_info=exc)
|
|
||||||
|
|
||||||
|
|
||||||
def task_callback_trace(task: asyncio.Task) -> None:
|
|
||||||
with contextlib.suppress(asyncio.CancelledError, asyncio.InvalidStateError):
|
|
||||||
if exc := task.exception():
|
|
||||||
log.trace("%s raised an Exception", task.get_name(), exc_info=exc)
|
|
||||||
|
|
||||||
|
|
||||||
def has_internal_server():
|
|
||||||
async def pred(ctx: commands.Context):
|
async def pred(ctx: commands.Context):
|
||||||
|
if ctx.cog is None:
|
||||||
|
return True
|
||||||
external = await ctx.cog.config.use_external_lavalink()
|
external = await ctx.cog.config.use_external_lavalink()
|
||||||
return not external
|
return not external
|
||||||
|
|
||||||
return commands.check(pred)
|
return commands.check(pred)
|
||||||
|
|
||||||
|
|
||||||
|
def has_unmanaged_server():
|
||||||
|
async def pred(ctx: commands.Context):
|
||||||
|
if ctx.cog is None:
|
||||||
|
return True
|
||||||
|
external = await ctx.cog.config.use_external_lavalink()
|
||||||
|
return external
|
||||||
|
|
||||||
|
return commands.check(pred)
|
||||||
|
|
||||||
|
|
||||||
|
async def replace_p_with_prefix(bot: Red, message: str) -> str:
|
||||||
|
"""Replaces [p] with the bot prefix"""
|
||||||
|
prefixes = await bot.get_valid_prefixes()
|
||||||
|
prefix = re.sub(rf"<@!?{bot.user.id}>", f"@{bot.user.name}".replace("\\", r"\\"), prefixes[0])
|
||||||
|
return message.replace("[p]", prefix)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user