2019-09-22 17:55:45 +12:00
|
|
|
import asyncio
|
|
|
|
import logging
|
|
|
|
import random
|
|
|
|
from datetime import date
|
|
|
|
from typing import Union
|
|
|
|
|
|
|
|
import discord
|
|
|
|
from redbot.core import Config, commands, checks
|
|
|
|
from redbot.core.bot import Red
|
|
|
|
from redbot.core.utils.chat_formatting import box, pagify
|
|
|
|
|
|
|
|
from .enums import WhisperType
|
|
|
|
|
|
|
|
__author__ = "tmerc"
|
|
|
|
|
|
|
|
log = logging.getLogger('red.tmerc.welcome')
|
|
|
|
|
|
|
|
ENABLED = 'enabled'
|
|
|
|
DISABLED = 'disabled'
|
|
|
|
|
|
|
|
class WhisperError(Exception):
|
|
|
|
pass
|
|
|
|
|
|
|
|
class Welcome(getattr(commands, "Cog", object)):
|
|
|
|
"""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,
|
|
|
|
'delete': False,
|
|
|
|
'last': None,
|
2019-11-06 19:26:29 +13:00
|
|
|
'channel': None,
|
2019-09-22 17:55:45 +12:00
|
|
|
'counter': 0,
|
|
|
|
'whisper': {
|
|
|
|
'state': 'off',
|
|
|
|
'message': default_whisper
|
|
|
|
},
|
|
|
|
'messages': [default_join],
|
|
|
|
'bot': None
|
|
|
|
},
|
|
|
|
'leave': {
|
|
|
|
'enabled': True,
|
|
|
|
'delete': False,
|
2019-11-06 19:26:29 +13:00
|
|
|
'channel': None,
|
2019-09-22 17:55:45 +12:00
|
|
|
'last': None,
|
|
|
|
'messages': [default_leave],
|
|
|
|
},
|
|
|
|
'ban': {
|
|
|
|
'enabled': True,
|
|
|
|
'delete': False,
|
2019-11-06 19:26:29 +13:00
|
|
|
'channel': None,
|
2019-09-22 17:55:45 +12:00
|
|
|
'last': None,
|
|
|
|
'messages': [default_ban],
|
|
|
|
},
|
|
|
|
'unban': {
|
|
|
|
'enabled': True,
|
|
|
|
'delete': False,
|
2019-11-06 19:26:29 +13:00
|
|
|
'channel': None,
|
2019-09-22 17:55:45 +12:00
|
|
|
'last': None,
|
|
|
|
'messages': [default_unban],
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
def __init__(self, bot: Red):
|
|
|
|
self.bot = bot
|
|
|
|
self.config = Config.get_conf(self, 86345009)
|
|
|
|
self.config.register_guild(**self.guild_defaults)
|
|
|
|
|
|
|
|
@commands.group()
|
|
|
|
@commands.guild_only()
|
|
|
|
@checks.admin_or_permissions(manage_guild=True)
|
|
|
|
async def welcomeset(self, ctx: commands.Context):
|
|
|
|
"""Change Welcome settings."""
|
|
|
|
|
|
|
|
await ctx.trigger_typing()
|
|
|
|
|
|
|
|
if ctx.invoked_subcommand is None:
|
|
|
|
guild = ctx.guild
|
|
|
|
c = await self.config.guild(guild).all()
|
|
|
|
|
2019-11-06 19:26:29 +13:00
|
|
|
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")
|
2019-09-22 17:55:45 +12:00
|
|
|
|
|
|
|
j = c['join']
|
|
|
|
jw = j['whisper']
|
|
|
|
v = c['leave']
|
|
|
|
b = c['ban']
|
|
|
|
u = c['unban']
|
|
|
|
|
|
|
|
if await ctx.embed_requested():
|
|
|
|
emb = discord.Embed(color=await ctx.embed_color(), title="Current Welcome Settings")
|
|
|
|
emb.add_field(name="General", value=(
|
|
|
|
"**Enabled:** {}\n"
|
2019-11-06 19:26:29 +13:00
|
|
|
"**Channel:** #{}\n."
|
|
|
|
).format(c['enabled'], channel))
|
2019-09-22 17:55:45 +12:00
|
|
|
emb.add_field(name="Join", value=(
|
|
|
|
"**Enabled:** {}\n"
|
2019-11-06 19:26:29 +13:00
|
|
|
"**Channel:** {}\n"
|
2019-09-22 17:55:45 +12:00
|
|
|
"**Delete previous:** {}\n"
|
|
|
|
"**Whisper state:** {}\n"
|
|
|
|
"**Whisper message:** {}\n"
|
|
|
|
"**Messages:** {}; do `{prefix}welcomeset join msg list` for a list\n"
|
|
|
|
"**Bot message:** {}"
|
2019-11-06 19:26:29 +13:00
|
|
|
).format(j['enabled'], join_channel, j['delete'], jw['state'], jw['message'] if len(jw['message']) <= 50 else jw['message'][:50] + "...", len(j['messages']), j['bot'],
|
2019-09-22 17:55:45 +12:00
|
|
|
prefix=ctx.prefix))
|
|
|
|
emb.add_field(name="Leave", value=(
|
|
|
|
"**Enabled:** {}\n"
|
2019-11-06 19:26:29 +13:00
|
|
|
"**Channel:** {}\n"
|
2019-09-22 17:55:45 +12:00
|
|
|
"**Delete previous:** {}\n"
|
|
|
|
"**Messages:** {}; do `{prefix}welcomeset leave msg list` for a list\n"
|
2019-11-06 19:26:29 +13:00
|
|
|
).format(v['enabled'], leave_channel, v['delete'], len(v['messages']), prefix=ctx.prefix))
|
2019-09-22 17:55:45 +12:00
|
|
|
emb.add_field(name="Ban", value=(
|
|
|
|
"**Enabled:** {}\n"
|
2019-11-06 19:26:29 +13:00
|
|
|
"**Channel:** {}\n"
|
2019-09-22 17:55:45 +12:00
|
|
|
"**Delete previous:** {}\n"
|
|
|
|
"**Messages:** {}; do `{prefix}welcomeset ban msg list` for a list\n"
|
2019-11-06 19:26:29 +13:00
|
|
|
).format(b['enabled'], ban_channel, b['delete'], len(b['messages']), prefix=ctx.prefix))
|
2019-09-22 17:55:45 +12:00
|
|
|
emb.add_field(name="Unban", value=(
|
|
|
|
"**Enabled:** {}\n"
|
2019-11-06 19:26:29 +13:00
|
|
|
"**Channel:** {}\n"
|
2019-09-22 17:55:45 +12:00
|
|
|
"**Delete previous:** {}\n"
|
|
|
|
"**Messages:** {}; do `{prefix}welcomeset unban msg list` for a list\n"
|
2019-11-06 19:26:29 +13:00
|
|
|
).format(u['enabled'], unban_channel, u['delete'], len(u['messages']), prefix=ctx.prefix))
|
2019-09-22 17:55:45 +12:00
|
|
|
|
|
|
|
await ctx.send(embed=emb)
|
|
|
|
else:
|
|
|
|
msg = box(
|
|
|
|
(" Enabled: {}\n"
|
|
|
|
" Channel: {}\n"
|
|
|
|
" Join:\n"
|
|
|
|
" Enabled: {}\n"
|
2019-11-06 19:26:29 +13:00
|
|
|
" Channel: {}\n"
|
2019-09-22 17:55:45 +12:00
|
|
|
" Delete previous: {}\n"
|
|
|
|
" Whisper:\n"
|
|
|
|
" State: {}\n"
|
|
|
|
" Message: {}\n"
|
|
|
|
" Messages: {}; do '{prefix}welcomeset join msg list' for a list\n"
|
|
|
|
" Bot message: {}\n"
|
|
|
|
" Leave:\n"
|
|
|
|
" Enabled: {}\n"
|
2019-11-06 19:26:29 +13:00
|
|
|
" Channel: {}\n"
|
2019-09-22 17:55:45 +12:00
|
|
|
" Delete previous: {}\n"
|
|
|
|
" Messages: {}; do '{prefix}welcomeset leave msg list' for a list\n"
|
|
|
|
" Ban:\n"
|
|
|
|
" Enabled: {}\n"
|
2019-11-06 19:26:29 +13:00
|
|
|
" Channel: {}\n"
|
2019-09-22 17:55:45 +12:00
|
|
|
" Delete previous: {}\n"
|
|
|
|
" Messages: {}; do '{prefix}welcomeset ban msg list' for a list\n"
|
|
|
|
" Unban:\n"
|
|
|
|
" Enabled: {}\n"
|
2019-11-06 19:26:29 +13:00
|
|
|
" Channel: {}\n"
|
2019-09-22 17:55:45 +12:00
|
|
|
" Delete previous: {}\n"
|
|
|
|
" Messages: {}; do '{prefix}welcomeset unban msg list' for a list\n"
|
|
|
|
"").format(c['enabled'], channel,
|
2019-11-06 19:26:29 +13:00
|
|
|
j['enabled'], join_channel, j['delete'], jw['state'], jw['message'], len(j['messages']), j['bot'],
|
|
|
|
v['enabled'], leave_channel, v['delete'], len(v['messages']),
|
|
|
|
b['enabled'], ban_channel, b['delete'], len(b['messages']),
|
|
|
|
u['enabled'], unban_channel, u['delete'], len(u['messages']),
|
2019-09-22 17:55:45 +12:00
|
|
|
prefix=ctx.prefix),
|
|
|
|
"Current Welcome settings:"
|
|
|
|
)
|
|
|
|
|
|
|
|
await ctx.send(msg)
|
|
|
|
|
|
|
|
@welcomeset.command(name='toggle')
|
|
|
|
async def welcomeset_toggle(self, ctx: commands.Context, on_off: bool = 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(
|
|
|
|
("Welcome is now {}."
|
|
|
|
"").format(ENABLED if target_state else DISABLED)
|
|
|
|
)
|
|
|
|
|
|
|
|
@welcomeset.command(name='channel')
|
|
|
|
async def welcomeset_channel(self, ctx: commands.Context, channel: discord.TextChannel):
|
|
|
|
"""Sets the channel to be used for event notices."""
|
|
|
|
|
|
|
|
if not self.__can_speak_in(channel):
|
|
|
|
await ctx.send(
|
|
|
|
("I do not have permission to send messages in {0.mention}. Check your permission settings and try again."
|
|
|
|
"").format(channel)
|
|
|
|
)
|
|
|
|
return
|
|
|
|
|
|
|
|
guild = ctx.guild
|
|
|
|
await self.config.guild(guild).channel.set(channel.id)
|
|
|
|
|
|
|
|
await ctx.send(
|
|
|
|
("I will now send event notices to {0.mention}."
|
|
|
|
"").format(channel)
|
|
|
|
)
|
|
|
|
|
|
|
|
@welcomeset.group(name='join')
|
|
|
|
async def welcomeset_join(self, ctx: commands.Context):
|
|
|
|
"""Change settings for join notices."""
|
|
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
@welcomeset_join.command(name='toggle')
|
|
|
|
async def welcomeset_join_toggle(self, ctx: commands.Context, on_off: bool = 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='toggledelete')
|
|
|
|
async def welcomeset_join_toggledelete(self, ctx: commands.Context, on_off: bool = 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):
|
|
|
|
"""Change settings for join whispers."""
|
|
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
@welcomeset_join_whisper.command(name='type')
|
|
|
|
async def welcomeset_join_whisper_type(self, ctx: commands.Context, choice: WhisperType):
|
|
|
|
"""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
|
2019-09-23 18:36:28 +12:00
|
|
|
channel = await self.__get_channel(ctx.guild, "join")
|
2019-09-22 17:55:45 +12:00
|
|
|
|
|
|
|
await self.config.guild(guild).join.whisper.state.set(whisper_type)
|
|
|
|
|
|
|
|
if choice == WhisperType.OFF:
|
|
|
|
await ctx.send(
|
|
|
|
("I will no longer DM new members, and will send a notice to {0.mention}."
|
|
|
|
"").format(channel)
|
|
|
|
)
|
|
|
|
elif choice == WhisperType.ONLY:
|
|
|
|
await ctx.send(
|
|
|
|
("I will now only DM new members, and will not send a notice to {0.mention}."
|
|
|
|
"").format(channel)
|
|
|
|
)
|
|
|
|
elif choice == WhisperType.BOTH:
|
|
|
|
await ctx.send(
|
|
|
|
("I will now send a DM to new members, as well as send a notice to {0.mention}."
|
|
|
|
"").format(channel)
|
|
|
|
)
|
|
|
|
elif choice == WhisperType.FALLBACK:
|
|
|
|
await ctx.send(
|
|
|
|
("I will now send a DM to new members, and if that fails I will send the message to {0.mention}."
|
|
|
|
"").format(channel)
|
|
|
|
)
|
|
|
|
|
|
|
|
@welcomeset_join_whisper.command(name='msg')
|
|
|
|
async def welcomeset_join_whisper_msg(self, ctx: commands.Context, *, msg_format: str):
|
|
|
|
"""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='msg')
|
|
|
|
async def welcomeset_join_msg(self, ctx: commands.Context):
|
|
|
|
"""Manage join message formats."""
|
|
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
@welcomeset_join_msg.command(name='add')
|
|
|
|
async def welcomeset_join_msg_add(self, ctx: commands.Context, *, msg_format: str):
|
|
|
|
"""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
|
|
|
|
|
|
|
|
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.__msg_add(ctx, msg_format, 'join')
|
|
|
|
|
|
|
|
@welcomeset_join_msg.command(name='del')
|
|
|
|
async def welcomeset_join_msg_del(self, ctx: commands.Context):
|
|
|
|
"""Delete an existing join message format from the list."""
|
|
|
|
|
|
|
|
await self.__msg_del(ctx, 'join')
|
|
|
|
|
|
|
|
@welcomeset_join_msg.command(name='list')
|
|
|
|
async def welcomeset_join_msg_list(self, ctx: commands.Context):
|
|
|
|
"""Lists the available join message formats."""
|
|
|
|
|
|
|
|
await self.__msg_list(ctx, 'join')
|
|
|
|
|
|
|
|
@welcomeset_join.command(name='botmsg')
|
|
|
|
async def welcomeset_join_botmsg(self, ctx: commands.Context, *, msg_format: str=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."
|
|
|
|
"")
|
|
|
|
)
|
|
|
|
|
2019-11-06 19:26:29 +13:00
|
|
|
@welcomeset_join.command(name='channel')
|
|
|
|
async def welcomeset_join_channel(self, ctx: commands.Context, channel: discord.TextChannel):
|
|
|
|
"""Set channel for join notices
|
|
|
|
|
|
|
|
If not set, join notices are sent to the default events channel.
|
|
|
|
"""
|
|
|
|
if not self.__can_speak_in(channel):
|
|
|
|
await ctx.send(
|
|
|
|
("I do not have permission to send messages in {0.mention}. Check your permission settings and try again."
|
|
|
|
"").format(channel)
|
|
|
|
)
|
|
|
|
return
|
|
|
|
|
|
|
|
await self.config.guild(ctx.guild).join.channel.set(channel.id)
|
|
|
|
|
|
|
|
await ctx.send(
|
|
|
|
("I will now send join event notices to {0.mention}."
|
|
|
|
"").format(channel)
|
|
|
|
)
|
|
|
|
|
2019-09-22 17:55:45 +12:00
|
|
|
@welcomeset.group(name='leave')
|
|
|
|
async def welcomeset_leave(self, ctx: commands.Context):
|
|
|
|
"""Change settings for leave notices."""
|
|
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
@welcomeset_leave.command(name='toggle')
|
|
|
|
async def welcomeset_leave_toggle(self, ctx: commands.Context, on_off: bool = 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='toggledelete')
|
|
|
|
async def welcomeset_leave_toggledelete(self, ctx: commands.Context, on_off: bool = 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')
|
|
|
|
|
2019-11-06 19:26:29 +13:00
|
|
|
@welcomeset_leave.command(name='channel')
|
|
|
|
async def welcomeset_leave_channel(self, ctx: commands.Context, channel: discord.TextChannel):
|
|
|
|
"""Set channel for leave notices
|
|
|
|
|
|
|
|
If not set, leave notices are sent to the default events channel.
|
|
|
|
"""
|
|
|
|
if not self.__can_speak_in(channel):
|
|
|
|
await ctx.send(
|
|
|
|
("I do not have permission to send messages in {0.mention}. Check your permission settings and try again."
|
|
|
|
"").format(channel)
|
|
|
|
)
|
|
|
|
return
|
|
|
|
|
|
|
|
await self.config.guild(ctx.guild).leave.channel.set(channel.id)
|
|
|
|
|
|
|
|
await ctx.send(
|
|
|
|
("I will now send leave event notices to {0.mention}."
|
|
|
|
"").format(channel)
|
|
|
|
)
|
|
|
|
|
2019-09-22 17:55:45 +12:00
|
|
|
@welcomeset_leave.group(name='msg')
|
|
|
|
async def welcomeset_leave_msg(self, ctx: commands.Context):
|
|
|
|
"""Manage leave message formats."""
|
|
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
@welcomeset_leave_msg.command(name='add')
|
|
|
|
async def welcomeset_leave_msg_add(self, ctx: commands.Context, *, msg_format: str):
|
|
|
|
"""Add a new leave message format to be chosen.
|
|
|
|
|
|
|
|
Allows for the following customizations:
|
|
|
|
`{member}` is the member who left
|
|
|
|
`{server}` is 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.__msg_add(ctx, msg_format, 'leave')
|
|
|
|
|
|
|
|
@welcomeset_leave_msg.command(name='del')
|
|
|
|
async def welcomeset_leave_msg_del(self, ctx: commands.Context):
|
|
|
|
"""Delete an existing leave message format from the list."""
|
|
|
|
|
|
|
|
await self.__msg_del(ctx, 'leave')
|
|
|
|
|
|
|
|
@welcomeset_leave_msg.command(name='list')
|
|
|
|
async def welcomeset_leave_msg_list(self, ctx: commands.Context):
|
|
|
|
"""Lists the available leave message formats."""
|
|
|
|
|
|
|
|
await self.__msg_list(ctx, 'leave')
|
|
|
|
|
|
|
|
@welcomeset.group(name='ban')
|
|
|
|
async def welcomeset_ban(self, ctx: commands.Context):
|
|
|
|
"""Change settings for ban notices."""
|
|
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
@welcomeset_ban.command(name='toggle')
|
|
|
|
async def welcomeset_ban_toggle(self, ctx: commands.Context, on_off: bool = 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='toggledelete')
|
|
|
|
async def welcomeset_ban_toggledelete(self, ctx: commands.Context, on_off: bool = 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')
|
|
|
|
|
2019-11-06 19:26:29 +13:00
|
|
|
@welcomeset_ban.command(name='channel')
|
|
|
|
async def welcomeset_ban_channel(self, ctx: commands.Context, channel: discord.TextChannel):
|
|
|
|
"""Set channel for ban notices
|
|
|
|
|
|
|
|
If not set, ban notices are sent to the default events channel.
|
|
|
|
"""
|
|
|
|
if not self.__can_speak_in(channel):
|
|
|
|
await ctx.send(
|
|
|
|
("I do not have permission to send messages in {0.mention}. Check your permission settings and try again."
|
|
|
|
"").format(channel)
|
|
|
|
)
|
|
|
|
return
|
|
|
|
|
|
|
|
await self.config.guild(ctx.guild).ban.channel.set(channel.id)
|
|
|
|
|
|
|
|
await ctx.send(
|
|
|
|
("I will now send ban event notices to {0.mention}."
|
|
|
|
"").format(channel)
|
|
|
|
)
|
|
|
|
|
2019-09-22 17:55:45 +12:00
|
|
|
@welcomeset_ban.group(name='msg')
|
|
|
|
async def welcomeset_ban_msg(self, ctx: commands.Context):
|
|
|
|
"""Manage ban message formats."""
|
|
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
@welcomeset_ban_msg.command(name='add')
|
|
|
|
async def welcomeset_ban_msg_add(self, ctx: commands.Context, *, msg_format: str):
|
|
|
|
"""Add a new ban message format to be chosen.
|
|
|
|
|
|
|
|
Allows for the following customizations:
|
|
|
|
`{member}` is the banned member
|
|
|
|
`{server}` is the server
|
|
|
|
|
|
|
|
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.__msg_add(ctx, msg_format, 'ban')
|
|
|
|
|
|
|
|
@welcomeset_ban_msg.command(name='del')
|
|
|
|
async def welcomeset_ban_msg_del(self, ctx: commands.Context):
|
|
|
|
"""Delete an existing ban message format from the list."""
|
|
|
|
|
|
|
|
await self.__msg_del(ctx, 'ban')
|
|
|
|
|
|
|
|
@welcomeset_ban_msg.command(name='list')
|
|
|
|
async def welcomeset_ban_msg_list(self, ctx: commands.Context):
|
|
|
|
"""Lists the available ban message formats."""
|
|
|
|
|
|
|
|
await self.__msg_list(ctx, 'ban')
|
|
|
|
|
|
|
|
@welcomeset.group(name='unban')
|
|
|
|
async def welcomeset_unban(self, ctx: commands.Context):
|
|
|
|
"""Change settings for unban notices."""
|
|
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
@welcomeset_unban.command(name='toggle')
|
|
|
|
async def welcomeset_unban_toggle(self, ctx: commands.Context, on_off: bool = 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='toggledelete')
|
|
|
|
async def welcomeset_unban_toggledelete(self, ctx: commands.Context, on_off: bool = 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')
|
|
|
|
|
2019-11-06 19:26:29 +13:00
|
|
|
@welcomeset_unban.command(name='channel')
|
|
|
|
async def welcomeset_unban_channel(self, ctx: commands.Context, channel: discord.TextChannel):
|
|
|
|
"""Set channel for unban notices
|
|
|
|
|
|
|
|
If not set, unban notices are sent to the default events channel.
|
|
|
|
"""
|
|
|
|
if not self.__can_speak_in(channel):
|
|
|
|
await ctx.send(
|
|
|
|
("I do not have permission to send messages in {0.mention}. Check your permission settings and try again."
|
|
|
|
"").format(channel)
|
|
|
|
)
|
|
|
|
return
|
|
|
|
|
|
|
|
await self.config.guild(ctx.guild).unban.channel.set(channel.id)
|
|
|
|
|
|
|
|
await ctx.send(
|
|
|
|
("I will now send unban event notices to {0.mention}."
|
|
|
|
"").format(channel)
|
|
|
|
)
|
|
|
|
|
2019-09-22 17:55:45 +12:00
|
|
|
@welcomeset_unban.group(name='msg')
|
|
|
|
async def welcomeset_unban_msg(self, ctx: commands.Context):
|
|
|
|
"""Manage unban message formats."""
|
|
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
@welcomeset_unban_msg.command(name='add')
|
|
|
|
async def welcomeset_unban_msg_add(self, ctx: commands.Context, *, msg_format: str):
|
|
|
|
"""Add a new unban message format to be chosen.
|
|
|
|
|
|
|
|
Allows for the following customizations:
|
|
|
|
`{member}` is the unbanned member
|
|
|
|
`{server}` is the server
|
|
|
|
|
|
|
|
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.__msg_add(ctx, msg_format, 'unban')
|
|
|
|
|
|
|
|
@welcomeset_unban_msg.command(name='del')
|
|
|
|
async def welcomeset_unban_msg_del(self, ctx: commands.Context):
|
|
|
|
"""Delete an existing unban message format from the list."""
|
|
|
|
|
|
|
|
await self.__msg_del(ctx, 'unban')
|
|
|
|
|
|
|
|
@welcomeset_unban_msg.command(name='list')
|
|
|
|
async def welcomeset_unban_msg_list(self, ctx: commands.Context):
|
|
|
|
"""Lists the available unban message formats."""
|
|
|
|
|
|
|
|
await self.__msg_list(ctx, 'unban')
|
|
|
|
|
|
|
|
@commands.Cog.listener()
|
|
|
|
async def on_member_join(self, member: discord.Member):
|
|
|
|
"""Listens for member joins."""
|
|
|
|
|
|
|
|
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 = None
|
|
|
|
if member.bot:
|
|
|
|
# bot
|
|
|
|
message_format = await guild_settings.join.bot()
|
|
|
|
|
|
|
|
else:
|
|
|
|
# only increment when it isn't a bot
|
|
|
|
await self.__increment_count(guild, 'join')
|
|
|
|
|
|
|
|
whisper_type = await guild_settings.join.whisper.state()
|
|
|
|
if whisper_type != 'off':
|
|
|
|
try:
|
|
|
|
await self.__dm_user(member)
|
|
|
|
except:
|
|
|
|
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):
|
|
|
|
"""Listens for member leaves."""
|
|
|
|
|
|
|
|
await self.__handle_event(member.guild, member, 'leave')
|
|
|
|
|
|
|
|
@commands.Cog.listener()
|
|
|
|
async def on_member_ban(self, guild: discord.Guild, member: discord.Member):
|
|
|
|
"""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):
|
|
|
|
"""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):
|
|
|
|
"""Handler for setting toggles."""
|
|
|
|
|
|
|
|
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(
|
|
|
|
("{} notices are now {}."
|
|
|
|
"").format(event.capitalize(), ENABLED if target_state else DISABLED)
|
|
|
|
)
|
|
|
|
|
|
|
|
async def __toggledelete(self, ctx: commands.Context, on_off: bool, event: str):
|
|
|
|
"""Handler for setting delete toggles."""
|
|
|
|
|
|
|
|
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(
|
|
|
|
("Deletion of previous {} notice is now {}."
|
|
|
|
"").format(event, ENABLED if target_state else DISABLED)
|
|
|
|
)
|
|
|
|
|
|
|
|
async def __msg_add(self, ctx: commands.Context, msg_format: str, event: str):
|
|
|
|
"""Handler for adding message formats."""
|
|
|
|
|
|
|
|
guild = ctx.guild
|
|
|
|
|
|
|
|
async with self.config.guild(guild).get_attr(event).messages() as messages:
|
|
|
|
messages.append(msg_format)
|
|
|
|
|
|
|
|
await ctx.send(
|
|
|
|
("New message format for {} notices added."
|
|
|
|
"").format(event)
|
|
|
|
)
|
|
|
|
|
|
|
|
async def __msg_del(self, ctx: commands.Context, event: str):
|
|
|
|
"""Handler for deleting message formats."""
|
|
|
|
|
|
|
|
guild = ctx.guild
|
|
|
|
|
|
|
|
async with self.config.guild(guild).get_attr(event).messages() as messages:
|
|
|
|
if len(messages) == 1:
|
|
|
|
await ctx.send(
|
|
|
|
("I only have one {} message format, so I can't let you delete it."
|
|
|
|
"").format(event)
|
|
|
|
)
|
|
|
|
return
|
|
|
|
|
|
|
|
await self.__msg_list(ctx, event)
|
|
|
|
await ctx.send(
|
|
|
|
("Please enter the number of the {} message format you wish to delete."
|
|
|
|
"").format(event)
|
|
|
|
)
|
|
|
|
|
|
|
|
try:
|
|
|
|
num = await self.__get_number_input(ctx, len(messages))
|
|
|
|
except asyncio.TimeoutError:
|
|
|
|
await ctx.send(
|
|
|
|
("Okay, I won't remove any of the {} message formats."
|
|
|
|
"").format(event)
|
|
|
|
)
|
|
|
|
return
|
|
|
|
else:
|
|
|
|
removed = messages.pop(num - 1)
|
|
|
|
|
|
|
|
await ctx.send(
|
|
|
|
("Done. This {} message format was deleted:\n"
|
|
|
|
"`{}`"
|
|
|
|
"").format(event, removed)
|
|
|
|
)
|
|
|
|
|
|
|
|
async def __msg_list(self, ctx: commands.Context, event: str):
|
|
|
|
"""Handler for listing message formats."""
|
|
|
|
|
|
|
|
guild = ctx.guild
|
|
|
|
|
|
|
|
msg = "{} message formats:\n".format(event.capitalize())
|
|
|
|
async with self.config.guild(guild).get_attr(event).messages() as messages:
|
|
|
|
for n, m in enumerate(messages, start=1):
|
|
|
|
msg += ' {}. {}\n'.format(n, m)
|
|
|
|
|
|
|
|
for page in pagify(msg, ['\n', ' '], 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):
|
|
|
|
"""Handler for actual events."""
|
|
|
|
|
|
|
|
guild_settings = self.config.guild(guild)
|
|
|
|
|
|
|
|
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
|
2019-09-23 18:36:28 +12:00
|
|
|
await self.__delete_message(guild, settings['last'], event)
|
2019-09-22 17:55:45 +12:00
|
|
|
# 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)
|
|
|
|
|
2019-09-23 18:36:28 +12:00
|
|
|
async def __get_channel(self, guild: discord.Guild, event: str) -> discord.TextChannel:
|
2019-09-22 17:55:45 +12:00
|
|
|
"""Gets the best text channel to use for event notices.
|
|
|
|
|
|
|
|
Order of priority:
|
2019-11-06 19:26:29 +13:00
|
|
|
1. User-defined channel for event
|
|
|
|
2. Cog-defined channel for all events
|
|
|
|
3. Guild's system channel (if bot can speak in it)
|
|
|
|
4. First channel that the bot can speak in
|
2019-09-22 17:55:45 +12:00
|
|
|
"""
|
|
|
|
|
|
|
|
channel = None
|
|
|
|
|
2019-09-23 18:36:28 +12:00
|
|
|
if event == 'join':
|
2019-11-06 19:26:29 +13:00
|
|
|
channel_id = await self.config.guild(guild).join.channel()
|
2019-09-23 18:36:28 +12:00
|
|
|
elif event == 'leave':
|
2019-11-06 19:26:29 +13:00
|
|
|
channel_id = await self.config.guild(guild).leave.channel()
|
|
|
|
elif event == 'ban':
|
|
|
|
channel_id = await self.config.guild(guild).ban.channel()
|
|
|
|
elif event == 'unban':
|
|
|
|
channel_id = await self.config.guild(guild).unban.channel()
|
|
|
|
elif event == 'default':
|
|
|
|
channel_id = await self.config.guild(guild).channel()
|
2019-09-23 18:36:28 +12:00
|
|
|
else:
|
|
|
|
raise TypeError("Wrong event type in __get_channel.")
|
|
|
|
|
2019-09-22 17:55:45 +12:00
|
|
|
if channel_id is not None:
|
|
|
|
channel = guild.get_channel(channel_id)
|
|
|
|
|
2019-11-06 19:26:29 +13:00
|
|
|
if channel is None or not self.__can_speak_in(channel):
|
|
|
|
channel_id = await self.config.guild(guild).channel()
|
|
|
|
channel = guild.get_channel(channel_id)
|
|
|
|
|
2019-09-22 17:55:45 +12:00
|
|
|
if channel is None or not self.__can_speak_in(channel):
|
|
|
|
channel = guild.system_channel
|
|
|
|
|
|
|
|
if channel is None or not self.__can_speak_in(channel):
|
|
|
|
for ch in guild.text_channels:
|
|
|
|
if self.__can_speak_in(ch):
|
|
|
|
channel = ch
|
|
|
|
break
|
|
|
|
|
|
|
|
return channel
|
|
|
|
|
|
|
|
async def __get_number_input(self, 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):
|
|
|
|
num = None
|
|
|
|
try:
|
|
|
|
num = int(m.content)
|
|
|
|
except ValueError:
|
|
|
|
pass
|
|
|
|
|
|
|
|
return num is not None \
|
|
|
|
and minimum < num <= maximum \
|
|
|
|
and m.author == author \
|
|
|
|
and m.channel == channel
|
|
|
|
|
|
|
|
try:
|
|
|
|
msg = await self.bot.wait_for('message', check=check, timeout=15.0)
|
|
|
|
except asyncio.TimeoutError:
|
|
|
|
raise
|
|
|
|
else:
|
|
|
|
return int(msg.content)
|
|
|
|
|
2019-09-23 18:36:28 +12:00
|
|
|
async def __delete_message(self, guild: discord.Guild, message_id: int, event: str):
|
2019-09-22 17:55:45 +12:00
|
|
|
"""Attempts to delete the message with the given ID."""
|
|
|
|
|
|
|
|
try:
|
2019-09-23 18:36:28 +12:00
|
|
|
await (await (await self.__get_channel(guild, event)).fetch_message(message_id)).delete()
|
2019-09-22 17:55:45 +12:00
|
|
|
except discord.NotFound:
|
|
|
|
log.warning(
|
|
|
|
("Failed to delete message (ID {}): not found"
|
|
|
|
"").format(message_id)
|
|
|
|
)
|
|
|
|
except discord.Forbidden:
|
|
|
|
log.warning(
|
|
|
|
("Failed to delete message (ID {}): insufficient permissions"
|
|
|
|
"").format(message_id)
|
|
|
|
)
|
|
|
|
except:
|
|
|
|
log.warning(
|
|
|
|
("Failed to delete message (ID {})"
|
|
|
|
"").format(message_id)
|
|
|
|
)
|
|
|
|
|
|
|
|
async def __send_notice(self, guild: discord.guild, user: Union[discord.Member, discord.User], event: str, *,
|
|
|
|
message_format=None) -> Union[discord.Message, None]:
|
|
|
|
"""Sends the notice for the event."""
|
|
|
|
|
|
|
|
format_str = message_format or await self.__get_random_message_format(guild, event)
|
|
|
|
|
|
|
|
count = event == 'join' and await self.config.guild(guild).get_attr(event).counter()
|
|
|
|
plural = ''
|
|
|
|
if count and count != 1:
|
|
|
|
plural = 's'
|
|
|
|
|
2019-09-23 18:36:28 +12:00
|
|
|
channel = await self.__get_channel(guild, event)
|
2019-09-22 17:55:45 +12:00
|
|
|
|
|
|
|
try:
|
|
|
|
return await channel.send(
|
|
|
|
format_str.format(member=user, server=guild, bot=user, count=count or '', plural=plural)
|
|
|
|
)
|
|
|
|
except discord.Forbidden:
|
|
|
|
log.error(
|
|
|
|
("Failed to send {} message to channel ID {1.id} (server ID {2.id}): insufficient permissions"
|
|
|
|
"").format(event, channel, guild)
|
|
|
|
)
|
|
|
|
return None
|
|
|
|
except:
|
|
|
|
log.error(
|
|
|
|
("Failed to send {} message to channel ID {1.id} (server ID {2.id})"
|
|
|
|
"").format(event, channel, guild)
|
|
|
|
)
|
|
|
|
return None
|
|
|
|
|
|
|
|
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):
|
|
|
|
"""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(self.__today())
|
|
|
|
|
|
|
|
if self.__today() > await guild_settings.date():
|
|
|
|
await guild_settings.date.set(self.__today())
|
|
|
|
await guild_settings.get_attr(event).counter.set(0)
|
|
|
|
|
|
|
|
count = 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):
|
|
|
|
"""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))
|
|
|
|
except discord.Forbidden:
|
|
|
|
log.error(
|
|
|
|
("Failed to send DM to member ID {0.id} (server ID {1.id}): insufficient permissions"
|
|
|
|
"").format(member, member.guild)
|
|
|
|
)
|
|
|
|
raise WhisperError("Error.")
|
|
|
|
except:
|
|
|
|
log.error(
|
|
|
|
("Failed to send DM to member ID {0.id} (server ID {1.id})"
|
|
|
|
"").format(member, member.guild)
|
|
|
|
)
|
|
|
|
raise WhisperError("Error.")
|
|
|
|
|
|
|
|
@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 date.today().toordinal()
|