aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Dennis Pham <[email protected]>2020-06-07 16:00:38 -0400
committerGravatar GitHub <[email protected]>2020-06-07 16:00:38 -0400
commitc88b5d1225716d5b09bf1c8cfbb0758ea6f1f65b (patch)
treedc7caf29552ec9ceca14b9d63572e0be53a6fd66
parentFix AttributeError for category check (diff)
parentEnable the 'redis' / 'aiohttp' Sentry integrations (diff)
Merge branch 'master' into bug/backend/stats-category-check
-rw-r--r--bot/__main__.py8
-rw-r--r--bot/cogs/information.py65
-rw-r--r--bot/cogs/moderation/scheduler.py41
-rw-r--r--bot/cogs/site.py3
-rw-r--r--tests/bot/cogs/test_information.py12
5 files changed, 96 insertions, 33 deletions
diff --git a/bot/__main__.py b/bot/__main__.py
index aa1d1aee8..4e0d4a111 100644
--- a/bot/__main__.py
+++ b/bot/__main__.py
@@ -3,7 +3,9 @@ import logging
import discord
import sentry_sdk
from discord.ext.commands import when_mentioned_or
+from sentry_sdk.integrations.aiohttp import AioHttpIntegration
from sentry_sdk.integrations.logging import LoggingIntegration
+from sentry_sdk.integrations.redis import RedisIntegration
from bot import constants, patches
from bot.bot import Bot
@@ -15,7 +17,11 @@ sentry_logging = LoggingIntegration(
sentry_sdk.init(
dsn=constants.Bot.sentry_dsn,
- integrations=[sentry_logging]
+ integrations=[
+ sentry_logging,
+ AioHttpIntegration(),
+ RedisIntegration(),
+ ]
)
bot = Bot(
diff --git a/bot/cogs/information.py b/bot/cogs/information.py
index f0eb3a1ea..f0bd1afdb 100644
--- a/bot/cogs/information.py
+++ b/bot/cogs/information.py
@@ -6,7 +6,8 @@ from collections import Counter, defaultdict
from string import Template
from typing import Any, Mapping, Optional, Union
-from discord import Colour, Embed, Member, Message, Role, Status, utils
+from discord import ChannelType, Colour, Embed, Guild, Member, Message, Role, Status, utils
+from discord.abc import GuildChannel
from discord.ext.commands import BucketType, Cog, Context, Paginator, command, group
from discord.utils import escape_markdown
@@ -26,6 +27,49 @@ class Information(Cog):
def __init__(self, bot: Bot):
self.bot = bot
+ @staticmethod
+ def role_can_read(channel: GuildChannel, role: Role) -> bool:
+ """Return True if `role` can read messages in `channel`."""
+ overwrites = channel.overwrites_for(role)
+ return overwrites.read_messages is True
+
+ def get_staff_channel_count(self, guild: Guild) -> int:
+ """
+ Get the number of channels that are staff-only.
+
+ We need to know two things about a channel:
+ - Does the @everyone role have explicit read deny permissions?
+ - Do staff roles have explicit read allow permissions?
+
+ If the answer to both of these questions is yes, it's a staff channel.
+ """
+ channel_ids = set()
+ for channel in guild.channels:
+ if channel.type is ChannelType.category:
+ continue
+
+ everyone_can_read = self.role_can_read(channel, guild.default_role)
+
+ for role in constants.STAFF_ROLES:
+ role_can_read = self.role_can_read(channel, guild.get_role(role))
+ if role_can_read and not everyone_can_read:
+ channel_ids.add(channel.id)
+ break
+
+ return len(channel_ids)
+
+ @staticmethod
+ def get_channel_type_counts(guild: Guild) -> str:
+ """Return the total amounts of the various types of channels in `guild`."""
+ channel_counter = Counter(c.type for c in guild.channels)
+ channel_type_list = []
+ for channel, count in channel_counter.items():
+ channel_type = str(channel).title()
+ channel_type_list.append(f"{channel_type} channels: {count}")
+
+ channel_type_list = sorted(channel_type_list)
+ return "\n".join(channel_type_list)
+
@with_role(*constants.MODERATION_ROLES)
@command(name="roles")
async def roles_info(self, ctx: Context) -> None:
@@ -102,15 +146,16 @@ class Information(Cog):
roles = len(ctx.guild.roles)
member_count = ctx.guild.member_count
-
- # How many of each type of channel?
- channels = Counter(c.type for c in ctx.guild.channels)
- channel_counts = "".join(sorted(f"{str(ch).title()} channels: {channels[ch]}\n" for ch in channels)).strip()
+ channel_counts = self.get_channel_type_counts(ctx.guild)
# How many of each user status?
statuses = Counter(member.status for member in ctx.guild.members)
embed = Embed(colour=Colour.blurple())
+ # How many staff members and staff channels do we have?
+ staff_member_count = len(ctx.guild.get_role(constants.Roles.helpers).members)
+ staff_channel_count = self.get_staff_channel_count(ctx.guild)
+
# Because channel_counts lacks leading whitespace, it breaks the dedent if it's inserted directly by the
# f-string. While this is correctly formated by Discord, it makes unit testing difficult. To keep the formatting
# without joining a tuple of strings we can use a Template string to insert the already-formatted channel_counts
@@ -122,12 +167,16 @@ class Information(Cog):
Voice region: {region}
Features: {features}
- **Counts**
+ **Channel counts**
+ $channel_counts
+ Staff channels: {staff_channel_count}
+
+ **Member counts**
Members: {member_count:,}
+ Staff members: {staff_member_count}
Roles: {roles}
- $channel_counts
- **Members**
+ **Member statuses**
{constants.Emojis.status_online} {statuses[Status.online]:,}
{constants.Emojis.status_idle} {statuses[Status.idle]:,}
{constants.Emojis.status_dnd} {statuses[Status.dnd]:,}
diff --git a/bot/cogs/moderation/scheduler.py b/bot/cogs/moderation/scheduler.py
index f0a3ad1b1..b03d89537 100644
--- a/bot/cogs/moderation/scheduler.py
+++ b/bot/cogs/moderation/scheduler.py
@@ -106,6 +106,27 @@ class InfractionScheduler(Scheduler):
log_content = None
failed = False
+ # DM the user about the infraction if it's not a shadow/hidden infraction.
+ # This needs to happen before we apply the infraction, as the bot cannot
+ # send DMs to user that it doesn't share a guild with. If we were to
+ # apply kick/ban infractions first, this would mean that we'd make it
+ # impossible for us to deliver a DM. See python-discord/bot#982.
+ if not infraction["hidden"]:
+ dm_result = f"{constants.Emojis.failmail} "
+ dm_log_text = "\nDM: **Failed**"
+
+ # Sometimes user is a discord.Object; make it a proper user.
+ try:
+ if not isinstance(user, (discord.Member, discord.User)):
+ user = await self.bot.fetch_user(user.id)
+ except discord.HTTPException as e:
+ log.error(f"Failed to DM {user.id}: could not fetch user (status {e.status})")
+ else:
+ # Accordingly display whether the user was successfully notified via DM.
+ if await utils.notify_infraction(user, infr_type, expiry, reason, icon):
+ dm_result = ":incoming_envelope: "
+ dm_log_text = "\nDM: Sent"
+
if infraction["actor"] == self.bot.user.id:
log.trace(
f"Infraction #{id_} actor is bot; including the reason in the confirmation message."
@@ -150,27 +171,7 @@ class InfractionScheduler(Scheduler):
log.exception(log_msg)
failed = True
- # DM the user about the infraction if it's not a shadow/hidden infraction.
- # Don't send DM when applying failed.
- if not infraction["hidden"] and not failed:
- dm_result = f"{constants.Emojis.failmail} "
- dm_log_text = "\nDM: **Failed**"
-
- # Sometimes user is a discord.Object; make it a proper user.
- try:
- if not isinstance(user, (discord.Member, discord.User)):
- user = await self.bot.fetch_user(user.id)
- except discord.HTTPException as e:
- log.error(f"Failed to DM {user.id}: could not fetch user (status {e.status})")
- else:
- # Accordingly display whether the user was successfully notified via DM.
- if await utils.notify_infraction(user, infr_type, expiry, reason, icon):
- dm_result = ":incoming_envelope: "
- dm_log_text = "\nDM: Sent"
-
if failed:
- dm_log_text = "\nDM: **Canceled**"
- dm_result = f"{constants.Emojis.failmail} "
log.trace(f"Deleted infraction {infraction['id']} from database because applying infraction failed.")
try:
await self.bot.api_client.delete(f"bot/infractions/{id_}")
diff --git a/bot/cogs/site.py b/bot/cogs/site.py
index 7fc2a9c34..e61cd5003 100644
--- a/bot/cogs/site.py
+++ b/bot/cogs/site.py
@@ -133,6 +133,9 @@ class Site(Cog):
await ctx.send(f":x: Invalid rule indices: {indices}")
return
+ for rule in rules:
+ self.bot.stats.incr(f"rule_uses.{rule}")
+
final_rules = tuple(f"**{pick}.** {full_rules[pick - 1]}" for pick in rules)
await LinePaginator.paginate(final_rules, ctx, rules_embed, max_lines=3)
diff --git a/tests/bot/cogs/test_information.py b/tests/bot/cogs/test_information.py
index aca6b594f..79c0e0ad3 100644
--- a/tests/bot/cogs/test_information.py
+++ b/tests/bot/cogs/test_information.py
@@ -148,14 +148,18 @@ class InformationCogTests(unittest.TestCase):
Voice region: {self.ctx.guild.region}
Features: {', '.join(self.ctx.guild.features)}
- **Counts**
- Members: {self.ctx.guild.member_count:,}
- Roles: {len(self.ctx.guild.roles)}
+ **Channel counts**
Category channels: 1
Text channels: 1
Voice channels: 1
+ Staff channels: 0
+
+ **Member counts**
+ Members: {self.ctx.guild.member_count:,}
+ Staff members: 0
+ Roles: {len(self.ctx.guild.roles)}
- **Members**
+ **Member statuses**
{constants.Emojis.status_online} 2
{constants.Emojis.status_idle} 1
{constants.Emojis.status_dnd} 4