diff --git a/README.md b/README.md index 3a9ab8f..ec76ead 100644 --- a/README.md +++ b/README.md @@ -14,11 +14,13 @@ Cog Function | exclusiverole | **Alpha** |
Prevent certain roles from getting any other rolesFully functional, but pretty simple
| | fifo | **Alpha** |
Schedule commands to be run at certain times or intervalsJust released, please report bugs as you find them. Only works for bot owner for now
| | fight | **Incomplete** |
Organize bracket tournaments within discordStill in-progress, a massive project
| +| firstmessage | **Release** |
Simple cog to provide a jump link to the first message in a channel/summary>Just released, please report bugs as you find them.
| | flag | **Alpha** |
Create temporary marks on users that expire after specified timePorted, will not import old data. Please report bugs
| | forcemention | **Alpha** |
Mentions unmentionable rolesVery simple cog, mention doesn't persist
| | hangman | **Beta** |
Play a game of hangmanSome visual glitches and needs more customization
| | howdoi | **Incomplete** |
Ask coding questions and get results from StackExchangeNot yet functional
| -| infochannel | **Beta** |
Create a channel to display server infoJust released, please report bugs
| +| infochannel | **Beta** |
Create a channel to display server infoDue to rate limits, this does not update as often as it once did
| +| isitdown | **Beta** |
Check if a website/url is downJust released, please report bugs
| | launchlib | **Beta** |
Access rocket launch dataJust released, please report bugs
| | leaver | **Beta** |
Send a message in a channel when a user leaves the serverSeems to be functional, please report any bugs or suggestions
| | lovecalculator | **Alpha** |
Calculate the love between two users[Snap-Ons] Just updated to V3
| @@ -38,7 +40,7 @@ Cog Function | unicode | **Alpha** |
Encode and Decode unicode characters[Snap-Ons] Just updated to V3
| | werewolf | **Pre-Alpha** |
Play the classic party game Werewolf within discordAnother massive project currently being developed, will be fully customizable
| -Check out my V2 cogs at [Fox-Cogs v2](https://github.com/bobloy/Fox-Cogs) +Check out *Deprecated* my V2 cogs at [Fox-Cogs v2](https://github.com/bobloy/Fox-Cogs) # Installation ### Recommended - Built-in Downloader diff --git a/audiotrivia/audiosession.py b/audiotrivia/audiosession.py index 780d4b9..1bdff02 100644 --- a/audiotrivia/audiosession.py +++ b/audiotrivia/audiosession.py @@ -1,8 +1,13 @@ """Module to manage audio trivia sessions.""" import asyncio +import logging import lavalink +from lavalink.enums import LoadType from redbot.cogs.trivia import TriviaSession +from redbot.core.utils.chat_formatting import bold + +log = logging.getLogger("red.fox_v3.audiotrivia.audiosession") class AudioSession(TriviaSession): @@ -23,9 +28,9 @@ class AudioSession(TriviaSession): async def run(self): """Run the audio trivia session. - In order for the trivia session to be stopped correctly, this should - only be called internally by `TriviaSession.start`. - """ + In order for the trivia session to be stopped correctly, this should + only be called internally by `TriviaSession.start`. + """ await self._send_startup_msg() max_score = self.settings["max_score"] delay = self.settings["delay"] @@ -36,8 +41,9 @@ class AudioSession(TriviaSession): self.count += 1 await self.player.stop() - msg = "**Question number {}!**\n\nName this audio!".format(self.count) - await self.ctx.send(msg) + msg = bold(f"Question number {self.count}!") + "\n\nName this audio!" + await self.ctx.maybe_send_embed(msg) + log.debug(f"Audio question: {question}") # print("Audio question: {}".format(question)) # await self.ctx.invoke(self.audio.play(ctx=self.ctx, query=question)) @@ -45,18 +51,28 @@ class AudioSession(TriviaSession): # await self.ctx.invoke(self.player.play, query=question) query = question.strip("<>") - tracks = await self.player.get_tracks(query) - seconds = tracks[0].length / 1000 + load_result = await self.player.load_tracks(query) + log.debug(f"{load_result.load_type=}") + if load_result.has_error or load_result.load_type != LoadType.TRACK_LOADED: + await self.ctx.maybe_send_embed(f"Track has error, skipping. See logs for details") + log.info(f"Track has error: {load_result.exception_message}") + continue # Skip tracks with error + tracks = load_result.tracks + + track = tracks[0] + seconds = track.length / 1000 if self.settings["repeat"] and seconds < delay: + # Append it until it's longer than the delay tot_length = seconds + 0 while tot_length < delay: - self.player.add(self.ctx.author, tracks[0]) + self.player.add(self.ctx.author, track) tot_length += seconds else: - self.player.add(self.ctx.author, tracks[0]) + self.player.add(self.ctx.author, track) if not self.player.current: + log.debug("Pressing play") await self.player.play() continue_ = await self.wait_for_answer(answers, delay, timeout) diff --git a/audiotrivia/audiotrivia.py b/audiotrivia/audiotrivia.py index c0c88fd..0bab980 100644 --- a/audiotrivia/audiotrivia.py +++ b/audiotrivia/audiotrivia.py @@ -1,4 +1,5 @@ import datetime +import logging import pathlib from typing import List @@ -15,7 +16,7 @@ from redbot.core.utils.chat_formatting import box from .audiosession import AudioSession -# from redbot.cogs.audio.utils import userlimit +log = logging.getLogger("red.fox_v3.audiotrivia") class AudioTrivia(Trivia): @@ -83,24 +84,24 @@ class AudioTrivia(Trivia): self.audio: Audio = self.bot.get_cog("Audio") if self.audio is None: - await ctx.send("Audio is not loaded. Load it and try again") + await ctx.maybe_send_embed("Audio is not loaded. Load it and try again") return categories = [c.lower() for c in categories] session = self._get_trivia_session(ctx.channel) if session is not None: - await ctx.send("There is already an ongoing trivia session in this channel.") + await ctx.maybe_send_embed("There is already an ongoing trivia session in this channel.") return status = await self.audio.config.status() notify = await self.audio.config.guild(ctx.guild).notify() if status: - await ctx.send( + await ctx.maybe_send_embed( f"It is recommended to disable audio status with `{ctx.prefix}audioset status`" ) if notify: - await ctx.send( + await ctx.maybe_send_embed( f"It is recommended to disable audio notify with `{ctx.prefix}audioset notify`" ) @@ -109,12 +110,12 @@ class AudioTrivia(Trivia): if not ctx.author.voice.channel.permissions_for( ctx.me ).connect or self.audio.is_vc_full(ctx.author.voice.channel): - return await ctx.send("I don't have permission to connect to your channel.") + return await ctx.maybe_send_embed("I don't have permission to connect to your channel.") await lavalink.connect(ctx.author.voice.channel) lavaplayer = lavalink.get_player(ctx.guild.id) lavaplayer.store("connect", datetime.datetime.utcnow()) except AttributeError: - return await ctx.send("Connect to a voice channel first.") + return await ctx.maybe_send_embed("Connect to a voice channel first.") lavaplayer = lavalink.get_player(ctx.guild.id) lavaplayer.store("channel", ctx.channel.id) # What's this for? I dunno @@ -122,7 +123,7 @@ class AudioTrivia(Trivia): await self.audio.set_player_settings(ctx) if not ctx.author.voice or ctx.author.voice.channel != lavaplayer.channel: - return await ctx.send( + return await ctx.maybe_send_embed( "You must be in the voice channel to use the audiotrivia command." ) @@ -134,13 +135,13 @@ class AudioTrivia(Trivia): try: dict_ = self.get_audio_list(category) except FileNotFoundError: - await ctx.send( + await ctx.maybe_send_embed( "Invalid category `{0}`. See `{1}audiotrivia list`" " for a list of trivia categories." "".format(category, ctx.prefix) ) except InvalidListError: - await ctx.send( + await ctx.maybe_send_embed( "There was an error parsing the trivia list for" " the `{}` category. It may be formatted" " incorrectly.".format(category) @@ -151,7 +152,7 @@ class AudioTrivia(Trivia): continue return if not trivia_dict: - await ctx.send( + await ctx.maybe_send_embed( "The trivia list was parsed successfully, however it appears to be empty!" ) return @@ -166,7 +167,10 @@ class AudioTrivia(Trivia): # Delay in audiosettings overwrites delay in settings combined_settings = {**settings, **audiosettings} session = AudioSession.start( - ctx=ctx, question_list=trivia_dict, settings=combined_settings, player=lavaplayer, + ctx=ctx, + question_list=trivia_dict, + settings=combined_settings, + player=lavaplayer, ) self.trivia_sessions.append(session) LOG.debug("New audio trivia session; #%s in %d", ctx.channel, ctx.guild.id) diff --git a/audiotrivia/data/lists/games.yaml b/audiotrivia/data/lists/games.yaml index c3a9078..4de795a 100644 --- a/audiotrivia/data/lists/games.yaml +++ b/audiotrivia/data/lists/games.yaml @@ -1,13 +1,13 @@ AUTHOR: Plab -https://www.youtube.com/watch?v=--bWm9hhoZo: +https://www.youtube.com/watch?v=f9O2Rjn1azc: - Transistor -https://www.youtube.com/watch?v=-4nCbgayZNE: +https://www.youtube.com/watch?v=PgUhYFkVdSY: - Dark Cloud 2 - Dark Cloud II -https://www.youtube.com/watch?v=-64NlME4lJU: +https://www.youtube.com/watch?v=1T1RZttyMwU: - Mega Man 7 - Mega Man VII -https://www.youtube.com/watch?v=-AesqnudNuw: +https://www.youtube.com/watch?v=AdDbbzuq1vY: - Mega Man 9 - Mega Man IX https://www.youtube.com/watch?v=-BmGDtP2t7M: diff --git a/ccrole/ccrole.py b/ccrole/ccrole.py index 190050d..eb654b1 100644 --- a/ccrole/ccrole.py +++ b/ccrole/ccrole.py @@ -7,6 +7,7 @@ from discord.ext.commands.view import StringView from redbot.core import Config, checks, commands from redbot.core.bot import Red from redbot.core.utils.chat_formatting import box, pagify +from redbot.core.utils.mod import get_audit_reason log = logging.getLogger("red.fox_v3.ccrole") @@ -358,23 +359,24 @@ class CCRole(commands.Cog): else: target = message.author + reason = get_audit_reason(message.author) + if cmd["aroles"]: arole_list = [ discord.utils.get(message.guild.roles, id=roleid) for roleid in cmd["aroles"] ] try: - await target.add_roles(*arole_list) + await target.add_roles(*arole_list, reason=reason) except discord.Forbidden: log.exception(f"Permission error: Unable to add roles") await ctx.send("Permission error: Unable to add roles") - await asyncio.sleep(1) if cmd["rroles"]: rrole_list = [ discord.utils.get(message.guild.roles, id=roleid) for roleid in cmd["rroles"] ] try: - await target.remove_roles(*rrole_list) + await target.remove_roles(*rrole_list, reason=reason) except discord.Forbidden: log.exception(f"Permission error: Unable to remove roles") await ctx.send("Permission error: Unable to remove roles") diff --git a/chatter/README.md b/chatter/README.md index e8c03d6..8ef6734 100644 --- a/chatter/README.md +++ b/chatter/README.md @@ -29,7 +29,7 @@ Chatter by default uses spaCy's `en_core_web_md` training model, which is ~50 MB Chatter can potential use spaCy's `en_core_web_lg` training model, which is ~800 MB -Chatter uses as sqlite database that can potentially take up a large amount os disk space, +Chatter uses as sqlite database that can potentially take up a large amount of disk space, depending on how much training Chatter has done. The sqlite database can be safely deleted at any time. Deletion will only erase training data. @@ -50,7 +50,9 @@ Linux is a bit easier, but only tested on Debian and Ubuntu. ## Windows Prerequisites -Install these on your windows machine before attempting the installation +**Requires 64 Bit Python to continue on Windows.** + +Install these on your windows machine before attempting the installation: [Visual Studio C++ Build Tools](https://visualstudio.microsoft.com/visual-cpp-build-tools/) @@ -83,6 +85,7 @@ pip install --no-deps "chatterbot>=1.1" #### Step 3: Load Chatter ``` +[p]repo add Fox https://github.com/bobloy/Fox-V3 # If you didn't already do this in step 1 [p]cog install Fox chatter [p]load chatter ``` @@ -92,7 +95,7 @@ pip install --no-deps "chatterbot>=1.1" #### Step 1: Built-in Downloader ``` -[p]cog install Chatter +[p]cog install Chatter ``` #### Step 2: Install Requirements diff --git a/chatter/chat.py b/chatter/chat.py index a81e669..ad8e37b 100644 --- a/chatter/chat.py +++ b/chatter/chat.py @@ -463,7 +463,7 @@ class Chatter(Cog): # Thank you Cog-Creators channel: discord.TextChannel = message.channel - if channel.id == await self.config.guild(guild).chatchannel(): + if guild is not None and channel.id == await self.config.guild(guild).chatchannel(): pass # good to go else: when_mentionables = commands.when_mentioned(self.bot, message) diff --git a/fifo/fifo.py b/fifo/fifo.py index 91991b0..30346d7 100644 --- a/fifo/fifo.py +++ b/fifo/fifo.py @@ -10,6 +10,7 @@ from apscheduler.schedulers.base import STATE_PAUSED, STATE_RUNNING from redbot.core import Config, checks, commands from redbot.core.bot import Red from redbot.core.commands import TimedeltaConverter +from redbot.core.utils.chat_formatting import pagify from .datetime_cron_converters import CronConverter, DatetimeConverter, TimezoneConverter from .task import Task @@ -306,7 +307,11 @@ class FIFO(commands.Cog): out += f"{task_name}: {task_data}\n" if out: - await ctx.maybe_send_embed(out) + if len(out) > 2000: + for page in pagify(out): + await ctx.maybe_send_embed(page) + else: + await ctx.maybe_send_embed(out) else: await ctx.maybe_send_embed("No tasks to list") diff --git a/fifo/info.json b/fifo/info.json index c9a80dd..dda63ce 100644 --- a/fifo/info.json +++ b/fifo/info.json @@ -16,6 +16,7 @@ "bobloy", "utilities", "tool", + "tools", "roles", "schedule", "cron", diff --git a/firstmessage/__init__.py b/firstmessage/__init__.py new file mode 100644 index 0000000..4dab2ed --- /dev/null +++ b/firstmessage/__init__.py @@ -0,0 +1,5 @@ +from .firstmessage import FirstMessage + + +async def setup(bot): + bot.add_cog(FirstMessage(bot)) diff --git a/firstmessage/firstmessage.py b/firstmessage/firstmessage.py new file mode 100644 index 0000000..f13bd60 --- /dev/null +++ b/firstmessage/firstmessage.py @@ -0,0 +1,49 @@ +import logging + +import discord +from redbot.core import Config, commands +from redbot.core.bot import Red + +log = logging.getLogger("red.fox_v3.firstmessage") + + +class FirstMessage(commands.Cog): + """ + Provides a link to the first message in the provided channel + """ + + def __init__(self, bot: Red): + super().__init__() + self.bot = bot + self.config = Config.get_conf( + self, identifier=701051141151167710111511597103101, force_registration=True + ) + + default_guild = {} + + self.config.register_guild(**default_guild) + + async def red_delete_data_for_user(self, **kwargs): + """Nothing to delete""" + return + + @commands.command() + async def firstmessage(self, ctx: commands.Context, channel: discord.TextChannel = None): + """ + Provide a link to the first message in current or provided channel. + """ + if channel is None: + channel = ctx.channel + try: + message: discord.Message = ( + await channel.history(limit=1, oldest_first=True).flatten() + )[0] + except (discord.Forbidden, discord.HTTPException): + log.exception(f"Unable to read message history for {channel.id=}") + await ctx.maybe_send_embed("Unable to read message history for that channel") + return + + em = discord.Embed(description=f"[First Message in {channel.mention}]({message.jump_url})") + em.set_author(name=message.author.display_name, icon_url=message.author.avatar_url) + + await ctx.send(embed=em) diff --git a/firstmessage/info.json b/firstmessage/info.json new file mode 100644 index 0000000..f656d32 --- /dev/null +++ b/firstmessage/info.json @@ -0,0 +1,17 @@ +{ + "author": [ + "Bobloy" + ], + "min_bot_version": "3.4.0", + "description": "Simple cog to jump to the first message of a channel easily", + "hidden": false, + "install_msg": "Thank you for installing FirstMessage.\nGet started with `[p]load firstmessage`, then `[p]help FirstMessage`", + "short": "Simple cog to jump to first message of a channel", + "end_user_data_statement": "This cog does not store any End User Data", + "tags": [ + "bobloy", + "utilities", + "tool", + "tools" + ] +} \ No newline at end of file diff --git a/hangman/__init__.py b/hangman/__init__.py index dbc62e7..35012c4 100644 --- a/hangman/__init__.py +++ b/hangman/__init__.py @@ -6,4 +6,3 @@ def setup(bot): n = Hangman(bot) data_manager.bundled_data_path(n) bot.add_cog(n) - bot.add_listener(n.on_react, "on_reaction_add") diff --git a/hangman/hangman.py b/hangman/hangman.py index c7d005d..2b6ab07 100644 --- a/hangman/hangman.py +++ b/hangman/hangman.py @@ -50,27 +50,27 @@ class Hangman(Cog): theface = await self.config.guild(guild).theface() self.hanglist[guild] = ( """> - \_________ + \\_________ |/ | | | | | - |\___ + |\\___ """, """> - \_________ + \\_________ |/ | | | | | | - |\___ + |\\___ H""", """> - \_________ + \\_________ |/ | | """ + theface @@ -79,10 +79,10 @@ class Hangman(Cog): | | | - |\___ + |\\___ HA""", """> - \________ + \\________ |/ | | """ + theface @@ -91,10 +91,10 @@ class Hangman(Cog): | | | | - |\___ + |\\___ HAN""", """> - \_________ + \\_________ |/ | | """ + theface @@ -103,43 +103,43 @@ class Hangman(Cog): | | | | - |\___ + |\\___ HANG""", """> - \_________ + \\_________ |/ | | """ + theface + """ - | /|\ + | /|\\ | | | | - |\___ + |\\___ HANGM""", """> - \________ + \\________ |/ | | """ + theface + """ - | /|\ + | /|\\ | | | / | - |\___ + |\\___ HANGMA""", """> - \________ + \\________ |/ | | """ + theface + """ - | /|\ + | /|\\ | | - | / \ + | / \\ | - |\___ + |\\___ HANGMAN""", ) @@ -255,7 +255,7 @@ class Hangman(Cog): elif i in self.the_data[guild]["guesses"] or i not in "ABCDEFGHIJKLMNOPQRSTUVWXYZ": out_str += "__" + i + "__ " else: - out_str += "**\_** " + out_str += "**\\_** " self.winbool[guild] = False return out_str @@ -286,10 +286,10 @@ class Hangman(Cog): await self._reprintgame(message) - @commands.Cog.listener() + @commands.Cog.listener("on_reaction_add") async def on_react(self, reaction, user: Union[discord.User, discord.Member]): - """ Thanks to flapjack reactpoll for guidelines - https://github.com/flapjax/FlapJack-Cogs/blob/master/reactpoll/reactpoll.py""" + """Thanks to flapjack reactpoll for guidelines + https://github.com/flapjax/FlapJack-Cogs/blob/master/reactpoll/reactpoll.py""" guild: discord.Guild = getattr(user, "guild", None) if guild is None: return diff --git a/isitdown/__init__.py b/isitdown/__init__.py new file mode 100644 index 0000000..fdebc2a --- /dev/null +++ b/isitdown/__init__.py @@ -0,0 +1,5 @@ +from .isitdown import IsItDown + + +async def setup(bot): + bot.add_cog(IsItDown(bot)) diff --git a/isitdown/info.json b/isitdown/info.json new file mode 100644 index 0000000..d321732 --- /dev/null +++ b/isitdown/info.json @@ -0,0 +1,17 @@ +{ + "author": [ + "Bobloy" + ], + "min_bot_version": "3.4.0", + "description": "Check if a website/url is down using the https://isitdown.site/ api", + "hidden": false, + "install_msg": "Thank you for installing IsItDown.\nGet started with `[p]load isitdown`, then `[p]help IsItDown`", + "short": "Check if a website/url is down", + "end_user_data_statement": "This cog does not store any End User Data", + "tags": [ + "bobloy", + "utilities", + "tool", + "tools" + ] +} \ No newline at end of file diff --git a/isitdown/isitdown.py b/isitdown/isitdown.py new file mode 100644 index 0000000..f786928 --- /dev/null +++ b/isitdown/isitdown.py @@ -0,0 +1,58 @@ +import logging +import re + +import aiohttp +from redbot.core import Config, commands +from redbot.core.bot import Red + +log = logging.getLogger("red.fox_v3.isitdown") + + +class IsItDown(commands.Cog): + """ + Cog Description + + Less important information about the cog + """ + + def __init__(self, bot: Red): + super().__init__() + self.bot = bot + self.config = Config.get_conf(self, identifier=0, force_registration=True) + + default_guild = {"iids": []} # List of tuple pairs (channel_id, website) + + self.config.register_guild(**default_guild) + + async def red_delete_data_for_user(self, **kwargs): + """Nothing to delete""" + return + + @commands.command(alias=["iid"]) + async def isitdown(self, ctx: commands.Context, url_to_check): + """ + Check if the provided url is down + + Alias: iid + """ + try: + resp = await self._check_if_down(url_to_check) + except AssertionError: + await ctx.maybe_send_embed("Invalid URL provided. Make sure not to include `http://`") + return + + if resp["isitdown"]: + await ctx.maybe_send_embed(f"{url_to_check} is DOWN!") + else: + await ctx.maybe_send_embed(f"{url_to_check} is UP!") + + async def _check_if_down(self, url_to_check): + url = re.compile(r"https?://(www\.)?") + url.sub("", url_to_check).strip().strip("/") + + url = f"https://isitdown.site/api/v3/{url}" + async with aiohttp.ClientSession() as session: + async with session.get(url) as response: + assert response.status == 200 + resp = await response.json() + return resp diff --git a/launchlib/launchlib.py b/launchlib/launchlib.py index e34a23d..ae870fd 100644 --- a/launchlib/launchlib.py +++ b/launchlib/launchlib.py @@ -22,7 +22,9 @@ class LaunchLib(commands.Cog): def __init__(self, bot: Red): super().__init__() self.bot = bot - self.config = Config.get_conf(self, identifier=0, force_registration=True) + self.config = Config.get_conf( + self, identifier=7697117110991047610598, force_registration=True + ) default_guild = {} diff --git a/lseen/lseen.py b/lseen/lseen.py index a451f57..3348b65 100644 --- a/lseen/lseen.py +++ b/lseen/lseen.py @@ -75,9 +75,7 @@ class LastSeen(Cog): else: last_seen = await self.config.member(member).seen() if last_seen is None: - await ctx.maybe_send_embed( - embed=discord.Embed(description="I've never seen this user") - ) + await ctx.maybe_send_embed("I've never seen this user") return last_seen = self.get_date_time(last_seen) diff --git a/planttycoon/planttycoon.py b/planttycoon/planttycoon.py index 61e5e06..665fc9a 100644 --- a/planttycoon/planttycoon.py +++ b/planttycoon/planttycoon.py @@ -360,7 +360,9 @@ class PlantTycoon(commands.Cog): ``{0}prune``: Prune your plant.\n""" em = discord.Embed( - title=title, description=description.format(prefix), color=discord.Color.green(), + title=title, + description=description.format(prefix), + color=discord.Color.green(), ) em.set_thumbnail(url="https://image.prntscr.com/image/AW7GuFIBSeyEgkR2W3SeiQ.png") em.set_footer( @@ -525,7 +527,8 @@ class PlantTycoon(commands.Cog): if t: em = discord.Embed( - title="Plant statistics of {}".format(plant["name"]), color=discord.Color.green(), + title="Plant statistics of {}".format(plant["name"]), + color=discord.Color.green(), ) em.set_thumbnail(url=plant["image"]) em.add_field(name="**Name**", value=plant["name"]) @@ -583,7 +586,8 @@ class PlantTycoon(commands.Cog): author = ctx.author if product is None: em = discord.Embed( - title="All gardening supplies that you can buy:", color=discord.Color.green(), + title="All gardening supplies that you can buy:", + color=discord.Color.green(), ) for pd in self.products: em.add_field( @@ -616,8 +620,11 @@ class PlantTycoon(commands.Cog): await gardener.save_gardener() message = "You bought {}.".format(product.lower()) else: - message = "You don't have enough Thneeds. You have {}, but need {}.".format( - gardener.points, self.products[product.lower()]["cost"] * amount, + message = ( + "You don't have enough Thneeds. You have {}, but need {}.".format( + gardener.points, + self.products[product.lower()]["cost"] * amount, + ) ) else: message = "I don't have this product." diff --git a/reactrestrict/reactrestrict.py b/reactrestrict/reactrestrict.py index 4030538..79c3c1c 100644 --- a/reactrestrict/reactrestrict.py +++ b/reactrestrict/reactrestrict.py @@ -1,3 +1,4 @@ +import logging from typing import List, Union import discord @@ -5,6 +6,8 @@ from redbot.core import Config, commands from redbot.core.bot import Red from redbot.core.commands import Cog +log = logging.getLogger("red.fox_v3.reactrestrict") + class ReactRestrictCombo: def __init__(self, message_id, role_id): @@ -131,10 +134,12 @@ class ReactRestrict(Cog): If no such channel or member can be found. """ channel = self.bot.get_channel(channel_id) + if channel is None: + raise LookupError("no channel found.") try: member = channel.guild.get_member(user_id) except AttributeError as e: - raise LookupError("No channel found.") from e + raise LookupError("No member found.") from e if member is None: raise LookupError("No member found.") @@ -168,7 +173,7 @@ class ReactRestrict(Cog): """ channel = self.bot.get_channel(channel_id) try: - return await channel.get_message(message_id) + return await channel.fetch_message(message_id) except discord.NotFound: pass except AttributeError: # VoiceChannel object has no attribute 'get_message' @@ -186,9 +191,11 @@ class ReactRestrict(Cog): :param message_id: :return: """ - for channel in ctx.guild.channels: + + guild: discord.Guild = ctx.guild + for channel in guild.text_channels: try: - return await channel.get_message(message_id) + return await channel.fetch_message(message_id) except discord.NotFound: pass except AttributeError: # VoiceChannel object has no attribute 'get_message' @@ -232,7 +239,7 @@ class ReactRestrict(Cog): # noinspection PyTypeChecker await self.add_reactrestrict(message_id, role) - await ctx.maybe_send_embed("Message|Role combo added.") + await ctx.maybe_send_embed("Message|Role restriction added.") @reactrestrict.command() async def remove(self, ctx: commands.Context, message_id: int, role: discord.Role): @@ -248,37 +255,38 @@ class ReactRestrict(Cog): # noinspection PyTypeChecker await self.remove_react(message_id, role) - await ctx.send("Reaction removed.") + await ctx.send("React restriction removed.") @commands.Cog.listener() - async def on_raw_reaction_add( - self, emoji: discord.PartialEmoji, message_id: int, channel_id: int, user_id: int - ): + async def on_raw_reaction_add(self, payload: discord.RawReactionActionEvent): """ Event handler for long term reaction watching. - - :param discord.PartialReactionEmoji emoji: - :param int message_id: - :param int channel_id: - :param int user_id: - :return: """ - if emoji.is_custom_emoji(): - emoji_id = emoji.id - else: - emoji_id = emoji.name + + emoji = payload.emoji + message_id = payload.message_id + channel_id = payload.channel_id + user_id = payload.user_id + + # if emoji.is_custom_emoji(): + # emoji_id = emoji.id + # else: + # emoji_id = emoji.name has_reactrestrict, combos = await self.has_reactrestrict_combo(message_id) if not has_reactrestrict: + log.debug("Message not react restricted") return try: member = self._get_member(channel_id, user_id) except LookupError: + log.exception("Unable to get member from guild") return if member.bot: + log.debug("Won't remove reactions added by bots") return if await self.bot.cog_disabled_in_guild(self, member.guild): @@ -287,14 +295,19 @@ class ReactRestrict(Cog): try: roles = [self._get_role(member.guild, c.role_id) for c in combos] except LookupError: + log.exception("Couldn't get approved roles from combos") return for apprrole in roles: if apprrole in member.roles: + log.debug("Has approved role") return message = await self._get_message_from_channel(channel_id, message_id) - await message.remove_reaction(emoji, member) + try: + await message.remove_reaction(emoji, member) + except (discord.Forbidden, discord.NotFound, discord.HTTPException): + log.exception("Unable to remove reaction") # try: # await member.add_roles(*roles)