From ecfe6bc1b89632c1161f5aad83a08a8459f8f13a Mon Sep 17 00:00:00 2001 From: Numerlor <25886452+Numerlor@users.noreply.github.com> Date: Tue, 7 Sep 2021 00:32:12 +0200 Subject: Add selective trace loggers Add selective enabling of trace loggers as described in python-discord/bot#1529 --- bot/constants.py | 1 + 1 file changed, 1 insertion(+) (limited to 'bot/constants.py') diff --git a/bot/constants.py b/bot/constants.py index 2313bfdb..ae6e02c1 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -138,6 +138,7 @@ class Client(NamedTuple): github_bot_repo = "https://github.com/python-discord/sir-lancebot" # Override seasonal locks: 1 (January) to 12 (December) month_override = int(environ["MONTH_OVERRIDE"]) if "MONTH_OVERRIDE" in environ else None + trace_loggers = environ.get("BOT_TRACE_LOGGERS") class Colours: -- cgit v1.2.3 From b0e9ffb94f05e6ef7619b7440e00363e10928932 Mon Sep 17 00:00:00 2001 From: Objectivitix <79152594+Objectivitix@users.noreply.github.com> Date: Sun, 26 Sep 2021 18:24:30 -0300 Subject: Allow everyone to use the `.bm` command everywhere (#885) * Allow everyone to use the bm command * Add everyone role in Roles constants * Use envvars and re-order Roles section to be more organized * Fix trailing whitespace We might need to squash merge, four commits for a single small fix is too much --- bot/constants.py | 3 ++- bot/exts/utilities/bookmark.py | 5 ++--- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'bot/constants.py') diff --git a/bot/constants.py b/bot/constants.py index 2313bfdb..6e45632f 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -280,11 +280,12 @@ if Client.month_override is not None: class Roles(NamedTuple): + owner = 267627879762755584 admin = int(environ.get("BOT_ADMIN_ROLE_ID", 267628507062992896)) moderator = 267629731250176001 - owner = 267627879762755584 helpers = int(environ.get("ROLE_HELPERS", 267630620367257601)) core_developers = 587606783669829632 + everyone = int(environ.get("BOT_GUILD", 267624335836053506)) class Tokens(NamedTuple): diff --git a/bot/exts/utilities/bookmark.py b/bot/exts/utilities/bookmark.py index a91ef1c0..39d65168 100644 --- a/bot/exts/utilities/bookmark.py +++ b/bot/exts/utilities/bookmark.py @@ -7,7 +7,7 @@ import discord from discord.ext import commands from bot.bot import Bot -from bot.constants import Categories, Colours, ERROR_REPLIES, Icons, WHITELISTED_CHANNELS +from bot.constants import Colours, ERROR_REPLIES, Icons, Roles from bot.utils.converters import WrappedMessageConverter from bot.utils.decorators import whitelist_override @@ -16,7 +16,6 @@ log = logging.getLogger(__name__) # Number of seconds to wait for other users to bookmark the same message TIMEOUT = 120 BOOKMARK_EMOJI = "πŸ“Œ" -WHITELISTED_CATEGORIES = (Categories.help_in_use,) class Bookmark(commands.Cog): @@ -87,8 +86,8 @@ class Bookmark(commands.Cog): await message.add_reaction(BOOKMARK_EMOJI) return message - @whitelist_override(channels=WHITELISTED_CHANNELS, categories=WHITELISTED_CATEGORIES) @commands.command(name="bookmark", aliases=("bm", "pin")) + @whitelist_override(roles=(Roles.everyone,)) async def bookmark( self, ctx: commands.Context, -- cgit v1.2.3 From 11d00e7f846748fb87f02b5f0bfecc92feac198c Mon Sep 17 00:00:00 2001 From: Izan Date: Fri, 8 Oct 2021 14:45:02 +0100 Subject: Rename `Roles.moderator` to `Roles.moderation_team` --- bot/constants.py | 4 ++-- bot/exts/fun/snakes/_utils.py | 2 +- bot/exts/fun/trivia_quiz.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) (limited to 'bot/constants.py') diff --git a/bot/constants.py b/bot/constants.py index 6e45632f..c5443393 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -332,8 +332,8 @@ class Reddit: # Default role combinations -MODERATION_ROLES = Roles.moderator, Roles.admin, Roles.owner -STAFF_ROLES = Roles.helpers, Roles.moderator, Roles.admin, Roles.owner +MODERATION_ROLES = Roles.moderation_team, Roles.admin, Roles.owner +STAFF_ROLES = Roles.helpers, Roles.moderation_team, Roles.admin, Roles.owner # Whitelisted channels WHITELISTED_CHANNELS = ( diff --git a/bot/exts/fun/snakes/_utils.py b/bot/exts/fun/snakes/_utils.py index de51339d..46834c2c 100644 --- a/bot/exts/fun/snakes/_utils.py +++ b/bot/exts/fun/snakes/_utils.py @@ -718,4 +718,4 @@ class SnakeAndLaddersGame: @staticmethod def _is_moderator(user: Member) -> bool: """Return True if the user is a Moderator.""" - return any(Roles.moderator == role.id for role in user.roles) + return any(Roles.moderation_team == role.id for role in user.roles) diff --git a/bot/exts/fun/trivia_quiz.py b/bot/exts/fun/trivia_quiz.py index 712c8a12..60afbb06 100644 --- a/bot/exts/fun/trivia_quiz.py +++ b/bot/exts/fun/trivia_quiz.py @@ -550,7 +550,7 @@ class TriviaQuiz(commands.Cog): if self.game_status[ctx.channel.id]: # Check if the author is the game starter or a moderator. if ctx.author == self.game_owners[ctx.channel.id] or any( - Roles.moderator == role.id for role in ctx.author.roles + Roles.moderation_team == role.id for role in ctx.author.roles ): self.game_status[ctx.channel.id] = False del self.game_owners[ctx.channel.id] -- cgit v1.2.3 From 71d800add94df3d678786ab482747ae904290129 Mon Sep 17 00:00:00 2001 From: Izan Date: Fri, 8 Oct 2021 15:04:35 +0100 Subject: Rename `Roles.admin` to `Roles.admins` --- bot/constants.py | 10 +++++----- bot/exts/core/internal_eval/_internal_eval.py | 6 +++--- bot/exts/events/advent_of_code/_cog.py | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) (limited to 'bot/constants.py') diff --git a/bot/constants.py b/bot/constants.py index c5443393..92b40914 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -280,9 +280,9 @@ if Client.month_override is not None: class Roles(NamedTuple): - owner = 267627879762755584 - admin = int(environ.get("BOT_ADMIN_ROLE_ID", 267628507062992896)) - moderator = 267629731250176001 + owners = 267627879762755584 + admins = int(environ.get("BOT_ADMIN_ROLE_ID", 267628507062992896)) + moderation_team = 267629731250176001 helpers = int(environ.get("ROLE_HELPERS", 267630620367257601)) core_developers = 587606783669829632 everyone = int(environ.get("BOT_GUILD", 267624335836053506)) @@ -332,8 +332,8 @@ class Reddit: # Default role combinations -MODERATION_ROLES = Roles.moderation_team, Roles.admin, Roles.owner -STAFF_ROLES = Roles.helpers, Roles.moderation_team, Roles.admin, Roles.owner +MODERATION_ROLES = Roles.moderation_team, Roles.admins, Roles.owner +STAFF_ROLES = Roles.helpers, Roles.moderation_team, Roles.admins, Roles.owner # Whitelisted channels WHITELISTED_CHANNELS = ( diff --git a/bot/exts/core/internal_eval/_internal_eval.py b/bot/exts/core/internal_eval/_internal_eval.py index 4f6b4321..47e564a5 100644 --- a/bot/exts/core/internal_eval/_internal_eval.py +++ b/bot/exts/core/internal_eval/_internal_eval.py @@ -146,14 +146,14 @@ class InternalEval(commands.Cog): await self._send_output(ctx, eval_context.format_output()) @commands.group(name="internal", aliases=("int",)) - @with_role(Roles.admin) + @with_role(Roles.admins) async def internal_group(self, ctx: commands.Context) -> None: """Internal commands. Top secret!""" if not ctx.invoked_subcommand: await invoke_help_command(ctx) @internal_group.command(name="eval", aliases=("e",)) - @with_role(Roles.admin) + @with_role(Roles.admins) async def eval(self, ctx: commands.Context, *, code: str) -> None: """Run eval in a REPL-like format.""" if match := list(FORMATTED_CODE_REGEX.finditer(code)): @@ -172,7 +172,7 @@ class InternalEval(commands.Cog): await self._eval(ctx, code) @internal_group.command(name="reset", aliases=("clear", "exit", "r", "c")) - @with_role(Roles.admin) + @with_role(Roles.admins) async def reset(self, ctx: commands.Context) -> None: """Reset the context and locals of the eval session.""" self.locals = {} diff --git a/bot/exts/events/advent_of_code/_cog.py b/bot/exts/events/advent_of_code/_cog.py index ca60e517..4d811fa4 100644 --- a/bot/exts/events/advent_of_code/_cog.py +++ b/bot/exts/events/advent_of_code/_cog.py @@ -251,7 +251,7 @@ class AdventOfCode(commands.Cog): info_embed = _helpers.get_summary_embed(leaderboard) await ctx.send(f"```\n{table}\n```", embed=info_embed) - @with_role(Roles.admin) + @with_role(Roles.admins) @adventofcode_group.command( name="refresh", aliases=("fetch",), -- cgit v1.2.3 From dc5f73be8c18ef11c701822e8c600c8fca7b149c Mon Sep 17 00:00:00 2001 From: Izan Date: Fri, 8 Oct 2021 15:06:07 +0100 Subject: Rename `Roles.owner` to `Roles.owners` --- bot/constants.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'bot/constants.py') diff --git a/bot/constants.py b/bot/constants.py index 92b40914..b4191c5e 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -332,8 +332,8 @@ class Reddit: # Default role combinations -MODERATION_ROLES = Roles.moderation_team, Roles.admins, Roles.owner -STAFF_ROLES = Roles.helpers, Roles.moderation_team, Roles.admins, Roles.owner +MODERATION_ROLES = Roles.moderation_team, Roles.admins, Roles.owners +STAFF_ROLES = Roles.helpers, Roles.moderation_team, Roles.admins, Roles.owners # Whitelisted channels WHITELISTED_CHANNELS = ( -- cgit v1.2.3 From b0be25bed15bbfc88d61e2e842b9f894c9cac15c Mon Sep 17 00:00:00 2001 From: Chris Lovering Date: Wed, 13 Oct 2021 20:44:07 +0100 Subject: update advent of code channel IDs We deleted and re-made the channels so new IDs are needed. --- bot/constants.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'bot/constants.py') diff --git a/bot/constants.py b/bot/constants.py index 6e45632f..567daadd 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -101,8 +101,8 @@ class Cats: class Channels(NamedTuple): - advent_of_code = int(environ.get("AOC_CHANNEL_ID", 782715290437943306)) - advent_of_code_commands = int(environ.get("AOC_COMMANDS_CHANNEL_ID", 607247579608121354)) + advent_of_code = int(environ.get("AOC_CHANNEL_ID", 897932085766004786)) + advent_of_code_commands = int(environ.get("AOC_COMMANDS_CHANNEL_ID", 897932607545823342)) bot = 267659945086812160 organisation = 551789653284356126 devlog = int(environ.get("CHANNEL_DEVLOG", 622895325144940554)) -- cgit v1.2.3 From f18e9c3dda721b9bbbba884e31b34e4ae2831ffc Mon Sep 17 00:00:00 2001 From: D0rs4n <41237606+D0rs4n@users.noreply.github.com> Date: Thu, 14 Oct 2021 21:55:41 +0200 Subject: Add support to query AoC results in respect of days and stars (#857) * Add support to query AoC results in respect of days and stars From now on the AoC leaderboard command accepts a total of 2 optional arguments a day and star string (eg.: 1-2, for the second star of the first day) and a number of results they would like to see, with a total maximum of 15. This commit also introduces a few minor fixes in the AoC helper. * Improve overall code consitency in the AoC event Cog and helpers * Improve indenting and code consistency in the AoC cog * Improve code transparency in the AoC helpers * Patch various inconsistencies in the AoC cog and helpers * Migrate AoC Day and Star statistics filtering to Dropdowns From now on when the AoC leadearboard command is used with the DayAndStar argument(bool) the bot will send a View with two dropdowns and a button to Fetch the data based on the value of the Dropdowns. * Improve code and comment consistency in the AoC views and helpers * Patch logic errors, improve consistency in the AoC cog and view. * Add support to delete view from the message after timeout in the AoC cog * Move the day_and_star logic out of the typing context manager in the AoC cog * Revert season-locker in the AoC cog * Improve overall code transparency and indenting in the AoC cog and views * Remove unnecessary returns in the AoC cog and view --- bot/constants.py | 1 + bot/exts/events/advent_of_code/_cog.py | 45 ++++++++++++-- bot/exts/events/advent_of_code/_helpers.py | 9 ++- .../events/advent_of_code/views/dayandstarview.py | 71 ++++++++++++++++++++++ 4 files changed, 119 insertions(+), 7 deletions(-) create mode 100644 bot/exts/events/advent_of_code/views/dayandstarview.py (limited to 'bot/constants.py') diff --git a/bot/constants.py b/bot/constants.py index 567daadd..0720dd20 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -88,6 +88,7 @@ class AdventOfCode: ignored_days = environ.get("AOC_IGNORED_DAYS", "").split(",") leaderboard_displayed_members = 10 leaderboard_cache_expiry_seconds = 1800 + max_day_and_star_results = 15 year = int(environ.get("AOC_YEAR", datetime.utcnow().year)) role_id = int(environ.get("AOC_ROLE_ID", 518565788744024082)) diff --git a/bot/exts/events/advent_of_code/_cog.py b/bot/exts/events/advent_of_code/_cog.py index ca60e517..7dd967ec 100644 --- a/bot/exts/events/advent_of_code/_cog.py +++ b/bot/exts/events/advent_of_code/_cog.py @@ -2,6 +2,7 @@ import json import logging from datetime import datetime, timedelta from pathlib import Path +from typing import Optional import arrow import discord @@ -12,6 +13,7 @@ from bot.constants import ( AdventOfCode as AocConfig, Channels, Colours, Emojis, Month, Roles, WHITELISTED_CHANNELS, ) from bot.exts.events.advent_of_code import _helpers +from bot.exts.events.advent_of_code.views.dayandstarview import AoCDropdownView from bot.utils.decorators import InChannelCheckFailure, in_month, whitelist_override, with_role from bot.utils.extensions import invoke_help_command @@ -150,7 +152,7 @@ class AdventOfCode(commands.Cog): else: try: join_code = await _helpers.get_public_join_code(author) - except _helpers.FetchingLeaderboardFailed: + except _helpers.FetchingLeaderboardFailedError: await ctx.send(":x: Failed to get join code! Notified maintainers.") return @@ -185,14 +187,29 @@ class AdventOfCode(commands.Cog): brief="Get a snapshot of the PyDis private AoC leaderboard", ) @whitelist_override(channels=AOC_WHITELIST_RESTRICTED) - async def aoc_leaderboard(self, ctx: commands.Context) -> None: - """Get the current top scorers of the Python Discord Leaderboard.""" + async def aoc_leaderboard( + self, + ctx: commands.Context, + day_and_star: Optional[bool] = False, + maximum_scorers: Optional[int] = 10 + ) -> None: + """ + Get the current top scorers of the Python Discord Leaderboard. + + Additionally, you can provide an argument `day_and_star` (Boolean) to have the bot send a View + that will let you filter by day and star. + """ + if maximum_scorers > AocConfig.max_day_and_star_results or maximum_scorers <= 0: + raise commands.BadArgument( + f"The maximum number of results you can query is {AocConfig.max_day_and_star_results}" + ) async with ctx.typing(): try: leaderboard = await _helpers.fetch_leaderboard() - except _helpers.FetchingLeaderboardFailed: + except _helpers.FetchingLeaderboardFailedError: await ctx.send(":x: Unable to fetch leaderboard!") return + if not day_and_star: number_of_participants = leaderboard["number_of_participants"] @@ -203,6 +220,22 @@ class AdventOfCode(commands.Cog): info_embed = _helpers.get_summary_embed(leaderboard) await ctx.send(content=f"{header}\n\n{table}", embed=info_embed) + return + + # This is a dictionary that contains solvers in respect of day, and star. + # e.g. 1-1 means the solvers of the first star of the first day and their completion time + per_day_and_star = json.loads(leaderboard['leaderboard_per_day_and_star']) + view = AoCDropdownView( + day_and_star_data=per_day_and_star, + maximum_scorers=maximum_scorers, + original_author=ctx.author + ) + message = await ctx.send( + content="Please select a day and a star to filter by!", + view=view + ) + await view.wait() + await message.edit(view=None) @in_month(Month.DECEMBER) @adventofcode_group.command( @@ -231,7 +264,7 @@ class AdventOfCode(commands.Cog): """Send an embed with daily completion statistics for the Python Discord leaderboard.""" try: leaderboard = await _helpers.fetch_leaderboard() - except _helpers.FetchingLeaderboardFailed: + except _helpers.FetchingLeaderboardFailedError: await ctx.send(":x: Can't fetch leaderboard for stats right now!") return @@ -267,7 +300,7 @@ class AdventOfCode(commands.Cog): async with ctx.typing(): try: await _helpers.fetch_leaderboard(invalidate_cache=True) - except _helpers.FetchingLeaderboardFailed: + except _helpers.FetchingLeaderboardFailedError: await ctx.send(":x: Something went wrong while trying to refresh the cache!") else: await ctx.send("\N{OK Hand Sign} Refreshed leaderboard cache!") diff --git a/bot/exts/events/advent_of_code/_helpers.py b/bot/exts/events/advent_of_code/_helpers.py index 5fedb60f..af64bc81 100644 --- a/bot/exts/events/advent_of_code/_helpers.py +++ b/bot/exts/events/advent_of_code/_helpers.py @@ -105,6 +105,7 @@ def _parse_raw_leaderboard_data(raw_leaderboard_data: dict) -> dict: # The data we get from the AoC website is structured by member, not by day/star, # which means we need to iterate over the members to transpose the data to a per # star view. We need that per star view to compute rank scores per star. + per_day_star_stats = collections.defaultdict(list) for member in raw_leaderboard_data.values(): name = member["name"] if member["name"] else f"Anonymous #{member['id']}" member_id = member["id"] @@ -122,6 +123,11 @@ def _parse_raw_leaderboard_data(raw_leaderboard_data: dict) -> dict: star_results[(day, star)].append( StarResult(member_id=member_id, completion_time=completion_time) ) + per_day_star_stats[f"{day}-{star}"].append( + {'completion_time': int(data["get_star_ts"]), 'member_name': name} + ) + for key in per_day_star_stats: + per_day_star_stats[key] = sorted(per_day_star_stats[key], key=operator.itemgetter('completion_time')) # Now that we have a transposed dataset that holds the completion time of all # participants per star, we can compute the rank-based scores each participant @@ -151,7 +157,7 @@ def _parse_raw_leaderboard_data(raw_leaderboard_data: dict) -> dict: # this data to JSON in order to cache it in Redis. daily_stats[day] = {"star_one": star_one, "star_two": star_two} - return {"daily_stats": daily_stats, "leaderboard": sorted_leaderboard} + return {"daily_stats": daily_stats, "leaderboard": sorted_leaderboard, 'per_day_and_star': per_day_star_stats} def _format_leaderboard(leaderboard: dict[str, dict]) -> str: @@ -289,6 +295,7 @@ async def fetch_leaderboard(invalidate_cache: bool = False) -> dict: "leaderboard_fetched_at": leaderboard_fetched_at, "number_of_participants": number_of_participants, "daily_stats": json.dumps(parsed_leaderboard_data["daily_stats"]), + "leaderboard_per_day_and_star": json.dumps(parsed_leaderboard_data["per_day_and_star"]) } # Store the new values in Redis diff --git a/bot/exts/events/advent_of_code/views/dayandstarview.py b/bot/exts/events/advent_of_code/views/dayandstarview.py new file mode 100644 index 00000000..243db32e --- /dev/null +++ b/bot/exts/events/advent_of_code/views/dayandstarview.py @@ -0,0 +1,71 @@ +from datetime import datetime + +import discord + +AOC_DAY_AND_STAR_TEMPLATE = "{rank: >4} | {name:25.25} | {completion_time: >10}" + + +class AoCDropdownView(discord.ui.View): + """Interactive view to filter AoC stats by Day and Star.""" + + def __init__(self, original_author: discord.Member, day_and_star_data: dict[str: dict], maximum_scorers: int): + super().__init__() + self.day = 0 + self.star = 0 + self.data = day_and_star_data + self.maximum_scorers = maximum_scorers + self.original_author = original_author + + def generate_output(self) -> str: + """Generates a formatted codeblock with AoC statistics based on the currently selected day and star.""" + header = AOC_DAY_AND_STAR_TEMPLATE.format( + rank="Rank", + name="Name", completion_time="Completion time (UTC)" + ) + lines = [f"{header}\n{'-' * (len(header) + 2)}"] + + for rank, scorer in enumerate(self.data[f"{self.day}-{self.star}"][:self.maximum_scorers]): + time_data = datetime.fromtimestamp(scorer['completion_time']).strftime("%I:%M:%S %p") + lines.append(AOC_DAY_AND_STAR_TEMPLATE.format( + datastamp="", + rank=rank + 1, + name=scorer['member_name'], + completion_time=time_data) + ) + joined_lines = "\n".join(lines) + return f"Statistics for Day: {self.day}, Star: {self.star}.\n ```\n{joined_lines}\n```" + + async def interaction_check(self, interaction: discord.Interaction) -> bool: + """Global check to ensure that the interacting user is the user who invoked the command originally.""" + return interaction.user == self.original_author + + @discord.ui.select( + placeholder="Day", + options=[discord.SelectOption(label=str(i)) for i in range(1, 26)], + custom_id="day_select" + ) + async def day_select(self, select: discord.ui.Select, interaction: discord.Interaction) -> None: + """Dropdown to choose a Day of the AoC.""" + self.day = select.values[0] + + @discord.ui.select( + placeholder="Star", + options=[discord.SelectOption(label=str(i)) for i in range(1, 3)], + custom_id="star_select" + ) + async def star_select(self, select: discord.ui.Select, interaction: discord.Interaction) -> None: + """Dropdown to choose either the first or the second star.""" + self.star = select.values[0] + + @discord.ui.button(label="Fetch", style=discord.ButtonStyle.blurple) + async def fetch(self, button: discord.ui.Button, interaction: discord.Interaction) -> None: + """Button that fetches the statistics based on the dropdown values.""" + if self.day == 0 or self.star == 0: + await interaction.response.send_message( + "You have to select a value from both of the dropdowns!", + ephemeral=True + ) + else: + await interaction.response.edit_message(content=self.generate_output()) + self.day = 0 + self.star = 0 -- cgit v1.2.3 From 40b58132d488bab962d8aeecfc932a1cb5842a85 Mon Sep 17 00:00:00 2001 From: Hassan Abouelela Date: Fri, 12 Nov 2021 06:19:59 +0400 Subject: Move Sentry Into Init Moves the sentry setup to be one of the very first things run during startup, so we are able to catch more errors, such as ones that might occur while setting up logs. Signed-off-by: Hassan Abouelela --- bot/__init__.py | 18 ++++++++++++++++++ bot/__main__.py | 20 +------------------- bot/constants.py | 3 --- 3 files changed, 19 insertions(+), 22 deletions(-) (limited to 'bot/constants.py') diff --git a/bot/__init__.py b/bot/__init__.py index b19bd76a..ae53a5a5 100644 --- a/bot/__init__.py +++ b/bot/__init__.py @@ -6,14 +6,32 @@ except ModuleNotFoundError: pass import asyncio +import logging import os from functools import partial, partialmethod import arrow +import sentry_sdk from discord.ext import commands +from sentry_sdk.integrations.logging import LoggingIntegration +from sentry_sdk.integrations.redis import RedisIntegration from bot import log, monkey_patches +sentry_logging = LoggingIntegration( + level=logging.DEBUG, + event_level=logging.WARNING +) + +sentry_sdk.init( + dsn=os.environ.get("BOT_SENTRY_DSN"), + integrations=[ + sentry_logging, + RedisIntegration() + ], + release=f"sir-lancebot@{os.environ.get('GIT_SHA', 'foobar')}" +) + log.setup() # Set timestamp of when execution started (approximately) diff --git a/bot/__main__.py b/bot/__main__.py index c6e5fa57..6889fe2b 100644 --- a/bot/__main__.py +++ b/bot/__main__.py @@ -1,28 +1,10 @@ import logging -import sentry_sdk -from sentry_sdk.integrations.logging import LoggingIntegration -from sentry_sdk.integrations.redis import RedisIntegration - from bot.bot import bot -from bot.constants import Client, GIT_SHA, STAFF_ROLES, WHITELISTED_CHANNELS +from bot.constants import Client, STAFF_ROLES, WHITELISTED_CHANNELS from bot.utils.decorators import whitelist_check from bot.utils.extensions import walk_extensions -sentry_logging = LoggingIntegration( - level=logging.DEBUG, - event_level=logging.WARNING -) - -sentry_sdk.init( - dsn=Client.sentry_dsn, - integrations=[ - sentry_logging, - RedisIntegration() - ], - release=f"sir-lancebot@{GIT_SHA}" -) - log = logging.getLogger(__name__) bot.add_check(whitelist_check(channels=WHITELISTED_CHANNELS, roles=STAFF_ROLES)) diff --git a/bot/constants.py b/bot/constants.py index 9d12000e..2b41b8a4 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -134,7 +134,6 @@ class Client(NamedTuple): guild = int(environ.get("BOT_GUILD", 267624335836053506)) prefix = environ.get("PREFIX", ".") token = environ.get("BOT_TOKEN") - sentry_dsn = environ.get("BOT_SENTRY_DSN") debug = environ.get("BOT_DEBUG", "true").lower() == "true" github_bot_repo = "https://github.com/python-discord/sir-lancebot" # Override seasonal locks: 1 (January) to 12 (December) @@ -348,8 +347,6 @@ WHITELISTED_CHANNELS = ( Channels.voice_chat_1, ) -GIT_SHA = environ.get("GIT_SHA", "foobar") - # Bot replies ERROR_REPLIES = [ "Please don't do that.", -- cgit v1.2.3 From b794b02c4aa81218500151fde53e95d8c10c9817 Mon Sep 17 00:00:00 2001 From: Hassan Abouelela Date: Sat, 20 Nov 2021 23:10:57 +0300 Subject: Disable File Logging By Default Place logging to file behind an environment variable. Signed-off-by: Hassan Abouelela --- bot/constants.py | 1 + bot/log.py | 5 +---- 2 files changed, 2 insertions(+), 4 deletions(-) (limited to 'bot/constants.py') diff --git a/bot/constants.py b/bot/constants.py index 2b41b8a4..33bc8b61 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -135,6 +135,7 @@ class Client(NamedTuple): prefix = environ.get("PREFIX", ".") token = environ.get("BOT_TOKEN") debug = environ.get("BOT_DEBUG", "true").lower() == "true" + file_logs = environ.get("FILE_LOGS", "false").lower() == "true" github_bot_repo = "https://github.com/python-discord/sir-lancebot" # Override seasonal locks: 1 (January) to 12 (December) month_override = int(environ["MONTH_OVERRIDE"]) if "MONTH_OVERRIDE" in environ else None diff --git a/bot/log.py b/bot/log.py index 97561be4..29e696e0 100644 --- a/bot/log.py +++ b/bot/log.py @@ -20,10 +20,7 @@ def setup() -> None: log_format = logging.Formatter(format_string) root_logger = logging.getLogger() - # Copied from constants file, which we can't import yet since loggers aren't instantiated - debug = os.environ.get("BOT_DEBUG", "true").lower() == "true" - - if debug: + if Client.file_logs: # Set up file logging log_file = Path("logs/sir-lancebot.log") log_file.parent.mkdir(exist_ok=True) -- cgit v1.2.3 From be7da6f34e50b7a99ef6c3a7eb67a48b24b818ed Mon Sep 17 00:00:00 2001 From: Izan Date: Mon, 29 Nov 2021 09:11:17 +0000 Subject: Change `MODERATION_ROLES` and `STAFF_ROLES` constants to be a set --- bot/constants.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'bot/constants.py') diff --git a/bot/constants.py b/bot/constants.py index b4191c5e..bef7f3d1 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -332,8 +332,8 @@ class Reddit: # Default role combinations -MODERATION_ROLES = Roles.moderation_team, Roles.admins, Roles.owners -STAFF_ROLES = Roles.helpers, Roles.moderation_team, Roles.admins, Roles.owners +MODERATION_ROLES = {Roles.moderation_team, Roles.admins, Roles.owners} +STAFF_ROLES = {Roles.helpers, Roles.moderation_team, Roles.admins, Roles.owners} # Whitelisted channels WHITELISTED_CHANNELS = ( -- cgit v1.2.3 From 7cb5c5517043c0006b001c15373b664b86cc6b43 Mon Sep 17 00:00:00 2001 From: onerandomusername Date: Fri, 3 Dec 2021 17:28:15 -0500 Subject: feat: implement moving commands add exceptions and handler for commands that move locations --- bot/constants.py | 3 +++ bot/exts/core/error_handler.py | 10 +++++++++- bot/utils/exceptions.py | 7 +++++++ 3 files changed, 19 insertions(+), 1 deletion(-) (limited to 'bot/constants.py') diff --git a/bot/constants.py b/bot/constants.py index f4b1cab0..854fbe55 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -23,6 +23,7 @@ __all__ = ( "Reddit", "RedisConfig", "RedirectOutput", + "PYTHON_PREFIX" "MODERATION_ROLES", "STAFF_ROLES", "WHITELISTED_CHANNELS", @@ -34,6 +35,8 @@ __all__ = ( log = logging.getLogger(__name__) +PYTHON_PREFIX = "!" + @dataclasses.dataclass class AdventOfCodeLeaderboard: id: str diff --git a/bot/exts/core/error_handler.py b/bot/exts/core/error_handler.py index fd2123e7..676a1e70 100644 --- a/bot/exts/core/error_handler.py +++ b/bot/exts/core/error_handler.py @@ -12,7 +12,7 @@ from sentry_sdk import push_scope from bot.bot import Bot from bot.constants import Channels, Colours, ERROR_REPLIES, NEGATIVE_REPLIES, RedirectOutput from bot.utils.decorators import InChannelCheckFailure, InMonthCheckFailure -from bot.utils.exceptions import APIError, UserNotPlayingError +from bot.utils.exceptions import APIError, MovedCommandError, UserNotPlayingError log = logging.getLogger(__name__) @@ -130,6 +130,14 @@ class CommandErrorHandler(commands.Cog): ) return + if isinstance(error, MovedCommandError): + description = ( + f"This command, `{ctx.prefix}{ctx.command.qualified_name}` has moved to `{error.new_command_name}`.\n" + f"Please use `{error.new_command_name}` instead." + ) + await ctx.send(embed=self.error_embed(description, NEGATIVE_REPLIES)) + return + with push_scope() as scope: scope.user = { "id": ctx.author.id, diff --git a/bot/utils/exceptions.py b/bot/utils/exceptions.py index bf0e5813..3cd96325 100644 --- a/bot/utils/exceptions.py +++ b/bot/utils/exceptions.py @@ -15,3 +15,10 @@ class APIError(Exception): self.api = api self.status_code = status_code self.error_msg = error_msg + + +class MovedCommandError(Exception): + """Raised when a command has moved locations.""" + + def __init__(self, new_command_name: str): + self.new_command_name = new_command_name -- cgit v1.2.3 From 21518aed7b3cfb9c2ec4fa1722689a6c4df56372 Mon Sep 17 00:00:00 2001 From: ChrisLovering Date: Sat, 25 Dec 2021 22:23:12 +0000 Subject: Add hourly task to assign AoC completer role This task uses the cached leaderboard to see who has all 50 stars and assigns them a role to highlight them as completers. --- bot/constants.py | 1 + bot/exts/events/advent_of_code/_cog.py | 60 ++++++++++++++++++++++++++++++++-- 2 files changed, 59 insertions(+), 2 deletions(-) (limited to 'bot/constants.py') diff --git a/bot/constants.py b/bot/constants.py index 854fbe55..01f825a0 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -291,6 +291,7 @@ class Roles(NamedTuple): helpers = int(environ.get("ROLE_HELPERS", 267630620367257601)) core_developers = 587606783669829632 everyone = int(environ.get("BOT_GUILD", 267624335836053506)) + aoc_completionist = int(environ.get("AOC_COMPLETIONIST_ROLE_ID", 916691790181056532)) class Tokens(NamedTuple): diff --git a/bot/exts/events/advent_of_code/_cog.py b/bot/exts/events/advent_of_code/_cog.py index 52254ea1..6974d89c 100644 --- a/bot/exts/events/advent_of_code/_cog.py +++ b/bot/exts/events/advent_of_code/_cog.py @@ -7,12 +7,15 @@ from typing import Optional import arrow import discord from async_rediscache import RedisCache -from discord.ext import commands +from discord.ext import commands, tasks from bot.bot import Bot -from bot.constants import AdventOfCode as AocConfig, Channels, Colours, Emojis, Month, Roles, WHITELISTED_CHANNELS +from bot.constants import ( + AdventOfCode as AocConfig, Channels, Client, Colours, Emojis, Month, Roles, WHITELISTED_CHANNELS +) from bot.exts.events.advent_of_code import _helpers from bot.exts.events.advent_of_code.views.dayandstarview import AoCDropdownView +from bot.utils import members from bot.utils.decorators import InChannelCheckFailure, in_month, whitelist_override, with_role from bot.utils.extensions import invoke_help_command @@ -31,6 +34,7 @@ class AdventOfCode(commands.Cog): """Advent of Code festivities! Ho Ho Ho!""" # Redis Cache for linking Discord IDs to Advent of Code usernames + # RedisCache[member_id: aoc_username_string] account_links = RedisCache() def __init__(self, bot: Bot): @@ -52,6 +56,57 @@ class AdventOfCode(commands.Cog): self.status_task.set_name("AoC Status Countdown") self.status_task.add_done_callback(_helpers.background_task_callback) + self.completionist_task.start() + + @tasks.loop(minutes=10.0) + async def completionist_task(self) -> None: + """ + Give members who have completed all 50 AoC stars the completionist role. + + Runs on a schedule, as defined in the task.loop decorator. + """ + await self.bot.wait_until_guild_available() + guild = self.bot.get_guild(Client.guild) + completionist_role = guild.get_role(Roles.aoc_completionist) + if completionist_role is None: + log.warning("Could not find the AoC completionist role; cancelling completionist task.") + self.completer_task.cancel() + return + + aoc_name_to_member_id = { + aoc_name: member_id + for member_id, aoc_name in await self.account_links.items() + } + + try: + leaderboard = await _helpers.fetch_leaderboard() + except _helpers.FetchingLeaderboardFailedError: + await self.bot.send_log("Unable to fetch AoC leaderboard during role sync.") + return + + placement_leaderboard = json.loads(leaderboard["placement_leaderboard"]) + + for member_aoc_info in placement_leaderboard.values(): + if not member_aoc_info["stars"] == 50: + # Only give the role to people who have completed all 50 stars + continue + + member_id = aoc_name_to_member_id[member_aoc_info["name"]] + if not member_id: + continue + + member = members.get_or_fetch_member(guild, member_id) + if member is None: + continue + + if completionist_role in member.roles: + continue + + if await self.completionist_block_list.contains(member_id): + continue + + await members.handle_role_change(member, member.add_roles, completionist_role) + @commands.group(name="adventofcode", aliases=("aoc",)) @whitelist_override(channels=AOC_WHITELIST) async def adventofcode_group(self, ctx: commands.Context) -> None: @@ -408,6 +463,7 @@ class AdventOfCode(commands.Cog): log.debug("Unloading the cog and canceling the background task.") self.notification_task.cancel() self.status_task.cancel() + self.completionist_task.cancel() def _build_about_embed(self) -> discord.Embed: """Build and return the informational "About AoC" embed from the resources file.""" -- cgit v1.2.3 From 2c66844a3e9e59b97fba43de0d850d8cf597d9ad Mon Sep 17 00:00:00 2001 From: Chris Lovering Date: Sun, 2 Jan 2022 22:23:47 +0000 Subject: Update GitHub issue closed emoji GitHub updated their issue closed emoji from red to purple, this updates the emoji to relfect that new colour. --- bot/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot/constants.py') diff --git a/bot/constants.py b/bot/constants.py index 01f825a0..3b426c47 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -197,7 +197,7 @@ class Emojis: # These icons are from Github's repo https://github.com/primer/octicons/ issue_open = "<:IssueOpen:852596024777506817>" - issue_closed = "<:IssueClosed:852596024739758081>" + issue_closed = "<:IssueClosed:927326162861039626>" issue_draft = "<:IssueDraft:852596025147523102>" # Not currently used by Github, but here for future. pull_request_open = "<:PROpen:852596471505223781>" pull_request_closed = "<:PRClosed:852596024732286976>" -- cgit v1.2.3 From 1fd39adbb53b14015033fcaff0cbfb4aa81c65d7 Mon Sep 17 00:00:00 2001 From: Hassan Abouelela Date: Tue, 25 Jan 2022 12:13:07 +0300 Subject: Clean Up Constants File This commit moves the logging constants from the Client class to a new Logging class. Flake8 is renabled for constants, and the error it was disabled for was specifically ignored. All new errors were fixed. It also fixes a bug with __all__, which was trying to export a missing symbol due to a missing comma. Signed-off-by: Hassan Abouelela --- bot/constants.py | 13 ++++++++++--- bot/log.py | 8 ++++---- tox.ini | 4 +++- 3 files changed, 17 insertions(+), 8 deletions(-) (limited to 'bot/constants.py') diff --git a/bot/constants.py b/bot/constants.py index 3b426c47..7e7ee749 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -12,6 +12,7 @@ __all__ = ( "Channels", "Categories", "Client", + "Logging", "Colours", "Emojis", "Icons", @@ -23,7 +24,7 @@ __all__ = ( "Reddit", "RedisConfig", "RedirectOutput", - "PYTHON_PREFIX" + "PYTHON_PREFIX", "MODERATION_ROLES", "STAFF_ROLES", "WHITELISTED_CHANNELS", @@ -37,6 +38,7 @@ log = logging.getLogger(__name__) PYTHON_PREFIX = "!" + @dataclasses.dataclass class AdventOfCodeLeaderboard: id: str @@ -130,18 +132,24 @@ class Categories(NamedTuple): media = 799054581991997460 staff = 364918151625965579 + codejam_categories_name = "Code Jam" # Name of the codejam team categories + class Client(NamedTuple): name = "Sir Lancebot" guild = int(environ.get("BOT_GUILD", 267624335836053506)) prefix = environ.get("PREFIX", ".") token = environ.get("BOT_TOKEN") debug = environ.get("BOT_DEBUG", "true").lower() == "true" - file_logs = environ.get("FILE_LOGS", "false").lower() == "true" github_bot_repo = "https://github.com/python-discord/sir-lancebot" # Override seasonal locks: 1 (January) to 12 (December) month_override = int(environ["MONTH_OVERRIDE"]) if "MONTH_OVERRIDE" in environ else None + + +class Logging(NamedTuple): + debug = Client.debug + file_logs = environ.get("FILE_LOGS", "false").lower() == "true" trace_loggers = environ.get("BOT_TRACE_LOGGERS") @@ -231,7 +239,6 @@ class Emojis: status_dnd = "<:status_dnd:470326272082313216>" status_offline = "<:status_offline:470326266537705472>" - stackoverflow_tag = "<:stack_tag:870926975307501570>" stackoverflow_views = "<:stack_eye:870926992692879371>" diff --git a/bot/log.py b/bot/log.py index 29e696e0..a87a836a 100644 --- a/bot/log.py +++ b/bot/log.py @@ -6,7 +6,7 @@ from pathlib import Path import coloredlogs -from bot.constants import Client +from bot.constants import Logging def setup() -> None: @@ -20,7 +20,7 @@ def setup() -> None: log_format = logging.Formatter(format_string) root_logger = logging.getLogger() - if Client.file_logs: + if Logging.file_logs: # Set up file logging log_file = Path("logs/sir-lancebot.log") log_file.parent.mkdir(exist_ok=True) @@ -45,7 +45,7 @@ def setup() -> None: coloredlogs.install(level=logging.TRACE, stream=sys.stdout) - root_logger.setLevel(logging.DEBUG if Client.debug else logging.INFO) + root_logger.setLevel(logging.DEBUG if Logging.debug else logging.INFO) # Silence irrelevant loggers logging.getLogger("discord").setLevel(logging.ERROR) logging.getLogger("websockets").setLevel(logging.ERROR) @@ -81,7 +81,7 @@ def _set_trace_loggers() -> None: Otherwise if the env var begins with a "*", the root logger is set to the trace level and other contents are ignored. """ - level_filter = Client.trace_loggers + level_filter = Logging.trace_loggers if level_filter: if level_filter.startswith("*"): logging.getLogger().setLevel(logging.TRACE) diff --git a/tox.ini b/tox.ini index f561fcd9..61ff9616 100644 --- a/tox.ini +++ b/tox.ini @@ -20,5 +20,7 @@ exclude= __pycache__,.cache, venv,.venv, tests, - constants.py +per-file-ignores = + # Don't require docstrings in constants + constants.py:D101 import-order-style=pycharm -- cgit v1.2.3 From 335620341046e3b7be547ac9f18d25d1fb9bec55 Mon Sep 17 00:00:00 2001 From: Hassan Abouelela Date: Tue, 25 Jan 2022 12:15:50 +0300 Subject: Reduce AOC Logging Output The AOC cog produces a lot of large logs very frequently which have minimal value, causing the logs to be significantly harder to navigate. Signed-off-by: Hassan Abouelela --- bot/constants.py | 2 +- bot/exts/events/advent_of_code/_helpers.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'bot/constants.py') diff --git a/bot/constants.py b/bot/constants.py index 7e7ee749..d39f7361 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -55,7 +55,7 @@ class AdventOfCodeLeaderboard: def session(self) -> str: """Return either the actual `session` cookie or the fallback cookie.""" if self.use_fallback_session: - log.info(f"Returning fallback cookie for board `{self.id}`.") + log.trace(f"Returning fallback cookie for board `{self.id}`.") return AdventOfCode.fallback_session return self._session diff --git a/bot/exts/events/advent_of_code/_helpers.py b/bot/exts/events/advent_of_code/_helpers.py index 15b1329d..6c004901 100644 --- a/bot/exts/events/advent_of_code/_helpers.py +++ b/bot/exts/events/advent_of_code/_helpers.py @@ -255,7 +255,7 @@ async def _fetch_leaderboard_data() -> dict[str, Any]: # Two attempts, one with the original session cookie and one with the fallback session for attempt in range(1, 3): - log.info(f"Attempting to fetch leaderboard `{leaderboard.id}` ({attempt}/2)") + log.debug(f"Attempting to fetch leaderboard `{leaderboard.id}` ({attempt}/2)") cookies = {"session": leaderboard.session} try: raw_data = await _leaderboard_request(leaderboard_url, leaderboard.id, cookies) -- cgit v1.2.3 From 436c9f740cc5002ff8199b57a6b7bc1a778d6b37 Mon Sep 17 00:00:00 2001 From: TizzySaurus <47674925+TizzySaurus@users.noreply.github.com> Date: Sun, 20 Feb 2022 11:24:51 +0000 Subject: Allow `.src` in dev-contrib and community-meta (#1033) --- bot/constants.py | 9 +++++---- bot/exts/core/error_handler.py | 3 ++- bot/exts/core/source.py | 4 +++- bot/exts/holidays/easter/egg_facts.py | 2 +- bot/exts/holidays/halloween/candy_collection.py | 6 +++--- bot/exts/holidays/halloween/spookynamerate.py | 8 ++++---- bot/exts/holidays/pride/pride_facts.py | 2 +- bot/exts/holidays/pride/pride_leader.py | 2 +- bot/exts/holidays/valentines/be_my_valentine.py | 2 +- bot/exts/holidays/valentines/lovecalculator.py | 4 ++-- bot/utils/checks.py | 2 +- bot/utils/decorators.py | 4 ++-- 12 files changed, 26 insertions(+), 22 deletions(-) (limited to 'bot/constants.py') diff --git a/bot/constants.py b/bot/constants.py index d39f7361..b4d7bc24 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -109,7 +109,8 @@ class Cats: class Channels(NamedTuple): advent_of_code = int(environ.get("AOC_CHANNEL_ID", 897932085766004786)) advent_of_code_commands = int(environ.get("AOC_COMMANDS_CHANNEL_ID", 897932607545823342)) - bot = 267659945086812160 + bot_commands = 267659945086812160 + community_meta = 267659945086812160 organisation = 551789653284356126 devlog = int(environ.get("CHANNEL_DEVLOG", 622895325144940554)) dev_contrib = 635950537262759947 @@ -118,7 +119,7 @@ class Channels(NamedTuple): off_topic_0 = 291284109232308226 off_topic_1 = 463035241142026251 off_topic_2 = 463035268514185226 - community_bot_commands = int(environ.get("CHANNEL_COMMUNITY_BOT_COMMANDS", 607247579608121354)) + sir_lancebot_playground = int(environ.get("CHANNEL_COMMUNITY_BOT_COMMANDS", 607247579608121354)) voice_chat_0 = 412357430186344448 voice_chat_1 = 799647045886541885 staff_voice = 541638762007101470 @@ -350,8 +351,8 @@ STAFF_ROLES = {Roles.helpers, Roles.moderation_team, Roles.admins, Roles.owners} # Whitelisted channels WHITELISTED_CHANNELS = ( - Channels.bot, - Channels.community_bot_commands, + Channels.bot_commands, + Channels.sir_lancebot_playground, Channels.off_topic_0, Channels.off_topic_1, Channels.off_topic_2, diff --git a/bot/exts/core/error_handler.py b/bot/exts/core/error_handler.py index 676a1e70..983632ba 100644 --- a/bot/exts/core/error_handler.py +++ b/bot/exts/core/error_handler.py @@ -98,7 +98,8 @@ class CommandErrorHandler(commands.Cog): if isinstance(error, commands.NoPrivateMessage): await ctx.send( embed=self.error_embed( - f"This command can only be used in the server. Go to <#{Channels.community_bot_commands}> instead!", + "This command can only be used in the server. " + f"Go to <#{Channels.sir_lancebot_playground}> instead!", NEGATIVE_REPLIES ) ) diff --git a/bot/exts/core/source.py b/bot/exts/core/source.py index 7572ce51..e9568933 100644 --- a/bot/exts/core/source.py +++ b/bot/exts/core/source.py @@ -6,14 +6,16 @@ from discord import Embed from discord.ext import commands from bot.bot import Bot -from bot.constants import Source +from bot.constants import Channels, Source, WHITELISTED_CHANNELS from bot.utils.converters import SourceConverter, SourceType +from bot.utils.decorators import whitelist_override class BotSource(commands.Cog): """Displays information about the bot's source code.""" @commands.command(name="source", aliases=("src",)) + @whitelist_override(channels=WHITELISTED_CHANNELS+[Channels.community_meta, Channels.dev_contrib]) async def source_command(self, ctx: commands.Context, *, source_item: SourceConverter = None) -> None: """Display information and a GitHub link to the source code of a command, tag, or cog.""" if not source_item: diff --git a/bot/exts/holidays/easter/egg_facts.py b/bot/exts/holidays/easter/egg_facts.py index 5f216e0d..152af6a4 100644 --- a/bot/exts/holidays/easter/egg_facts.py +++ b/bot/exts/holidays/easter/egg_facts.py @@ -31,7 +31,7 @@ class EasterFacts(commands.Cog): """A background task that sends an easter egg fact in the event channel everyday.""" await self.bot.wait_until_guild_available() - channel = self.bot.get_channel(Channels.community_bot_commands) + channel = self.bot.get_channel(Channels.sir_lancebot_playground) await channel.send(embed=self.make_embed()) @commands.command(name="eggfact", aliases=("fact",)) diff --git a/bot/exts/holidays/halloween/candy_collection.py b/bot/exts/holidays/halloween/candy_collection.py index 729bbc97..220ba8e5 100644 --- a/bot/exts/holidays/halloween/candy_collection.py +++ b/bot/exts/holidays/halloween/candy_collection.py @@ -55,7 +55,7 @@ class CandyCollection(commands.Cog): if message.author.bot: return # ensure it's hacktober channel - if message.channel.id != Channels.community_bot_commands: + if message.channel.id != Channels.sir_lancebot_playground: return # do random check for skull first as it has the lower chance @@ -77,7 +77,7 @@ class CandyCollection(commands.Cog): return # check to ensure it is in correct channel - if message.channel.id != Channels.community_bot_commands: + if message.channel.id != Channels.sir_lancebot_playground: return # if its not a candy or skull, and it is one of 10 most recent messages, @@ -139,7 +139,7 @@ class CandyCollection(commands.Cog): @property def hacktober_channel(self) -> discord.TextChannel: """Get #hacktoberbot channel from its ID.""" - return self.bot.get_channel(Channels.community_bot_commands) + return self.bot.get_channel(Channels.sir_lancebot_playground) @staticmethod async def send_spook_msg( diff --git a/bot/exts/holidays/halloween/spookynamerate.py b/bot/exts/holidays/halloween/spookynamerate.py index a3aa4f13..02fb71c3 100644 --- a/bot/exts/holidays/halloween/spookynamerate.py +++ b/bot/exts/holidays/halloween/spookynamerate.py @@ -223,7 +223,7 @@ class SpookyNameRate(Cog): if self.first_time: await channel.send( "Okkey... Welcome to the **Spooky Name Rate Game**! It's a relatively simple game.\n" - f"Everyday, a random name will be sent in <#{Channels.community_bot_commands}> " + f"Everyday, a random name will be sent in <#{Channels.sir_lancebot_playground}> " "and you need to try and spookify it!\nRegister your name using " f"`{Client.prefix}spookynamerate add spookified name`" ) @@ -359,10 +359,10 @@ class SpookyNameRate(Cog): """Gets the sir-lancebot-channel after waiting until ready.""" await self.bot.wait_until_ready() channel = self.bot.get_channel( - Channels.community_bot_commands - ) or await self.bot.fetch_channel(Channels.community_bot_commands) + Channels.sir_lancebot_playground + ) or await self.bot.fetch_channel(Channels.sir_lancebot_playground) if not channel: - logger.warning("Bot is unable to get the #seasonalbot-commands channel. Please check the channel ID.") + logger.warning("Bot is unable to get the #sir-lancebot-playground channel. Please check the channel ID.") return channel @staticmethod diff --git a/bot/exts/holidays/pride/pride_facts.py b/bot/exts/holidays/pride/pride_facts.py index e6ef7108..340f0b43 100644 --- a/bot/exts/holidays/pride/pride_facts.py +++ b/bot/exts/holidays/pride/pride_facts.py @@ -30,7 +30,7 @@ class PrideFacts(commands.Cog): """Background task to post the daily pride fact every day.""" await self.bot.wait_until_guild_available() - channel = self.bot.get_channel(Channels.community_bot_commands) + channel = self.bot.get_channel(Channels.sir_lancebot_playground) await self.send_select_fact(channel, datetime.utcnow()) async def send_random_fact(self, ctx: commands.Context) -> None: diff --git a/bot/exts/holidays/pride/pride_leader.py b/bot/exts/holidays/pride/pride_leader.py index 298c9328..adf01134 100644 --- a/bot/exts/holidays/pride/pride_leader.py +++ b/bot/exts/holidays/pride/pride_leader.py @@ -83,7 +83,7 @@ class PrideLeader(commands.Cog): embed.add_field( name="For More Information", value=f"Do `{constants.Client.prefix}wiki {name}`" - f" in <#{constants.Channels.community_bot_commands}>", + f" in <#{constants.Channels.sir_lancebot_playground}>", inline=False ) embed.set_thumbnail(url=pride_leader["url"]) diff --git a/bot/exts/holidays/valentines/be_my_valentine.py b/bot/exts/holidays/valentines/be_my_valentine.py index 1572d474..cbb95157 100644 --- a/bot/exts/holidays/valentines/be_my_valentine.py +++ b/bot/exts/holidays/valentines/be_my_valentine.py @@ -70,7 +70,7 @@ class BeMyValentine(commands.Cog): raise commands.UserInputError("Come on, you can't send a valentine to yourself :expressionless:") emoji_1, emoji_2 = self.random_emoji() - channel = self.bot.get_channel(Channels.community_bot_commands) + channel = self.bot.get_channel(Channels.sir_lancebot_playground) valentine, title = self.valentine_check(valentine_type) embed = discord.Embed( diff --git a/bot/exts/holidays/valentines/lovecalculator.py b/bot/exts/holidays/valentines/lovecalculator.py index 99fba150..10dea9df 100644 --- a/bot/exts/holidays/valentines/lovecalculator.py +++ b/bot/exts/holidays/valentines/lovecalculator.py @@ -32,7 +32,7 @@ class LoveCalculator(Cog): Tells you how much the two love each other. This command requires at least one member as input, if two are given love will be calculated between - those two users, if only one is given, the second member is asusmed to be the invoker. + those two users, if only one is given, the second member is assumed to be the invoker. Members are converted from: - User ID - Mention @@ -51,7 +51,7 @@ class LoveCalculator(Cog): raise BadArgument( "This command can only be ran against members with the lovefest role! " "This role be can assigned by running " - f"`{PYTHON_PREFIX}subscribe` in <#{Channels.bot}>." + f"`{PYTHON_PREFIX}subscribe` in <#{Channels.bot_commands}>." ) if whom is None: diff --git a/bot/utils/checks.py b/bot/utils/checks.py index 8c426ed7..5433f436 100644 --- a/bot/utils/checks.py +++ b/bot/utils/checks.py @@ -33,7 +33,7 @@ def in_whitelist_check( channels: Container[int] = (), categories: Container[int] = (), roles: Container[int] = (), - redirect: Optional[int] = constants.Channels.community_bot_commands, + redirect: Optional[int] = constants.Channels.sir_lancebot_playground, fail_silently: bool = False, ) -> bool: """ diff --git a/bot/utils/decorators.py b/bot/utils/decorators.py index 7a3b14ad..8954e016 100644 --- a/bot/utils/decorators.py +++ b/bot/utils/decorators.py @@ -257,10 +257,10 @@ def whitelist_check(**default_kwargs: Container[int]) -> Callable[[Context], boo channels = set(kwargs.get("channels") or {}) categories = kwargs.get("categories") - # Only output override channels + community_bot_commands + # Only output override channels + sir_lancebot_playground if channels: default_whitelist_channels = set(WHITELISTED_CHANNELS) - default_whitelist_channels.discard(Channels.community_bot_commands) + default_whitelist_channels.discard(Channels.sir_lancebot_playground) channels.difference_update(default_whitelist_channels) # Add all whitelisted category channels, but skip if we're in DMs -- cgit v1.2.3 From 58e33b1b3dbf623ac4e7796a392dd8446b809744 Mon Sep 17 00:00:00 2001 From: Chris Lovering Date: Sun, 20 Feb 2022 12:20:24 +0000 Subject: Don't call bot.run() if IN_CI env var is set --- bot/__main__.py | 3 ++- bot/constants.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) (limited to 'bot/constants.py') diff --git a/bot/__main__.py b/bot/__main__.py index 6889fe2b..0bf7b398 100644 --- a/bot/__main__.py +++ b/bot/__main__.py @@ -12,4 +12,5 @@ bot.add_check(whitelist_check(channels=WHITELISTED_CHANNELS, roles=STAFF_ROLES)) for ext in walk_extensions(): bot.load_extension(ext) -bot.run(Client.token) +if not Client.in_ci: + bot.run(Client.token) diff --git a/bot/constants.py b/bot/constants.py index b4d7bc24..5d876d97 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -143,6 +143,7 @@ class Client(NamedTuple): prefix = environ.get("PREFIX", ".") token = environ.get("BOT_TOKEN") debug = environ.get("BOT_DEBUG", "true").lower() == "true" + in_ci = environ.get("IN_CI", "false").lower() == "true" github_bot_repo = "https://github.com/python-discord/sir-lancebot" # Override seasonal locks: 1 (January) to 12 (December) month_override = int(environ["MONTH_OVERRIDE"]) if "MONTH_OVERRIDE" in environ else None -- cgit v1.2.3 From a468379dec50a69b1932516e3ed66ce0f157fd2b Mon Sep 17 00:00:00 2001 From: Gustav Odinger <65498475+gustavwilliam@users.noreply.github.com> Date: Mon, 14 Mar 2022 01:56:00 +0100 Subject: Add Twemoji utility command (#988) Co-authored-by: Xithrius <15021300+Xithrius@users.noreply.github.com> --- bot/constants.py | 1 + bot/exts/utilities/twemoji.py | 150 ++++++++++++++++++++++++++++++++++++++++++ poetry.lock | 16 ++++- pyproject.toml | 1 + 4 files changed, 167 insertions(+), 1 deletion(-) create mode 100644 bot/exts/utilities/twemoji.py (limited to 'bot/constants.py') diff --git a/bot/constants.py b/bot/constants.py index 5d876d97..da81a089 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -157,6 +157,7 @@ class Logging(NamedTuple): class Colours: blue = 0x0279FD + twitter_blue = 0x1DA1F2 bright_green = 0x01D277 dark_green = 0x1F8B4C orange = 0xE67E22 diff --git a/bot/exts/utilities/twemoji.py b/bot/exts/utilities/twemoji.py new file mode 100644 index 00000000..c915f05b --- /dev/null +++ b/bot/exts/utilities/twemoji.py @@ -0,0 +1,150 @@ +import logging +import re +from typing import Literal, Optional + +import discord +from discord.ext import commands +from emoji import UNICODE_EMOJI_ENGLISH, is_emoji + +from bot.bot import Bot +from bot.constants import Colours, Roles +from bot.utils.decorators import whitelist_override +from bot.utils.extensions import invoke_help_command + +log = logging.getLogger(__name__) +BASE_URLS = { + "png": "https://raw.githubusercontent.com/twitter/twemoji/master/assets/72x72/", + "svg": "https://raw.githubusercontent.com/twitter/twemoji/master/assets/svg/", +} +CODEPOINT_REGEX = re.compile(r"[a-f1-9][a-f0-9]{3,5}$") + + +class Twemoji(commands.Cog): + """Utilities for working with Twemojis.""" + + def __init__(self, bot: Bot): + self.bot = bot + + @staticmethod + def get_url(codepoint: str, format: Literal["png", "svg"]) -> str: + """Returns a source file URL for the specified Twemoji, in the corresponding format.""" + return f"{BASE_URLS[format]}{codepoint}.{format}" + + @staticmethod + def alias_to_name(alias: str) -> str: + """ + Transform a unicode alias to an emoji name. + + Example usages: + >>> alias_to_name(":falling_leaf:") + "Falling leaf" + >>> alias_to_name(":family_man_girl_boy:") + "Family man girl boy" + """ + name = alias.strip(":").replace("_", " ") + return name.capitalize() + + @staticmethod + def build_embed(codepoint: str) -> discord.Embed: + """Returns the main embed for the `twemoji` commmand.""" + emoji = "".join(Twemoji.emoji(e) or "" for e in codepoint.split("-")) + + embed = discord.Embed( + title=Twemoji.alias_to_name(UNICODE_EMOJI_ENGLISH[emoji]), + description=f"{codepoint.replace('-', ' ')}\n[Download svg]({Twemoji.get_url(codepoint, 'svg')})", + colour=Colours.twitter_blue, + ) + embed.set_thumbnail(url=Twemoji.get_url(codepoint, "png")) + return embed + + @staticmethod + def emoji(codepoint: Optional[str]) -> Optional[str]: + """ + Returns the emoji corresponding to a given `codepoint`, or `None` if no emoji was found. + + The return value is an emoji character, such as "πŸ‚". The `codepoint` + argument can be of any format, since it will be trimmed automatically. + """ + if code := Twemoji.trim_code(codepoint): + return chr(int(code, 16)) + + @staticmethod + def codepoint(emoji: Optional[str]) -> Optional[str]: + """ + Returns the codepoint, in a trimmed format, of a single emoji. + + `emoji` should be an emoji character, such as "🐍" and "πŸ₯°", and + not a codepoint like "1f1f8". When working with combined emojis, + such as "πŸ‡ΈπŸ‡ͺ" and "πŸ‘¨β€πŸ‘©β€πŸ‘¦", send the component emojis through the method + one at a time. + """ + if emoji is None: + return None + return hex(ord(emoji)).removeprefix("0x") + + @staticmethod + def trim_code(codepoint: Optional[str]) -> Optional[str]: + """ + Returns the meaningful information from the given `codepoint`. + + If no codepoint is found, `None` is returned. + + Example usages: + >>> trim_code("U+1f1f8") + "1f1f8" + >>> trim_code("\u0001f1f8") + "1f1f8" + >>> trim_code("1f466") + "1f466" + """ + if code := CODEPOINT_REGEX.search(codepoint or ""): + return code.group() + + @staticmethod + def codepoint_from_input(raw_emoji: tuple[str, ...]) -> str: + """ + Returns the codepoint corresponding to the passed tuple, separated by "-". + + The return format matches the format used in URLs for Twemoji source files. + + Example usages: + >>> codepoint_from_input(("🐍",)) + "1f40d" + >>> codepoint_from_input(("1f1f8", "1f1ea")) + "1f1f8-1f1ea" + >>> codepoint_from_input(("πŸ‘¨β€πŸ‘§β€πŸ‘¦",)) + "1f468-200d-1f467-200d-1f466" + """ + raw_emoji = [emoji.lower() for emoji in raw_emoji] + if is_emoji(raw_emoji[0]): + emojis = (Twemoji.codepoint(emoji) or "" for emoji in raw_emoji[0]) + return "-".join(emojis) + + emoji = "".join( + Twemoji.emoji(Twemoji.trim_code(code)) or "" for code in raw_emoji + ) + if is_emoji(emoji): + return "-".join(Twemoji.codepoint(e) or "" for e in emoji) + + raise ValueError("No codepoint could be obtained from the given input") + + @commands.command(aliases=("tw",)) + @whitelist_override(roles=(Roles.everyone,)) + async def twemoji(self, ctx: commands.Context, *raw_emoji: str) -> None: + """Sends a preview of a given Twemoji, specified by codepoint or emoji.""" + if len(raw_emoji) == 0: + await invoke_help_command(ctx) + return + try: + codepoint = self.codepoint_from_input(raw_emoji) + except ValueError: + raise commands.BadArgument( + "please include a valid emoji or emoji codepoint." + ) + + await ctx.send(embed=self.build_embed(codepoint)) + + +def setup(bot: Bot) -> None: + """Load the Twemoji cog.""" + bot.add_cog(Twemoji(bot)) diff --git a/poetry.lock b/poetry.lock index 68bfc43a..56eb53d9 100644 --- a/poetry.lock +++ b/poetry.lock @@ -202,6 +202,17 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "emoji" +version = "1.6.3" +description = "Emoji for Python" +category = "main" +optional = false +python-versions = "*" + +[package.extras] +dev = ["pytest", "coverage", "coveralls"] + [[package]] name = "emojis" version = "0.6.0" @@ -824,7 +835,7 @@ multidict = ">=4.0" [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "e824a5fa909d43e861478178ad7e77ee04be05a60cd3028bda8bd4754c848616" +content-hash = "27075494e06333e5934e751f9847f419efb712b6d4d4e6173785d47319de1f29" [metadata.files] aiodns = [ @@ -975,6 +986,9 @@ distlib = [ {file = "distlib-0.3.4-py2.py3-none-any.whl", hash = "sha256:6564fe0a8f51e734df6333d08b8b94d4ea8ee6b99b5ed50613f731fd4089f34b"}, {file = "distlib-0.3.4.zip", hash = "sha256:e4b58818180336dc9c529bfb9a0b58728ffc09ad92027a3f30b7cd91e3458579"}, ] +emoji = [ + {file = "emoji-1.6.3.tar.gz", hash = "sha256:cc28bdc1010b1c03c241f69c7af1e8715144ef45a273bfadc14dc89319ba26d0"}, +] emojis = [ {file = "emojis-0.6.0-py3-none-any.whl", hash = "sha256:7da34c8a78ae262fd68cef9e2c78a3c1feb59784489eeea0f54ba1d4b7111c7c"}, {file = "emojis-0.6.0.tar.gz", hash = "sha256:bf605d1f1a27a81cd37fe82eb65781c904467f569295a541c33710b97e4225ec"}, diff --git a/pyproject.toml b/pyproject.toml index 7d3f0a5e..a72fa706 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,6 +21,7 @@ emojis = "~=0.6.0" coloredlogs = "~=15.0" colorama = { version = "~=0.4.3", markers = "sys_platform == 'win32'" } lxml = "~=4.6" +emoji = "^1.6.1" [tool.poetry.dev-dependencies] flake8 = "~=3.8" -- cgit v1.2.3