Repository: Cog-Creators/Red-DiscordBot

Issue:
[Audio] Ensure commands that need the voice channel can't be run by a user not in the voice channel
FFS Draper you dumb son ...

Traceback (most recent call last):
  File "/home/martinebots/martinebotsvenv/lib/python3.7/site-packages/discord/ext/commands/core.py", line 79, in wrapped
    ret = await coro(*args, **kwargs)
  File "/home/martinebots/martinebotscogs/cogs_modified/audio/audio.py", line 6455, in seek
    is_alone = await self._is_alone(ctx, ctx.author)
  File "/home/martinebots/martinebotscogs/cogs_modified/audio/audio.py", line 6705, in _is_alone
    elif ctx.guild.get_member(member.id).voice.channel.members == 1:
AttributeError: 'NoneType' object has no attribute 'channel'

Note: Making this so i dont forget to fix it in a couple of hours


Discussion:

Resolving patch:
diff --git a/redbot/cogs/audio/audio.py b/redbot/cogs/audio/audio.py
--- a/redbot/cogs/audio/audio.py
+++ b/redbot/cogs/audio/audio.py
@@ -5,7 +5,7 @@
 import heapq
 import json
 import logging
-import os
+import math
 import random
 import re
 import time
@@ -17,7 +17,6 @@
 import aiohttp
 import discord
 import lavalink
-import math
 from fuzzywuzzy import process
 
 import redbot.core
@@ -34,8 +33,9 @@
     start_adding_reactions,
 )
 from redbot.core.utils.predicates import MessagePredicate, ReactionPredicate
+
 from . import audio_dataclasses
-from .apis import MusicCache, HAS_SQL, _ERROR
+from .apis import _ERROR, HAS_SQL, MusicCache
 from .checks import can_have_caching
 from .converters import ComplexScopeParser, ScopeParser, get_lazy_converter, get_playlist_converter
 from .equalizer import Equalizer
@@ -51,8 +51,26 @@
     get_playlist,
     humanize_scope,
 )
-from .utils import *
-
+from .utils import (
+    CacheLevel,
+    Notifier,
+    clear_react,
+    draw_time,
+    dynamic_time,
+    get_description,
+    is_allowed,
+    match_url,
+    match_yt_playlist,
+    pass_config_to_dependencies,
+    queue_duration,
+    remove_react,
+    rgetattr,
+    time_convert,
+    track_creator,
+    track_limit,
+    url_check,
+    userlimit,
+)
 
 _ = Translator("Audio", __file__)
 
@@ -1654,9 +1672,7 @@ async def disconnect(self, ctx: commands.Context):
             if dj_enabled:
                 if not await self._can_instaskip(ctx, ctx.author):
                     return await self._embed_msg(ctx, _("You need the DJ role to disconnect."))
-            if not await self._can_instaskip(ctx, ctx.author) and not await self._is_alone(
-                ctx, ctx.author
-            ):
+            if not await self._can_instaskip(ctx, ctx.author) and not await self._is_alone(ctx):
                 return await self._embed_msg(ctx, _("There are other people listening to music."))
             else:
                 await self._embed_msg(ctx, _("Disconnecting..."))
@@ -2255,9 +2271,7 @@ async def now(self, ctx: commands.Context):
         dj_enabled = await self.config.guild(ctx.guild).dj_enabled()
         vote_enabled = await self.config.guild(ctx.guild).vote_enabled()
         if dj_enabled or vote_enabled:
-            if not await self._can_instaskip(ctx, ctx.author) and not await self._is_alone(
-                ctx, ctx.author
-            ):
+            if not await self._can_instaskip(ctx, ctx.author) and not await self._is_alone(ctx):
                 return
 
         if player.current:
@@ -2307,9 +2321,7 @@ async def pause(self, ctx: commands.Context):
                 ctx, _("You must be in the voice channel pause or resume.")
             )
         if dj_enabled:
-            if not await self._can_instaskip(ctx, ctx.author) and not await self._is_alone(
-                ctx, ctx.author
-            ):
+            if not await self._can_instaskip(ctx, ctx.author) and not await self._is_alone(ctx):
                 return await self._embed_msg(
                     ctx, _("You need the DJ role to pause or resume tracks.")
                 )
@@ -5175,9 +5187,7 @@ async def prev(self, ctx: commands.Context):
         dj_enabled = await self.config.guild(ctx.guild).dj_enabled()
         player = lavalink.get_player(ctx.guild.id)
         if dj_enabled:
-            if not await self._can_instaskip(ctx, ctx.author) and not await self._is_alone(
-                ctx, ctx.author
-            ):
+            if not await self._can_instaskip(ctx, ctx.author) and not await self._is_alone(ctx):
                 return await self._embed_msg(ctx, _("You need the DJ role to skip tracks."))
         if (
             not ctx.author.voice or ctx.author.voice.channel != player.channel
@@ -5495,10 +5505,9 @@ async def _queue_clear(self, ctx: commands.Context):
         dj_enabled = await self.config.guild(ctx.guild).dj_enabled()
         if not self._player_check(ctx) or not player.queue:
             return await self._embed_msg(ctx, _("There's nothing in the queue."))
+
         if dj_enabled:
-            if not await self._can_instaskip(ctx, ctx.author) and not await self._is_alone(
-                ctx, ctx.author
-            ):
+            if not await self._can_instaskip(ctx, ctx.author) and not await self._is_alone(ctx):
                 return await self._embed_msg(ctx, _("You need the DJ role to clear the queue."))
         player.queue.clear()
         await self._embed_msg(ctx, _("The queue has been cleared."))
@@ -5515,9 +5524,7 @@ async def _queue_clean(self, ctx: commands.Context):
         if not self._player_check(ctx) or not player.queue:
             return await self._embed_msg(ctx, _("There's nothing in the queue."))
         if dj_enabled:
-            if not await self._can_instaskip(ctx, ctx.author) and not await self._is_alone(
-                ctx, ctx.author
-            ):
+            if not await self._can_instaskip(ctx, ctx.author) and not await self._is_alone(ctx):
                 return await self._embed_msg(ctx, _("You need the DJ role to clean the queue."))
         clean_tracks = []
         removed_tracks = 0
@@ -5597,10 +5604,8 @@ async def _queue_shuffle(self, ctx: commands.Context):
         """Shuffles the queue."""
         dj_enabled = await self.config.guild(ctx.guild).dj_enabled()
         if dj_enabled:
-            if not await self._can_instaskip(ctx, ctx.author) and not await self._is_alone(
-                ctx, ctx.author
-            ):
-                return await self._embed_msg(ctx, _("You need the DJ role to clean the queue."))
+            if not await self._can_instaskip(ctx, ctx.author) and not await self._is_alone(ctx):
+                return await self._embed_msg(ctx, _("You need the DJ role to shuffle the queue."))
         if not self._player_check(ctx):
             return await self._embed_msg(ctx, _("There's nothing in the queue."))
         try:
@@ -6063,7 +6068,7 @@ async def seek(self, ctx: commands.Context, seconds: Union[int, str]):
         Accepts seconds or a value formatted like 00:00:00 (`hh:mm:ss`) or 00:00 (`mm:ss`)."""
         dj_enabled = await self.config.guild(ctx.guild).dj_enabled()
         vote_enabled = await self.config.guild(ctx.guild).vote_enabled()
-        is_alone = await self._is_alone(ctx, ctx.author)
+        is_alone = await self._is_alone(ctx)
         is_requester = await self.is_requester(ctx, ctx.author)
         can_skip = await self._can_instaskip(ctx, ctx.author)
 
@@ -6185,10 +6190,9 @@ async def skip(self, ctx: commands.Context, skip_to_track: int = None):
             return await self._embed_msg(ctx, _("Nothing playing."))
         dj_enabled = await self.config.guild(ctx.guild).dj_enabled()
         vote_enabled = await self.config.guild(ctx.guild).vote_enabled()
-        is_alone = await self._is_alone(ctx, ctx.author)
+        is_alone = await self._is_alone(ctx)
         is_requester = await self.is_requester(ctx, ctx.author)
         can_skip = await self._can_instaskip(ctx, ctx.author)
-
         if dj_enabled and not vote_enabled:
             if not (can_skip or is_requester) and not is_alone:
                 return await self._embed_msg(
@@ -6201,7 +6205,6 @@ async def skip(self, ctx: commands.Context, skip_to_track: int = None):
                 and skip_to_track > 1
             ):
                 return await self._embed_msg(ctx, _("You can only skip the current track."))
-
         if vote_enabled:
             if not can_skip:
                 if skip_to_track is not None:
@@ -6269,40 +6272,21 @@ async def _can_instaskip(self, ctx: commands.Context, member: discord.Member):
 
         return False
 
-    async def _is_alone(self, ctx: commands.Context, member: discord.Member):
-        try:
-            user_voice = ctx.guild.get_member(member.id).voice
-            bot_voice = ctx.guild.get_member(self.bot.user.id).voice
-            nonbots = sum(not m.bot for m in user_voice.channel.members)
-            if user_voice.channel != bot_voice.channel:
-                nonbots = nonbots + 1
-        except AttributeError:
-            if ctx.guild.get_member(self.bot.user.id).voice is not None:
-                nonbots = sum(
-                    not m.bot for m in ctx.guild.get_member(self.bot.user.id).voice.channel.members
-                )
-                if nonbots == 1:
-                    nonbots = 2
-            elif ctx.guild.get_member(member.id).voice.channel.members == 1:
-                nonbots = 1
-            else:
-                nonbots = 0
-        return nonbots <= 1
+    async def _is_alone(self, ctx: commands.Context):
+        channel_members = rgetattr(ctx, "guild.me.voice.channel.members", [])
+        nonbots = sum(m.id != ctx.author.id for m in channel_members if not m.bot)
+        return nonbots < 1
 
     async def _has_dj_role(self, ctx: commands.Context, member: discord.Member):
         dj_role_obj = ctx.guild.get_role(await self.config.guild(ctx.guild).dj_role())
-        if dj_role_obj in ctx.guild.get_member(member.id).roles:
-            return True
-        return False
+        return dj_role_obj in ctx.guild.get_member(member.id).roles
 
     @staticmethod
     async def is_requester(ctx: commands.Context, member: discord.Member):
         try:
             player = lavalink.get_player(ctx.guild.id)
             log.debug(f"Current requester is {player.current}")
-            if player.current.requester.id == member.id:
-                return True
-            return False
+            return player.current.requester.id == member.id
         except Exception as e:
             log.error(e)
         return False
@@ -6395,9 +6379,7 @@ async def stop(self, ctx: commands.Context):
                 ctx, _("You must be in the voice channel to stop the music.")
             )
         if vote_enabled or vote_enabled and dj_enabled:
-            if not await self._can_instaskip(ctx, ctx.author) and not await self._is_alone(
-                ctx, ctx.author
-            ):
+            if not await self._can_instaskip(ctx, ctx.author) and not await self._is_alone(ctx):
                 return await self._embed_msg(
                     ctx, _("There are other people listening - vote to skip instead.")
                 )
@@ -6727,10 +6709,7 @@ async def disconnect_timer(self):
                             log.error(
                                 "Exception raised in Audio's emptypause_timer.", exc_info=True
                             )
-                        finally:
-                            pause_times.pop(server.id, None)
-                    else:
-                        pause_times.pop(server.id, None)
+                    pause_times.pop(server.id, None)
             servers = stop_times.copy()
             servers.update(pause_times)
             for sid in servers:
diff --git a/redbot/cogs/audio/utils.py b/redbot/cogs/audio/utils.py
--- a/redbot/cogs/audio/utils.py
+++ b/redbot/cogs/audio/utils.py
@@ -1,5 +1,6 @@
 import asyncio
 import contextlib
+import functools
 import os
 import re
 import time
@@ -10,10 +11,9 @@
 
 from redbot.core import Config, commands
 from redbot.core.bot import Red
-from . import audio_dataclasses
 
+from . import audio_dataclasses
 from .converters import _pass_config_to_converters
-
 from .playlists import _pass_config_to_playlist
 
 __all__ = [
@@ -32,6 +32,7 @@
     "url_check",
     "userlimit",
     "is_allowed",
+    "rgetattr",
     "CacheLevel",
     "Notifier",
 ]
@@ -240,6 +241,18 @@ def userlimit(channel):
     return True
 
 
+def rsetattr(obj, attr, val):
+    pre, _, post = attr.rpartition(".")
+    return setattr(rgetattr(obj, pre) if pre else obj, post, val)
+
+
+def rgetattr(obj, attr, *args):
+    def _getattr(obj2, attr2):
+        return getattr(obj2, attr2, *args)
+
+    return functools.reduce(_getattr, [obj] + attr.split("."))
+
+
 class CacheLevel:
     __slots__ = ("value",)
 


Test patch: