Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions bot/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
# Feature cogs
bot.load_extension("bot.cogs.alias")
bot.load_extension("bot.cogs.defcon")
bot.load_extension("bot.cogs.dm_relay")
bot.load_extension("bot.cogs.duck_pond")
bot.load_extension("bot.cogs.eval")
bot.load_extension("bot.cogs.information")
Expand Down
106 changes: 106 additions & 0 deletions bot/cogs/dm_relay.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import logging

import discord
from discord import Color
from discord.ext import commands
from discord.ext.commands import Cog

from bot import constants
from bot.bot import Bot
from bot.utils.checks import in_whitelist_check, with_role_check
from bot.utils.messages import send_attachments
from bot.utils.webhooks import send_webhook

log = logging.getLogger(__name__)


class DMRelay(Cog):
"""Relay direct messages to and from the bot."""

def __init__(self, bot: Bot):
self.bot = bot
self.webhook_id = constants.Webhooks.dm_log
self.webhook = None
self.bot.loop.create_task(self.fetch_webhook())

@commands.command(aliases=("reply",))
async def send_dm(self, ctx: commands.Context, member: discord.Member, *, message: str) -> None:
"""
Allows you to send a DM to a user from the bot.

A `member` must be provided.

This feature should be used extremely sparingly. Use ModMail if you need to have a serious
conversation with a user. This is just for responding to extraordinary DMs, having a little
fun with users, and telling people they are DMing the wrong bot.

NOTE: This feature will be removed if it is overused.
"""
try:
await member.send(message)
await ctx.message.add_reaction("✅")
return

except discord.errors.Forbidden:
log.debug("User has disabled DMs.")
await ctx.message.add_reaction("❌")

async def fetch_webhook(self) -> None:
"""Fetches the webhook object, so we can post to it."""
await self.bot.wait_until_guild_available()

try:
self.webhook = await self.bot.fetch_webhook(self.webhook_id)
except discord.HTTPException:
log.exception(f"Failed to fetch webhook with id `{self.webhook_id}`")
Comment thread
lemonsaurus marked this conversation as resolved.

@Cog.listener()
async def on_message(self, message: discord.Message) -> None:
"""Relays the message's content and attachments to the dm_log channel."""
# Only relay DMs from humans
if message.author.bot or message.guild or self.webhook is None:
return

if message.clean_content:
await send_webhook(
webhook=self.webhook,
content=message.clean_content,
username=message.author.display_name,
avatar_url=message.author.avatar_url
)

# Handle any attachments
if message.attachments:
try:
await send_attachments(message, self.webhook)
except (discord.errors.Forbidden, discord.errors.NotFound):
e = discord.Embed(
description=":x: **This message contained an attachment, but it could not be retrieved**",
color=Color.red()
)
await send_webhook(
webhook=self.webhook,
embed=e,
username=message.author.display_name,
avatar_url=message.author.avatar_url
)
except discord.HTTPException:
log.exception("Failed to send an attachment to the webhook")

def cog_check(self, ctx: commands.Context) -> bool:
"""Only allow moderators to invoke the commands in this cog."""
checks = [
with_role_check(ctx, *constants.MODERATION_ROLES),
in_whitelist_check(
ctx,
channels=[constants.Channels.dm_log],
redirect=None,
fail_silently=True,
)
]
return all(checks)


def setup(bot: Bot) -> None:
"""Load the DMRelay cog."""
bot.add_cog(DMRelay(bot))
34 changes: 9 additions & 25 deletions bot/cogs/duck_pond.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import logging
from typing import Optional, Union
from typing import Union

import discord
from discord import Color, Embed, Member, Message, RawReactionActionEvent, User, errors
from discord.ext.commands import Cog

from bot import constants
from bot.bot import Bot
from bot.utils.messages import send_attachments, sub_clyde
from bot.utils.messages import send_attachments
from bot.utils.webhooks import send_webhook

log = logging.getLogger(__name__)

Expand All @@ -18,6 +19,7 @@ class DuckPond(Cog):
def __init__(self, bot: Bot):
self.bot = bot
self.webhook_id = constants.Webhooks.duck_pond
self.webhook = None
self.bot.loop.create_task(self.fetch_webhook())

async def fetch_webhook(self) -> None:
Expand Down Expand Up @@ -47,24 +49,6 @@ async def has_green_checkmark(self, message: Message) -> bool:
return True
return False

async def send_webhook(
self,
content: Optional[str] = None,
username: Optional[str] = None,
avatar_url: Optional[str] = None,
embed: Optional[Embed] = None,
) -> None:
"""Send a webhook to the duck_pond channel."""
try:
await self.webhook.send(
content=content,
username=sub_clyde(username),
avatar_url=avatar_url,
embed=embed
)
except discord.HTTPException:
log.exception("Failed to send a message to the Duck Pool webhook")

async def count_ducks(self, message: Message) -> int:
"""
Count the number of ducks in the reactions of a specific message.
Expand Down Expand Up @@ -94,10 +78,9 @@ async def count_ducks(self, message: Message) -> int:

async def relay_message(self, message: Message) -> None:
"""Relays the message's content and attachments to the duck pond channel."""
clean_content = message.clean_content

if clean_content:
await self.send_webhook(
if message.clean_content:
await send_webhook(
webhook=self.webhook,
content=message.clean_content,
username=message.author.display_name,
avatar_url=message.author.avatar_url
Expand All @@ -111,7 +94,8 @@ async def relay_message(self, message: Message) -> None:
description=":x: **This message contained an attachment, but it could not be retrieved**",
color=Color.red()
)
await self.send_webhook(
await send_webhook(
webhook=self.webhook,
embed=e,
username=message.author.display_name,
avatar_url=message.author.avatar_url
Expand Down
72 changes: 31 additions & 41 deletions bot/cogs/python_news.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

from bot import constants
from bot.bot import Bot
from bot.utils.messages import sub_clyde
from bot.utils.webhooks import send_webhook

PEPS_RSS_URL = "https://www.python.org/dev/peps/peps.rss/"

Expand Down Expand Up @@ -100,13 +100,21 @@ async def post_pep_news(self) -> None:
):
continue

msg = await self.send_webhook(
# Build an embed and send a webhook
embed = discord.Embed(
title=new["title"],
description=new["summary"],
timestamp=new_datetime,
url=new["link"],
webhook_profile_name=data["feed"]["title"],
footer=data["feed"]["title"]
colour=constants.Colours.soft_green
)
embed.set_footer(text=data["feed"]["title"], icon_url=AVATAR_URL)
msg = await send_webhook(
webhook=self.webhook,
username=data["feed"]["title"],
embed=embed,
avatar_url=AVATAR_URL,
wait=True,
)
payload["data"]["pep"].append(pep_nr)

Expand Down Expand Up @@ -161,15 +169,29 @@ async def post_maillist_news(self) -> None:

content = email_information["content"]
link = THREAD_URL.format(id=thread["href"].split("/")[-2], list=maillist)
msg = await self.send_webhook(

# Build an embed and send a message to the webhook
embed = discord.Embed(
title=thread_information["subject"],
description=content[:500] + f"... [continue reading]({link})" if len(content) > 500 else content,
timestamp=new_date,
url=link,
author=f"{email_information['sender_name']} ({email_information['sender']['address']})",
author_url=MAILMAN_PROFILE_URL.format(id=email_information["sender"]["mailman_id"]),
webhook_profile_name=self.webhook_names[maillist],
footer=f"Posted to {self.webhook_names[maillist]}"
colour=constants.Colours.soft_green
)
embed.set_author(
name=f"{email_information['sender_name']} ({email_information['sender']['address']})",
url=MAILMAN_PROFILE_URL.format(id=email_information["sender"]["mailman_id"]),
)
embed.set_footer(
text=f"Posted to {self.webhook_names[maillist]}",
icon_url=AVATAR_URL,
)
msg = await send_webhook(
webhook=self.webhook,
username=self.webhook_names[maillist],
embed=embed,
avatar_url=AVATAR_URL,
wait=True,
)
payload["data"][maillist].append(thread_information["thread_id"])

Expand All @@ -182,38 +204,6 @@ async def post_maillist_news(self) -> None:

await self.bot.api_client.put("bot/bot-settings/news", json=payload)

async def send_webhook(self,
title: str,
description: str,
timestamp: datetime,
url: str,
webhook_profile_name: str,
footer: str,
author: t.Optional[str] = None,
author_url: t.Optional[str] = None,
) -> discord.Message:
"""Send webhook entry and return sent message."""
embed = discord.Embed(
title=title,
description=description,
timestamp=timestamp,
url=url,
colour=constants.Colours.soft_green
)
if author and author_url:
embed.set_author(
name=author,
url=author_url
)
embed.set_footer(text=footer, icon_url=AVATAR_URL)

return await self.webhook.send(
embed=embed,
username=sub_clyde(webhook_profile_name),
avatar_url=AVATAR_URL,
wait=True
)

async def get_thread_and_first_mail(self, maillist: str, thread_identifier: str) -> t.Tuple[t.Any, t.Any]:
"""Get mail thread and first mail from mail.python.org based on `maillist` and `thread_identifier`."""
async with self.bot.http_session.get(
Expand Down
3 changes: 3 additions & 0 deletions bot/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,7 @@ class Channels(metaclass=YAMLGetter):
dev_contrib: int
dev_core: int
dev_log: int
dm_log: int
esoteric: int
helpers: int
how_to_get_help: int
Expand Down Expand Up @@ -427,6 +428,7 @@ class Webhooks(metaclass=YAMLGetter):
reddit: int
duck_pond: int
dev_log: int
dm_log: int


class Roles(metaclass=YAMLGetter):
Expand Down Expand Up @@ -460,6 +462,7 @@ class Guild(metaclass=YAMLGetter):
staff_channels: List[int]
staff_roles: List[int]


class Keys(metaclass=YAMLGetter):
section = "keys"

Expand Down
34 changes: 34 additions & 0 deletions bot/utils/webhooks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import logging
from typing import Optional

import discord
from discord import Embed

from bot.utils.messages import sub_clyde

log = logging.getLogger(__name__)


async def send_webhook(
webhook: discord.Webhook,
content: Optional[str] = None,
username: Optional[str] = None,
avatar_url: Optional[str] = None,
embed: Optional[Embed] = None,
wait: Optional[bool] = False
) -> discord.Message:
Comment thread
lemonsaurus marked this conversation as resolved.
"""
Send a message using the provided webhook.

This uses sub_clyde() and tries for an HTTPException to ensure it doesn't crash.
"""
try:
return await webhook.send(
content=content,
username=sub_clyde(username),
avatar_url=avatar_url,
embed=embed,
wait=wait,
)
except discord.HTTPException:
log.exception("Failed to send a message to the webhook!")
4 changes: 2 additions & 2 deletions config-default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ guild:
mod_log: &MOD_LOG 282638479504965634
user_log: 528976905546760203
voice_log: 640292421988646961
dm_log: 653713721625018428

# Off-topic
off_topic_0: 291284109232308226
Expand Down Expand Up @@ -251,10 +252,9 @@ guild:
duck_pond: 637821475327311927
dev_log: 680501655111729222
python_news: &PYNEWS_WEBHOOK 704381182279942324

dm_log: 654567640664244225

filter:

# What do we filter?
filter_zalgo: false
filter_invites: true
Expand Down
Loading