Brandon209-Red-bot-Cogs/welcome/welcome.py

918 lines
36 KiB
Python
Raw Normal View History

import asyncio
import datetime
import discord
import logging
import random
from typing import Optional, Union, Literal
from redbot.core import Config, checks, commands
from redbot.core.utils.chat_formatting import box, pagify, humanize_list
from .enums import WhisperType
from .errors import WhisperError
__author__ = "tmerc"
log = logging.getLogger("red.tmerc.welcome")
ENABLED = "enabled"
DISABLED = "disabled"
class Welcome(commands.Cog):
"""Announce when users join or leave a server."""
default_join = "Welcome {member.mention} to {server.name}!"
default_leave = "{member.name} has left {server.name}!"
default_ban = "{member.name} has been banned from {server.name}!"
default_unban = "{member.name} has been unbanned from {server.name}!"
default_whisper = "Hey there {member.name}, welcome to {server.name}!"
guild_defaults = {
"enabled": False,
"channel": None,
"date": None,
"join": {
"enabled": True,
"channel": None,
"delete": False,
"last": None,
"counter": 0,
"whisper": {"state": "off", "message": default_whisper},
"messages": [default_join],
"bot": None,
},
"leave": {
"enabled": True,
"channel": None,
"delete": False,
"last": None,
"counter": 0,
"messages": [default_leave],
},
"ban": {
"enabled": True,
"channel": None,
"delete": False,
"last": None,
"counter": 0,
"messages": [default_ban],
},
"unban": {
"enabled": True,
"channel": None,
"delete": False,
"last": None,
"counter": 0,
"messages": [default_unban],
},
}
def __init__(self, *args, **kwargs) -> None:
super().__init__()
self.bot = kwargs["bot"]
self.config = Config.get_conf(self, 86345009)
self.config.register_guild(**self.guild_defaults)
@commands.group(aliases=["welcome"])
@commands.guild_only()
@checks.admin_or_permissions(manage_guild=True)
async def welcomeset(self, ctx: commands.Context) -> None:
"""Change Welcome settings."""
await ctx.trigger_typing()
if ctx.invoked_subcommand is None:
guild: discord.Guild = ctx.guild
c = await self.config.guild(guild).all()
channel = await self.__get_channel(guild, "default")
join_channel = await self.__get_channel(guild, "join")
leave_channel = await self.__get_channel(guild, "leave")
ban_channel = await self.__get_channel(guild, "ban")
unban_channel = await self.__get_channel(guild, "unban")
j = c["join"]
jw = j["whisper"]
v = c["leave"]
b = c["ban"]
u = c["unban"]
whisper_message = jw["message"] if len(jw["message"]) <= 50 else jw["message"][:50] + "..."
if await ctx.embed_requested():
emb = discord.Embed(color=await ctx.embed_color(), title="Current Welcome Settings")
emb.add_field(
name="General",
inline=False,
value=f"**Enabled:** {c['enabled']}\n**Channel:** {channel.mention}\n",
)
emb.add_field(
name="Join",
inline=False,
value=(
f"**Enabled:** {j['enabled']}\n"
f"**Channel:** {join_channel.mention}\n"
f"**Delete previous:** {j['delete']}\n"
f"**Whisper state:** {jw['state']}\n"
f"**Whisper message:** {whisper_message}\n"
f"**Messages:** {len(j['messages'])}; do `{ctx.prefix}welcomeset join msg list` for a list\n"
f"**Bot message:** {j['bot']}"
),
)
emb.add_field(
name="Leave",
inline=False,
value=(
f"**Enabled:** {v['enabled']}\n"
f"**Channel:** {leave_channel.mention}\n"
f"**Delete previous:** {v['delete']}\n"
f"**Messages:** {len(v['messages'])}; do `{ctx.prefix}welcomeset leave msg list` for a list\n"
),
)
emb.add_field(
name="Ban",
inline=False,
value=(
f"**Enabled:** {b['enabled']}\n"
f"**Channel:** {ban_channel.mention}\n"
f"**Delete previous:** {b['delete']}\n"
f"**Messages:** {len(b['messages'])}; do `{ctx.prefix}welcomeset ban msg list` for a list\n"
),
)
emb.add_field(
name="Unban",
inline=False,
value=(
f"**Enabled:** {u['enabled']}\n"
f"**Channel:** {unban_channel.mention}\n"
f"**Delete previous:** {u['delete']}\n"
f"**Messages:** {len(u['messages'])}; do `{ctx.prefix}welcomeset unban msg list` for a list\n"
),
)
await ctx.send(embed=emb)
else:
msg = box(
f" Enabled: {c['enabled']}\n"
f" Channel: {channel}\n"
f" Join:\n"
f" Enabled: {j['enabled']}\n"
f" Channel: {join_channel}\n"
f" Delete previous: {j['delete']}\n"
f" Whisper:\n"
f" State: {jw['state']}\n"
f" Message: {whisper_message}\n"
f" Messages: {len(j['messages'])}; do '{ctx.prefix}welcomeset join msg list' for a list\n"
f" Bot message: {j['bot']}\n"
f" Leave:\n"
f" Enabled: {v['enabled']}\n"
f" Channel: {leave_channel}\n"
f" Delete previous: {v['delete']}\n"
f" Messages: {len(v['messages'])}; do '{ctx.prefix}welcomeset leave msg list' for a list\n"
f" Ban:\n"
f" Enabled: {b['enabled']}\n"
f" Channel: {ban_channel}\n"
f" Delete previous: {b['delete']}\n"
f" Messages: {len(b['messages'])}; do '{ctx.prefix}welcomeset ban msg list' for a list\n"
f" Unban:\n"
f" Enabled: {u['enabled']}\n"
f" Channel: {unban_channel}\n"
f" Delete previous: {u['delete']}\n"
f" Messages: {len(u['messages'])}; do '{ctx.prefix}welcomeset unban msg list' for a list\n",
"Current Welcome Settings",
)
await ctx.send(msg)
@welcomeset.command(name="toggle")
async def welcomeset_toggle(self, ctx: commands.Context, on_off: bool = None) -> None:
"""Turns Welcome on or off.
If `on_off` is not provided, the state will be flipped.
"""
guild = ctx.guild
target_state = on_off if on_off is not None else not (await self.config.guild(guild).enabled())
await self.config.guild(guild).enabled.set(target_state)
await ctx.send(f"Welcome is now {ENABLED if target_state else DISABLED}.")
@welcomeset.command(name="channel")
async def welcomeset_channel(self, ctx: commands.Context, channel: discord.TextChannel) -> None:
"""Sets the channel to be used for event notices."""
if not Welcome.__can_speak_in(channel):
await ctx.send(
f"I do not have permission to send messages in {channel.mention}. "
"Check your permission settings and try again."
)
return
guild = ctx.guild
await self.config.guild(guild).channel.set(channel.id)
await ctx.send(f"I will now send event notices to {channel.mention}.")
@welcomeset.group(name="join")
async def welcomeset_join(self, ctx: commands.Context) -> None:
"""Change settings for join notices."""
pass
@welcomeset_join.command(name="toggle")
async def welcomeset_join_toggle(self, ctx: commands.Context, on_off: bool = None) -> None:
"""Turns join notices on or off.
If `on_off` is not provided, the state will be flipped.
"""
await self.__toggle(ctx, on_off, "join")
@welcomeset_join.command(name="channel")
async def welcomeset_join_channel(self, ctx: commands.Context, channel: discord.TextChannel = None) -> None:
"""Sets the channel to be used specifically for join notices.
If `channel` is not provided, the join-specific channel is cleared.
"""
await self.__set_channel(ctx, channel, "join")
@welcomeset_join.command(name="toggledelete")
async def welcomeset_join_toggledelete(self, ctx: commands.Context, on_off: bool = None) -> None:
"""Turns deletion of previous join notice on or off.
If `on_off` is not provided, the state will be flipped.
"""
await self.__toggledelete(ctx, on_off, "join")
@welcomeset_join.group(name="whisper")
async def welcomeset_join_whisper(self, ctx: commands.Context) -> None:
"""Change settings for join whispers."""
pass
@welcomeset_join_whisper.command(name="type")
async def welcomeset_join_whisper_type(self, ctx: commands.Context, choice: WhisperType) -> None:
"""Set if a DM is sent to the new member.
Options:
off - no DM is sent
only - only send a DM to the member, do not send a message to the channel
both - send a DM to the member and a message to the channel
fall - send a DM to the member, if it fails send the whisper message to the channel instead
"""
guild = ctx.guild
whisper_type = choice.value
channel = await self.__get_channel(ctx.guild, "join")
await self.config.guild(guild).join.whisper.state.set(whisper_type)
if choice == WhisperType.OFF:
await ctx.send(f"I will no longer DM new members, and will send a notice to {channel.mention}.")
elif choice == WhisperType.ONLY:
await ctx.send(f"I will now only DM new members, and will not send a notice to {channel.mention}.")
elif choice == WhisperType.BOTH:
await ctx.send(f"I will now send a DM to new members, as well as send a notice to {channel.mention}.")
elif choice == WhisperType.FALLBACK:
await ctx.send(
f"I will now send a DM to new members, and if that fails I will send the message to {channel.mention}."
)
@welcomeset_join_whisper.command(name="message", aliases=["msg"])
async def welcomeset_join_whisper_message(self, ctx: commands.Context, *, msg_format: str) -> None:
"""Set the message DM'd to new members when they join.
Allows for the following customizations:
`{member}` is the member who joined
`{server}` is the server
"""
await self.config.guild(ctx.guild).join.whisper.message.set(msg_format)
await ctx.send("I will now use that message format when whispering new members, if whisper is enabled.")
@welcomeset_join.group(name="message", aliases=["msg"])
async def welcomeset_join_message(self, ctx: commands.Context) -> None:
"""Manage join message formats."""
pass
@welcomeset_join_message.command(name="add")
async def welcomeset_join_message_add(self, ctx: commands.Context, *, msg_format: str) -> None:
"""Add a new join message format to be chosen.
Allows for the following customizations:
`{member}` is the new member
`{server}` is the server
`{count}` is the number of members who have joined today
`{plural}` is an 's' if `count` is not 1, and nothing if it is
`{stats}` to include user stats (if using activitylog cog)
`{roles}` to show member roles at time of event
For example:
{member.mention}... What are you doing here???
{server.name} has a new member! {member.name}#{member.discriminator} - {member.id}
Someone new has joined! Who is it?! D: IS HE HERE TO HURT US?!
"""
await self.__message_add(ctx, msg_format, "join")
@welcomeset_join_message.command(name="delete", aliases=["del"])
async def welcomeset_join_message_delete(self, ctx: commands.Context) -> None:
"""Delete an existing join message format from the list."""
await self.__message_delete(ctx, "join")
@welcomeset_join_message.command(name="list", aliases=["ls"])
async def welcomeset_join_message_list(self, ctx: commands.Context) -> None:
"""Lists the available join message formats."""
await self.__message_list(ctx, "join")
@welcomeset_join.command(name="botmessage", aliases=["botmsg"])
async def welcomeset_join_botmessage(self, ctx: commands.Context, *, msg_format: str = None) -> None:
"""Sets the message format to use for join notices for bots.
Supply no format to use normal join message formats for bots.
Allows for the following customizations:
`{bot}` is the bot
`{server}` is the server
`{count}` is the number of members who have joined today
`{plural}` is an 's' if `count` is not 1, and nothing if it is
For example:
{bot.mention} beep boop.
"""
await self.config.guild(ctx.guild).join.bot.set(msg_format)
if msg_format is not None:
await ctx.send("Bot join message format set. I will now greet bots with that message.")
else:
await ctx.send("Bot join message format removed. I will now greet bots like normal members.")
@welcomeset.group(name="leave")
async def welcomeset_leave(self, ctx: commands.Context) -> None:
"""Change settings for leave notices."""
pass
@welcomeset_leave.command(name="toggle")
async def welcomeset_leave_toggle(self, ctx: commands.Context, on_off: bool = None) -> None:
"""Turns leave notices on or off.
If `on_off` is not provided, the state will be flipped.
"""
await self.__toggle(ctx, on_off, "leave")
@welcomeset_leave.command(name="channel")
async def welcomeset_leave_channel(self, ctx: commands.Context, channel: discord.TextChannel = None) -> None:
"""Sets the channel to be used specifically for leave notices.
If `channel` is not provided, the leave-specific channel is cleared.
"""
await self.__set_channel(ctx, channel, "leave")
@welcomeset_leave.command(name="toggledelete")
async def welcomeset_leave_toggledelete(self, ctx: commands.Context, on_off: bool = None) -> None:
"""Turns deletion of previous leave notice on or off.
If `on_off` is not provided, the state will be flipped.
"""
await self.__toggledelete(ctx, on_off, "leave")
@welcomeset_leave.group(name="message", aliases=["msg"])
async def welcomeset_leave_message(self, ctx: commands.Context) -> None:
"""Manage leave message formats."""
pass
@welcomeset_leave_message.command(name="add")
async def welcomeset_leave_message_add(self, ctx: commands.Context, *, msg_format: str) -> None:
"""Add a new leave message format to be chosen.
Allows for the following customizations:
`{member}` is the member who left
`{server}` is the server
`{count}` is the number of members who have left today
`{plural}` is an 's' if `count` is not 1, and nothing if it is
`{stats}` to include user stats (if using activitylog cog)
`{roles}` to show member roles at time of event
`{joined_on}` is the Discord formated time the user joined the server
For example:
{member.name}... Why did you leave???
{server.name} has lost a member! {member.name}#{member.discriminator} - {member.id}
Someone has left... Aww... Bye :(
"""
await self.__message_add(ctx, msg_format, "leave")
@welcomeset_leave_message.command(name="delete", aliases=["del"])
async def welcomeset_leave_message_delete(self, ctx: commands.Context) -> None:
"""Delete an existing leave message format from the list."""
await self.__message_delete(ctx, "leave")
@welcomeset_leave_message.command(name="list", aliases=["ls"])
async def welcomeset_leave_message_list(self, ctx: commands.Context) -> None:
"""Lists the available leave message formats."""
await self.__message_list(ctx, "leave")
@welcomeset.group(name="ban")
async def welcomeset_ban(self, ctx: commands.Context) -> None:
"""Change settings for ban notices."""
pass
@welcomeset_ban.command(name="toggle")
async def welcomeset_ban_toggle(self, ctx: commands.Context, on_off: bool = None) -> None:
"""Turns ban notices on or off.
If `on_off` is not provided, the state will be flipped.
"""
await self.__toggle(ctx, on_off, "ban")
@welcomeset_ban.command(name="channel")
async def welcomeset_ban_channel(self, ctx: commands.Context, channel: discord.TextChannel = None) -> None:
"""Sets the channel to be used specifically for ban notices.
If `channel` is not provided, the ban-specific channel is cleared.
"""
await self.__set_channel(ctx, channel, "ban")
@welcomeset_ban.command(name="toggledelete")
async def welcomeset_ban_toggledelete(self, ctx: commands.Context, on_off: bool = None) -> None:
"""Turns deletion of previous ban notice on or off.
If `on_off` is not provided, the state will be flipped.
"""
await self.__toggledelete(ctx, on_off, "ban")
@welcomeset_ban.group(name="message", aliases=["msg"])
async def welcomeset_ban_message(self, ctx: commands.Context) -> None:
"""Manage ban message formats."""
pass
@welcomeset_ban_message.command(name="add")
async def welcomeset_ban_message_add(self, ctx: commands.Context, *, msg_format: str) -> None:
"""Add a new ban message format to be chosen.
Allows for the following customizations:
`{member}` is the banned member
`{server}` is the server
`{count}` is the number of members who have been banned today
`{plural}` is an 's' if `count` is not 1, and nothing if it is
`{stats}` to include user stats (if using activitylog cog)
`{roles}` to show member roles at time of event
For example:
{member.name} was banned... What did you do???
A member of {server.name} has been banned! {member.name}#{member.discriminator} - {member.id}
Someone has been banned. Good riddance!
"""
await self.__message_add(ctx, msg_format, "ban")
@welcomeset_ban_message.command(name="delete", aliases=["del"])
async def welcomeset_ban_message_delete(self, ctx: commands.Context) -> None:
"""Delete an existing ban message format from the list."""
await self.__message_delete(ctx, "ban")
@welcomeset_ban_message.command(name="list", aliases=["ls"])
async def welcomeset_ban_message_list(self, ctx: commands.Context) -> None:
"""Lists the available ban message formats."""
await self.__message_list(ctx, "ban")
@welcomeset.group(name="unban")
async def welcomeset_unban(self, ctx: commands.Context) -> None:
"""Change settings for unban notices."""
pass
@welcomeset_unban.command(name="toggle")
async def welcomeset_unban_toggle(self, ctx: commands.Context, on_off: bool = None) -> None:
"""Turns unban notices on or off.
If `on_off` is not provided, the state will be flipped.
"""
await self.__toggle(ctx, on_off, "unban")
@welcomeset_unban.command(name="channel")
async def welcomeset_unban_channel(self, ctx: commands.Context, channel: discord.TextChannel = None) -> None:
"""Sets the channel to be used specifically for unban notices.
If `channel` is not provided, the unban-specific channel is cleared.
"""
await self.__set_channel(ctx, channel, "unban")
@welcomeset_unban.command(name="toggledelete")
async def welcomeset_unban_toggledelete(self, ctx: commands.Context, on_off: bool = None) -> None:
"""Turns deletion of previous unban notice on or off.
If `on_off` is not provided, the state will be flipped.
"""
await self.__toggledelete(ctx, on_off, "unban")
@welcomeset_unban.group(name="message", aliases=["msg"])
async def welcomeset_unban_message(self, ctx: commands.Context) -> None:
"""Manage unban message formats."""
pass
@welcomeset_unban_message.command(name="add")
async def welcomeset_unban_message_add(self, ctx: commands.Context, *, msg_format: str) -> None:
"""Add a new unban message format to be chosen.
Allows for the following customizations:
`{member}` is the unbanned member
`{server}` is the server
`{count}` is the number of members who have been unbanned today
`{plural}` is an 's' if `count` is not 1, and nothing if it is
For example:
{member.name} was unbanned... Did you learn your lesson???
A member of {server.name} has been unbanned! {member.name}#{member.discriminator} - {member.id}
Someone has been unbanned. Don't waste your second chance!
"""
await self.__message_add(ctx, msg_format, "unban")
@welcomeset_unban_message.command(name="delete", aliases=["del"])
async def welcomeset_unban_message_delete(self, ctx: commands.Context) -> None:
"""Delete an existing unban message format from the list."""
await self.__message_delete(ctx, "unban")
@welcomeset_unban_message.command(name="list", aliases=["ls"])
async def welcomeset_unban_message_list(self, ctx: commands.Context) -> None:
"""Lists the available unban message formats."""
await self.__message_list(ctx, "unban")
@commands.Cog.listener()
async def on_member_join(self, member: discord.Member) -> None:
"""Listens for member joins."""
if await self.bot.cog_disabled_in_guild(self, member.guild):
return
guild: discord.Guild = member.guild
guild_settings = self.config.guild(guild)
if await guild_settings.enabled() and await guild_settings.join.enabled():
# join notice should be sent
message_format: Optional[str] = None
if member.bot:
# bot
message_format = await guild_settings.join.bot()
else:
whisper_type: str = await guild_settings.join.whisper.state()
if whisper_type != "off":
try:
await self.__dm_user(member)
except WhisperError:
if whisper_type == "fall":
message_format = await self.config.guild(member.guild).join.whisper.message()
await self.__handle_event(guild, member, "join", message_format=message_format)
return
if whisper_type == "only" or whisper_type == "fall":
# we're done here
return
await self.__handle_event(guild, member, "join", message_format=message_format)
@commands.Cog.listener()
async def on_member_remove(self, member: discord.Member) -> None:
"""Listens for member leaves."""
if await self.bot.cog_disabled_in_guild(self, member.guild):
return
await self.__handle_event(member.guild, member, "leave")
@commands.Cog.listener()
async def on_member_ban(self, guild: discord.Guild, member: discord.User) -> None:
"""Listens for user bans."""
await self.__handle_event(guild, member, "ban")
@commands.Cog.listener()
async def on_member_unban(self, guild: discord.Guild, user: discord.User) -> None:
"""Listens for user unbans."""
await self.__handle_event(guild, user, "unban")
#
# concrete handlers for settings changes and events
#
async def __toggle(self, ctx: commands.Context, on_off: bool, event: str) -> None:
"""Handler for setting toggles."""
guild: discord.Guild = ctx.guild
target_state = on_off if on_off is not None else not (await self.config.guild(guild).get_attr(event).enabled())
await self.config.guild(guild).get_attr(event).enabled.set(target_state)
await ctx.send(f"{event.capitalize()} notices are now {ENABLED if target_state else DISABLED}.")
async def __set_channel(self, ctx: commands.Context, channel: discord.TextChannel, event: str) -> None:
"""Handler for setting channels."""
guild: discord.Guild = ctx.guild
store_this = channel.id if channel is not None else None
await self.config.guild(guild).get_attr(event).channel.set(store_this)
if store_this is not None:
await ctx.send(f"I will now send {event} notices to {channel.mention}.")
else:
default_channel = await self.__get_channel(guild, "default")
await ctx.send(f"I will now send {event} messages to the default channel, {default_channel.mention}.")
async def __toggledelete(self, ctx: commands.Context, on_off: bool, event: str) -> None:
"""Handler for setting delete toggles."""
guild: discord.Guild = ctx.guild
target_state = on_off if on_off is not None else not (await self.config.guild(guild).get_attr(event).delete())
await self.config.guild(guild).get_attr(event).delete.set(target_state)
await ctx.send(f"Deletion of previous {event} notice is now {ENABLED if target_state else DISABLED}")
async def __message_add(self, ctx: commands.Context, msg_format: str, event: str) -> None:
"""Handler for adding message formats."""
guild: discord.Guild = ctx.guild
async with self.config.guild(guild).get_attr(event).messages() as messages:
messages.append(msg_format)
await ctx.send(f"New message format for {event} notices added.")
async def __message_delete(self, ctx: commands.Context, event: str) -> None:
"""Handler for deleting message formats."""
guild: discord.Guild = ctx.guild
async with self.config.guild(guild).get_attr(event).messages() as messages:
if len(messages) == 1:
await ctx.send(f"I only have one {event} message format, so I can't let you delete it.")
return
await self.__message_list(ctx, event)
await ctx.send(f"Please enter the number of the {event} message format you wish to delete.")
try:
num = await Welcome.__get_number_input(ctx, len(messages))
except asyncio.TimeoutError:
await ctx.send(f"Okay, I won't remove any of the {event} message formats.")
return
else:
removed = messages.pop(num - 1)
await ctx.send(f"Done. This {event} message format was deleted:\n`{removed}`")
async def __message_list(self, ctx: commands.Context, event: str) -> None:
"""Handler for listing message formats."""
guild: discord.Guild = ctx.guild
msg = f"{event.capitalize()} message formats:\n"
messages = await self.config.guild(guild).get_attr(event).messages()
for n, m in enumerate(messages, start=1):
msg += f" {n}. {m}\n"
for page in pagify(msg, shorten_by=20):
await ctx.send(box(page))
async def __handle_event(
self, guild: discord.guild, user: Union[discord.Member, discord.User], event: str, *, message_format=None
) -> None:
"""Handler for actual events."""
guild_settings = self.config.guild(guild)
# always increment, even if we aren't sending a notice
await self.__increment_count(guild, event)
if await guild_settings.enabled():
settings = await guild_settings.get_attr(event).all()
if settings["enabled"]:
# notices for this event are enabled
if settings["delete"] and settings["last"] is not None:
# we need to delete the previous message
await self.__delete_message(guild, settings["last"], event)
# regardless of success, remove reference to that message
await guild_settings.get_attr(event).last.set(None)
# send a notice to the channel
new_message = await self.__send_notice(guild, user, event, message_format=message_format)
# store it for (possible) deletion later
await guild_settings.get_attr(event).last.set(new_message and new_message.id)
async def __get_channel(self, guild: discord.Guild, event: str) -> discord.TextChannel:
"""Gets the best text channel to use for event notices.
Order of priority:
1. User-defined channel
2. Guild's system channel (if bot can speak in it)
3. First channel that the bot can speak in
"""
channel = None
if event == "default":
channel_id: int = await self.config.guild(guild).channel()
else:
channel_id = await self.config.guild(guild).get_attr(event).channel()
if channel_id is not None:
channel = guild.get_channel(channel_id)
if channel is None or not Welcome.__can_speak_in(channel):
channel = guild.get_channel(await self.config.guild(guild).channel())
if channel is None or not Welcome.__can_speak_in(channel):
channel = guild.system_channel
if channel is None or not Welcome.__can_speak_in(channel):
for ch in guild.text_channels:
if Welcome.__can_speak_in(ch):
channel = ch
break
return channel
async def __delete_message(self, guild: discord.Guild, message_id: int, event: str) -> None:
"""Attempts to delete the message with the given ID."""
try:
await (await (await self.__get_channel(guild, event)).fetch_message(message_id)).delete()
except discord.NotFound:
log.warning("Failed to delete message (ID {message_id}): not found")
except discord.Forbidden:
log.warning("Failed to delete message (ID {message_id}): insufficient permissions")
except discord.DiscordException:
log.warning("Failed to delete message (ID {message_id})")
async def __send_notice(
self, guild: discord.guild, user: Union[discord.Member, discord.User], event: str, *, message_format=None
) -> Optional[discord.Message]:
"""Sends the notice for the event."""
format_str = message_format or await self.__get_random_message_format(guild, event)
count = await self.config.guild(guild).get_attr(event).counter()
plural = ""
if count and count != 1:
plural = "s"
channel = await self.__get_channel(guild, event)
if isinstance(user, discord.Member):
2020-02-12 08:28:38 +13:00
roles = [r for r in user.roles if r.name != "@everyone"]
roles.sort(reverse=True)
roles = [r.name for r in roles]
roles = humanize_list(roles)
else:
roles = []
actlog = self.bot.get_cog("ActivityLogger")
if actlog:
stats = await actlog.userstats(guild, user)
stats = stats[0]
else:
stats = ""
try:
return await channel.send(
2020-02-01 13:37:32 +13:00
format_str.format(
member=user,
server=guild,
bot=user,
count=count or "",
plural=plural,
roles=roles,
stats=stats,
joined_on=f"<t:{int(user.joined_at.timestamp())}>",
),
allowed_mentions=discord.AllowedMentions.all(),
)
except discord.Forbidden:
log.error(
f"Failed to send {event} message to channel ID {channel.id} (server ID {guild.id}): "
"insufficient permissions"
)
return None
except discord.DiscordException:
log.error(f"Failed to send {event} message to channel ID {channel.id} (server ID {guild.id})")
return None
except KeyError:
log.error(
f"Failed to send {event} message to channel ID {channel.id} (server id {guild.id}) because there is an error in message formatting."
)
return await channel.send(f"{box(format_str)} has an unknown key in brackets. Please fix this format.")
async def __get_random_message_format(self, guild: discord.guild, event: str) -> str:
"""Gets a random message for event of type event."""
async with self.config.guild(guild).get_attr(event).messages() as messages:
return random.choice(messages)
async def __increment_count(self, guild: discord.Guild, event: str) -> None:
"""Increments the counter for <event>s today. Handles date changes."""
guild_settings = self.config.guild(guild)
if await guild_settings.date() is None:
await guild_settings.date.set(Welcome.__today())
if Welcome.__today() > await guild_settings.date():
await guild_settings.date.set(Welcome.__today())
await guild_settings.get_attr(event).counter.set(0)
count: int = await guild_settings.get_attr(event).counter()
await guild_settings.get_attr(event).counter.set(count + 1)
async def __dm_user(self, member: discord.Member) -> None:
"""Sends a DM to the user with a filled-in message_format."""
message_format = await self.config.guild(member.guild).join.whisper.message()
try:
await member.send(
message_format.format(member=member, server=member.guild),
allowed_mentions=discord.AllowedMentions.all(),
)
except discord.Forbidden:
log.error(
f"Failed to send DM to member ID {member.id} (server ID {member.guild.id}): insufficient permissions"
)
raise WhisperError()
except discord.DiscordException:
log.error(f"Failed to send DM to member ID {member.id} (server ID {member.guild.id})")
raise WhisperError()
@staticmethod
async def __get_number_input(ctx: commands.Context, maximum: int, minimum: int = 0) -> int:
"""Gets a number from the user, minimum < x <= maximum."""
author = ctx.author
channel = ctx.channel
def check(m: discord.Message) -> bool:
try:
num = int(m.content)
except ValueError:
return False
return num is not None and minimum < num <= maximum and m.author == author and m.channel == channel
try:
msg = await ctx.bot.wait_for("message", check=check, timeout=15.0)
except asyncio.TimeoutError:
raise
else:
return int(msg.content)
@staticmethod
def __can_speak_in(channel: discord.TextChannel) -> bool:
"""Indicates whether the bot has permission to speak in channel."""
return channel.permissions_for(channel.guild.me).send_messages
@staticmethod
def __today() -> int:
"""Gets today's date in ordinal form."""
return datetime.date.today().toordinal()
async def red_delete_data_for_user(
self,
*,
requester: Literal["discord_deleted_user", "owner", "user", "user_strict"],
user_id: int,
):
pass