[Audio] New stuff from RLL 0.7.0 (#4529)

* New stuff from RLL 0.7.0

* discard here

* formatting

* do this properly

* make it more unique

* bump RLL

* nuke `[p]llset restport`, only `[p]llset wsport` matters

* Update setup.cfg

* properly deprecate Rest port and Ensure Nodes are properly closed upon running LLSET commands

* restore player on a attempt reconnect

* restore player as a task

* ensure we send the signal only if not playing.

* register events a little earlier

* hmmm

* ffs

* update application.yml

* fix permissions edge case
This commit is contained in:
Draper
2020-10-27 16:16:19 +00:00
committed by GitHub
parent af8af1934c
commit d421c1c240
12 changed files with 61 additions and 42 deletions

View File

@@ -1,4 +1,5 @@
import asyncio import asyncio
import datetime
import json import json
from collections import Counter from collections import Counter
@@ -62,6 +63,7 @@ class Audio(
self.play_lock = {} self.play_lock = {}
self.lavalink_connect_task = None self.lavalink_connect_task = None
self._restore_task = None
self.player_automated_timer_task = None self.player_automated_timer_task = None
self.cog_cleaned_up = False self.cog_cleaned_up = False
self.lavalink_connection_aborted = False self.lavalink_connection_aborted = False
@@ -82,6 +84,8 @@ class Audio(
"can_post": False, "can_post": False,
"can_delete": False, "can_delete": False,
} }
self._ll_guild_updates = set()
self._last_ll_update = datetime.datetime.now(datetime.timezone.utc)
default_global = dict( default_global = dict(
schema_version=1, schema_version=1,

View File

@@ -1,11 +1,12 @@
from __future__ import annotations from __future__ import annotations
import asyncio import asyncio
import datetime
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from collections import Counter from collections import Counter
from pathlib import Path from pathlib import Path
from typing import TYPE_CHECKING, Any, List, Mapping, MutableMapping, Optional, Tuple, Union from typing import Set, TYPE_CHECKING, Any, List, Mapping, MutableMapping, Optional, Tuple, Union
import aiohttp import aiohttp
import discord import discord
@@ -57,6 +58,7 @@ class MixinMeta(ABC):
_error_counter: Counter _error_counter: Counter
lavalink_connect_task: Optional[asyncio.Task] lavalink_connect_task: Optional[asyncio.Task]
_restore_task: Optional[asyncio.Task]
player_automated_timer_task: Optional[asyncio.Task] player_automated_timer_task: Optional[asyncio.Task]
cog_init_task: Optional[asyncio.Task] cog_init_task: Optional[asyncio.Task]
cog_ready_event: asyncio.Event cog_ready_event: asyncio.Event
@@ -64,6 +66,9 @@ class MixinMeta(ABC):
_default_lavalink_settings: Mapping _default_lavalink_settings: Mapping
permission_cache = discord.Permissions permission_cache = discord.Permissions
_last_ll_update: datetime.datetime
_ll_guild_updates: Set[int]
@abstractmethod @abstractmethod
async def command_llsetup(self, ctx: commands.Context): async def command_llsetup(self, ctx: commands.Context):
raise NotImplementedError() raise NotImplementedError()
@@ -122,6 +127,12 @@ class MixinMeta(ABC):
) -> None: ) -> None:
raise NotImplementedError() raise NotImplementedError()
@abstractmethod
async def lavalink_update_handler(
self, player: lavalink.Player, event_type: lavalink.enums.PlayerState, extra
) -> None:
raise NotImplementedError()
@abstractmethod @abstractmethod
async def _clear_react( async def _clear_react(
self, message: discord.Message, emoji: MutableMapping = None self, message: discord.Message, emoji: MutableMapping = None

View File

@@ -1472,14 +1472,12 @@ 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():
lavalink.unregister_event_listener(self.lavalink_event_handler)
await lavalink.close() await lavalink.close()
if self.player_manager is not None: if self.player_manager is not None:
await self.player_manager.shutdown() await self.player_manager.shutdown()
self.lavalink_restart_connect() self.lavalink_restart_connect()
lavalink.register_event_listener(self.lavalink_event_handler)
await self.restore_players()
await self.send_embed_msg( await self.send_embed_msg(
ctx, ctx,
title=_("Restarting Lavalink"), title=_("Restarting Lavalink"),

View File

@@ -72,6 +72,7 @@ class PlayerControllerCommands(MixinMeta, metaclass=CompositeMetaClass):
await self.config.custom("EQUALIZER", ctx.guild.id).eq_bands.set(eq.bands) await self.config.custom("EQUALIZER", ctx.guild.id).eq_bands.set(eq.bands)
await player.stop() await player.stop()
await player.disconnect() await player.disconnect()
self._ll_guild_updates.discard(ctx.guild.id)
await self.api_interface.persistent_queue_api.drop(ctx.guild.id) await self.api_interface.persistent_queue_api.drop(ctx.guild.id)
@commands.command(name="now") @commands.command(name="now")

View File

@@ -72,7 +72,7 @@ class LavalinkSetupCommands(MixinMeta, metaclass=CompositeMetaClass):
ctx, ctx,
title=_("Failed To Shutdown Lavalink"), title=_("Failed To Shutdown Lavalink"),
description=_( description=_(
"For it to take effect please reload " "Audio (`{prefix}reload audio`)." "For it to take effect please reload Audio (`{prefix}reload audio`)."
).format( ).format(
prefix=ctx.prefix, prefix=ctx.prefix,
), ),
@@ -188,31 +188,6 @@ class LavalinkSetupCommands(MixinMeta, metaclass=CompositeMetaClass):
), ),
) )
@command_llsetup.command(name="restport")
async def command_llsetup_restport(self, ctx: commands.Context, rest_port: int):
"""Set the Lavalink REST server port."""
await self.config.rest_port.set(rest_port)
footer = None
if await self.update_external_status():
footer = _("External Lavalink server set to True.")
await self.send_embed_msg(
ctx,
title=_("Setting Changed"),
description=_("REST port set to {port}.").format(port=rest_port),
footer=footer,
)
try:
self.lavalink_restart_connect()
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="wsport") @command_llsetup.command(name="wsport")
async def command_llsetup_wsport(self, ctx: commands.Context, ws_port: int): async def command_llsetup_wsport(self, ctx: commands.Context, ws_port: int):
"""Set the Lavalink websocket server port.""" """Set the Lavalink websocket server port."""
@@ -248,8 +223,9 @@ class LavalinkSetupCommands(MixinMeta, metaclass=CompositeMetaClass):
ws_port = configs["ws_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=host)
msg += _("Rest Port: [{port}]\n").format(port=rest_port)
msg += _("WS Port: [{port}]\n").format(port=ws_port) msg += _("WS Port: [{port}]\n").format(port=ws_port)
if ws_port != rest_port:
msg += _("Rest Port: [{port}]\n").format(port=rest_port)
msg += _("Password: [{password}]\n").format(password=password) msg += _("Password: [{password}]\n").format(password=password)
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"))

View File

@@ -278,7 +278,7 @@ class PlayerCommands(MixinMeta, metaclass=CompositeMetaClass):
if not await self.is_query_allowed( if not await self.is_query_allowed(
self.config, self.config,
ctx, ctx,
f"{single_track.title} {single_track.author} {single_track.uri} " f"{str(query)}", f"{single_track.title} {single_track.author} {single_track.uri} {str(query)}",
query_obj=query, query_obj=query,
): ):
if IS_DEBUG: if IS_DEBUG:

View File

@@ -239,7 +239,11 @@ class DpyEvents(MixinMeta, metaclass=CompositeMetaClass):
if self.cog_init_task: if self.cog_init_task:
self.cog_init_task.cancel() self.cog_init_task.cancel()
if self._restore_task:
self._restore_task.cancel()
lavalink.unregister_event_listener(self.lavalink_event_handler) lavalink.unregister_event_listener(self.lavalink_event_handler)
lavalink.unregister_update_listener(self.lavalink_update_handler)
self.bot.loop.create_task(lavalink.close()) self.bot.loop.create_task(lavalink.close())
if self.player_manager is not None: if self.player_manager is not None:
self.bot.loop.create_task(self.player_manager.shutdown()) self.bot.loop.create_task(self.player_manager.shutdown())

View File

@@ -1,5 +1,6 @@
import asyncio import asyncio
import contextlib import contextlib
import datetime
import logging import logging
from pathlib import Path from pathlib import Path
@@ -16,6 +17,12 @@ _ = Translator("Audio", Path(__file__))
class LavalinkEvents(MixinMeta, metaclass=CompositeMetaClass): class LavalinkEvents(MixinMeta, metaclass=CompositeMetaClass):
async def lavalink_update_handler(
self, player: lavalink.Player, event_type: lavalink.enums.PlayerState, extra
):
self._last_ll_update = datetime.datetime.now(datetime.timezone.utc)
self._ll_guild_updates.add(int(extra.get("guildId", 0)))
async def lavalink_event_handler( async def lavalink_event_handler(
self, player: lavalink.Player, event_type: lavalink.LavalinkEvents, extra self, player: lavalink.Player, event_type: lavalink.LavalinkEvents, extra
) -> None: ) -> None:
@@ -160,6 +167,7 @@ class LavalinkEvents(MixinMeta, metaclass=CompositeMetaClass):
if disconnect: if disconnect:
self.bot.dispatch("red_audio_audio_disconnect", guild) self.bot.dispatch("red_audio_audio_disconnect", guild)
await player.disconnect() await player.disconnect()
self._ll_guild_updates.discard(guild.id)
if status: if status:
player_check = await self.get_active_player_count() player_check = await self.get_active_player_count()
await self.update_bot_presence(*player_check) await self.update_bot_presence(*player_check)
@@ -193,6 +201,7 @@ class LavalinkEvents(MixinMeta, metaclass=CompositeMetaClass):
await self.config.custom("EQUALIZER", guild_id).eq_bands.set(eq.bands) await self.config.custom("EQUALIZER", guild_id).eq_bands.set(eq.bands)
await player.stop() await player.stop()
await player.disconnect() await player.disconnect()
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 = self.bot.get_channel(message_channel)

View File

@@ -4,6 +4,7 @@ from pathlib import Path
import lavalink import lavalink
from redbot.core import data_manager
from redbot.core.i18n import Translator from redbot.core.i18n import Translator
from ...errors import LavalinkDownloadFailed from ...errors import LavalinkDownloadFailed
from ...manager import ServerManager from ...manager import ServerManager
@@ -16,9 +17,16 @@ _ = 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) -> None:
lavalink.unregister_event_listener(self.lavalink_event_handler)
lavalink.unregister_update_listener(self.lavalink_update_handler)
if self.lavalink_connect_task: if self.lavalink_connect_task:
self.lavalink_connect_task.cancel() self.lavalink_connect_task.cancel()
if self._restore_task:
self._restore_task.cancel()
self._restore_task = None
lavalink.register_event_listener(self.lavalink_event_handler)
lavalink.register_update_listener(self.lavalink_update_handler)
self.lavalink_connect_task = self.bot.loop.create_task(self.lavalink_attempt_connect()) self.lavalink_connect_task = self.bot.loop.create_task(self.lavalink_attempt_connect())
async def lavalink_attempt_connect(self, timeout: int = 50) -> None: async def lavalink_attempt_connect(self, timeout: int = 50) -> None:
@@ -33,7 +41,6 @@ class LavalinkTasks(MixinMeta, metaclass=CompositeMetaClass):
settings = self._default_lavalink_settings settings = self._default_lavalink_settings
host = settings["host"] host = settings["host"]
password = settings["password"] password = settings["password"]
rest_port = settings["rest_port"]
ws_port = settings["ws_port"] ws_port = settings["ws_port"]
if self.player_manager is not None: if self.player_manager is not None:
await self.player_manager.shutdown() await self.player_manager.shutdown()
@@ -73,7 +80,6 @@ class LavalinkTasks(MixinMeta, metaclass=CompositeMetaClass):
else: else:
host = configs["host"] host = configs["host"]
password = configs["password"] password = configs["password"]
rest_port = configs["rest_port"]
ws_port = configs["ws_port"] ws_port = configs["ws_port"]
break break
else: else:
@@ -86,14 +92,17 @@ class LavalinkTasks(MixinMeta, metaclass=CompositeMetaClass):
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,
rest_port=rest_port, rest_port=ws_port,
ws_port=ws_port, ws_port=ws_port,
timeout=timeout, timeout=timeout,
resume_key=f"Red-Core-Audio-{self.bot.user.id}-{data_manager.instance_name}",
) )
except asyncio.TimeoutError: except asyncio.TimeoutError:
log.error("Connecting to Lavalink server timed out, retrying...") log.error("Connecting to Lavalink server timed out, retrying...")
@@ -115,3 +124,5 @@ class LavalinkTasks(MixinMeta, metaclass=CompositeMetaClass):
"Connecting to the Lavalink server failed after multiple attempts. " "Connecting to the Lavalink server failed after multiple attempts. "
"See above tracebacks for details." "See above tracebacks for details."
) )
return
self._restore_task = asyncio.create_task(self.restore_players())

View File

@@ -57,8 +57,6 @@ class StartUpTasks(MixinMeta, metaclass=CompositeMetaClass):
self.player_automated_timer() self.player_automated_timer()
) )
self.player_automated_timer_task.add_done_callback(task_callback) self.player_automated_timer_task.add_done_callback(task_callback)
lavalink.register_event_listener(self.lavalink_event_handler)
await self.restore_players()
except Exception as err: except Exception as err:
log.exception("Audio failed to start up, please report this issue.", exc_info=err) log.exception("Audio failed to start up, please report this issue.", exc_info=err)
raise err raise err
@@ -68,6 +66,7 @@ class StartUpTasks(MixinMeta, metaclass=CompositeMetaClass):
async def restore_players(self): async def restore_players(self):
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()
await asyncio.sleep(10)
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)
try: try:
@@ -95,6 +94,12 @@ class StartUpTasks(MixinMeta, metaclass=CompositeMetaClass):
while tries < 25 and vc is not None: while tries < 25 and vc is not None:
try: try:
vc = guild.get_channel(track_data[-1].room_id) vc = guild.get_channel(track_data[-1].room_id)
if not vc:
break
perms = vc.permissions_for(guild.me)
if not (perms.connect and perms.speak):
vc = None
break
await lavalink.connect(vc) await lavalink.connect(vc)
player = lavalink.get_player(guild.id) player = lavalink.get_player(guild.id)
player.store("connect", datetime.datetime.utcnow()) player.store("connect", datetime.datetime.utcnow())
@@ -126,8 +131,8 @@ class StartUpTasks(MixinMeta, metaclass=CompositeMetaClass):
track = track.track_object track = track.track_object
player.add(guild.get_member(track.extras.get("requester")) or guild.me, track) player.add(guild.get_member(track.extras.get("requester")) or guild.me, track)
player.maybe_shuffle() player.maybe_shuffle()
if not player.is_playing:
await player.play() await player.play()
except Exception as err: except Exception as err:
debug_exc_log(log, err, f"Error restoring player in {guild_id}") debug_exc_log(log, err, f"Error restoring player in {guild_id}")
await self.api_interface.persistent_queue_api.drop(guild_id) await self.api_interface.persistent_queue_api.drop(guild_id)

View File

@@ -1,6 +1,6 @@
server: server:
host: "localhost" host: "localhost"
port: 2333 # REST server port: 2333 # WS port
lavalink: lavalink:
server: server:
password: "youshallnotpass" password: "youshallnotpass"
@@ -18,7 +18,7 @@ lavalink:
youtubePlaylistLoadLimit: 10000 youtubePlaylistLoadLimit: 10000
logging: logging:
file: file:
max-history: 30 max-history: 7
max-size: 1GB max-size: 1GB
path: ./logs/ path: ./logs/
level: level:

View File

@@ -53,7 +53,7 @@ install_requires =
python-Levenshtein-wheels==0.13.1 python-Levenshtein-wheels==0.13.1
pytz==2020.1 pytz==2020.1
PyYAML==5.3.1 PyYAML==5.3.1
Red-Lavalink==0.6.0 Red-Lavalink==0.7.1
schema==0.7.2 schema==0.7.2
tqdm==4.48.0 tqdm==4.48.0
typing-extensions==3.7.4.2 typing-extensions==3.7.4.2