Skip to content

Commit

Permalink
Improved close command + custom thread creation response. (#124)
Browse files Browse the repository at this point in the history
* Add close after functionality to the thread class.

* Add a ctx.thread for convienience.

* Move thread close logging within thread class

* Update close-cmd branch with master (#123)

* Add support for multiple image and file attachments

* Add support for custom sent emoji and blocked emoji resolves #112

* Fix emoji handling

* Fixed a bug where blocked users are still able to message modmail

* Improve about command style

* Increment version

* Fix changelog version parsing

* Add changelog docstring

* Add dhooks to requirements for eval stuff

* Add discord server badge

* Update README.md

added forks badge

* Use blurple color

* Use stars instead

* Let anyone use alias invoked eval commands

* Add an instances badge

* Update README.md

* Update README.md

* Update README.md

* Remove the first desc

* Update README.md

* Add patreon in readme

* Update README.md

* Improve logs format

* Update README.md

* Rainbow colors + patreon

* Change order

* Test

* Revert "Test"

This reverts commit 56e68fc.

* Basic closing after a certain amount.

* Increment version

* Update requirements.txt

* Add UserFriendlyTime conveter from Rapptz

* Use user friendly time converter.

* Add support for custom close message

* Default arg to None

* Add ability to silently close.

* fix

* Add ability to set custom thread creation response

* Change thread creation title

* Update thread.py
  • Loading branch information
kyb3r authored Jan 12, 2019
1 parent 27a33dd commit 4d20da4
Show file tree
Hide file tree
Showing 7 changed files with 355 additions and 87 deletions.
14 changes: 13 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,20 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

# v[unreleased]


# v2.1.0

### Added
- Ability to set a custom thread creation response message.
- Do this via `config set thread_creation_response [message]`

### Changed
- Improve logs command format.
- Improve thread log channel message to have more relevant info.
- Improve close command.
- You now can close the thread after a delay and use a custom thread close message.
- You also now have the ability to close a thread silently.

# v2.0.10

Expand Down
51 changes: 13 additions & 38 deletions bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
SOFTWARE.
"""

__version__ = '2.0.10'
__version__ = '2.1.0'

import asyncio
import textwrap
Expand Down Expand Up @@ -284,46 +284,21 @@ async def on_message(self, message):
async def on_guild_channel_delete(self, channel):
if channel.guild != self.modmail_guild:
return
thread = await self.threads.find(channel=channel)
if thread:
del self.threads.cache[thread.id]

mod = None

audit_logs = self.modmail_guild.audit_logs()
entry = await audit_logs.find(lambda e: e.target.id == channel.id)
mod = entry.user
if mod.bot:
return

log_data = await self.modmail_api.post_log(channel.id, {
'open': False,
'closed_at': str(datetime.datetime.utcnow()),
'closer': {
'id': str(mod.id),
'name': mod.name,
'discriminator': mod.discriminator,
'avatar_url': mod.avatar_url,
'mod': True
}})

em = discord.Embed(title='Thread Closed')
em.description = f'{mod.mention} has closed this modmail thread.'
em.color = discord.Color.red()

try:
await thread.recipient.send(embed=em)
except:
pass

log_url = f"https://logs.modmail.tk/{log_data['user_id']}/{log_data['key']}"
mod = None
audit_logs = self.modmail_guild.audit_logs()
entry = await audit_logs.find(lambda e: e.target.id == channel.id)
mod = entry.user

user = thread.recipient.mention if thread.recipient else f'`{thread.id}`'
if mod.bot:
return

thread = await self.threads.find(channel=channel)
if not thread:
return

await thread.close(closer=mod, silent=True, delete_channel=False)

desc = f"[`{log_data['key']}`]({log_url}) {mod.mention} closed a thread with {user}"
em = discord.Embed(description=desc, color=em.color)
em.set_author(name='Thread closed', url=log_url)
await self.log_channel.send(embed=em)

async def on_message_delete(self, message):
"""Support for deleting linked messages"""
Expand Down
92 changes: 54 additions & 38 deletions cogs/modmail.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@


import datetime
from typing import Optional, Union
import re

import discord
from discord.ext import commands
import datetime
import dateutil.parser
from typing import Optional, Union

from core.decorators import trigger_typing
from core.paginator import PaginatorSession
from core.time import UserFriendlyTime, human_timedelta


class Modmail:
Expand Down Expand Up @@ -117,50 +122,61 @@ async def move(self, ctx, *, category: discord.CategoryChannel):
await thread.channel.edit(category=category)
await ctx.message.add_reaction('✅')

@commands.command(name='close')
@commands.has_permissions(manage_channels=True)
async def _close(self, ctx):
"""Close the current thread."""

thread = await self.bot.threads.find(channel=ctx.channel)
if not thread:
return await ctx.send('This is not a modmail thread.')

await thread.close()
async def send_scheduled_close_message(self, ctx, after, silent=False):
human_delta = human_timedelta(after.dt)

silent = '*silently* ' if silent else ''

em = discord.Embed(title='Thread Closed')
em.description = f'{ctx.author.mention} has closed this modmail thread.'
em.color = discord.Color.red()
em = discord.Embed(
title='Scheduled close',
description=f'This thread will close {silent}in {human_delta}.',
color=discord.Color.red()
)

try:
await thread.recipient.send(embed=em)
except:
pass
if after.arg and not silent:
em.add_field(name='Message', value=after.arg)

em.set_footer(text='Closing will be cancelled if a thread message is sent.')
em.timestamp = after.dt

await ctx.send(embed=em)

# Logging
log_channel = self.bot.log_channel
@commands.command(name='close', usage='[after] [close message]')
async def _close(self, ctx, *, after: UserFriendlyTime=None):
"""Close the current thread.
Close after a period of time:
- `close in 5 hours`
- `close 2m30s`
Custom close messages:
- `close 2 hours The issue has been resolved.`
- `close We will contact you once we find out more.`
log_data = await self.bot.modmail_api.post_log(ctx.channel.id, {
'open': False, 'closed_at': str(datetime.datetime.utcnow()), 'closer': {
'id': str(ctx.author.id),
'name': ctx.author.name,
'discriminator': ctx.author.discriminator,
'avatar_url': ctx.author.avatar_url,
'mod': True
}
})
Silently close a thread (no message)
- `close silently`
- `close in 10m silently`
"""

if isinstance(log_data, str):
print(log_data) # error
thread = await self.bot.threads.find(channel=ctx.channel)
if not thread:
return

now = datetime.datetime.utcnow()

log_url = f"https://logs.modmail.tk/{log_data['user_id']}/{log_data['key']}"
close_after = (after.dt - now).total_seconds() if after else 0
message = after.arg if after else None
silent = str(message).lower() in {'silent', 'silently'}

user = thread.recipient.mention if thread.recipient else f'`{thread.id}`'
if after and after.dt > now:
await self.send_scheduled_close_message(ctx, after, silent)

desc = f"[`{log_data['key']}`]({log_url}) {ctx.author.mention} closed a thread with {user}"
em = discord.Embed(description=desc, color=em.color)
em.set_author(name='Thread closed', url=log_url)
await log_channel.send(embed=em)
await thread.close(
closer=ctx.author,
after=close_after,
message=message,
silent=silent,
)

@commands.command()
async def nsfw(self, ctx):
Expand Down
11 changes: 7 additions & 4 deletions core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,18 @@ class ConfigManager:

allowed_to_change_in_command = {
'status', 'log_channel_id', 'mention', 'disable_autoupdates', 'prefix',
'main_category_id', 'sent_emoji', 'blocked_emoji'
'main_category_id', 'sent_emoji', 'blocked_emoji', 'thread_creation_response'
}

internal_keys = {
'token', 'snippets', 'aliases', 'owners', 'modmail_api_token',
'guild_id', 'modmail_guild_id', 'blocked'
'snippets', 'aliases', 'blocked'
}

protected_keys = {
'token', 'owners', 'modmail_api_token', 'guild_id', 'modmail_guild_id',
}

valid_keys = allowed_to_change_in_command.union(internal_keys)
valid_keys = allowed_to_change_in_command.union(internal_keys).union(protected_keys)

def __init__(self, bot):
self.bot = bot
Expand Down
79 changes: 73 additions & 6 deletions core/thread.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ def __init__(self, manager, recipient):
self.recipient = recipient
self.channel = None
self.ready_event = asyncio.Event()
self.close_task = None
self.close_after = 0 # seconds

def __repr__(self):
return f'Thread(recipient="{self.recipient}", channel={self.channel.id})'
Expand All @@ -39,10 +41,64 @@ def ready(self):
def ready(self, flag):
if flag is True:
self.ready_event.set()

async def _close_after(self, after, **kwargs):
await asyncio.sleep(after)
await self.close(**kwargs)

async def close(self, *, closer, after=0, silent=False, delete_channel=True, message=None):
'''Close a thread now or after a set time in seconds'''
if after > 0:
if self.close_task is not None and not self.close_task.cancelled():
self.close_task.cancel()
self.close_task = asyncio.create_task(self._close_after(after, closer=closer, silent=silent, message=message))
return

def close(self):
del self.manager.cache[self.id]
return self.channel.delete()

# Logging
log_data = await self.bot.modmail_api.post_log(self.channel.id, {
'open': False, 'closed_at': str(datetime.datetime.utcnow()), 'closer': {
'id': str(closer.id),
'name': closer.name,
'discriminator': closer.discriminator,
'avatar_url': closer.avatar_url,
'mod': True
}
})

if isinstance(log_data, str):
print(log_data) # errored somehow on server

log_url = f"https://logs.modmail.tk/{log_data['user_id']}/{log_data['key']}"
user = self.recipient.mention if self.recipient else f'`{self.id}`'

if log_data['messages']:
msg = log_data['messages'][0]['content']
sneak_peak = msg if len(msg) < 50 else msg[:48] + '...'
else:
sneak_peak = 'No content'

desc = f"[`{log_data['key']}`]({log_url}) {user}: {sneak_peak}"

em = discord.Embed(description=desc, color=discord.Color.red())
em.set_author(name='Thread closed', url=log_url)
em.set_footer(text=f'Closed by: {closer} ({closer.id})')

tasks = [self.bot.log_channel.send(embed=em)]

em = discord.Embed(title='Thread Closed')
em.description = message or f'{closer.mention} has closed this modmail thread.'
em.color = discord.Color.red()

if not silent:
tasks.append(self.recipient.send(embed=em))

if delete_channel:
tasks.append(self.channel.delete())

await asyncio.gather(*tasks)

async def _edit_thread_message(self, channel, message_id, message):
async for msg in channel.history():
Expand All @@ -68,12 +124,22 @@ async def reply(self, message):
raise commands.UserInputError
if self.recipient not in self.bot.guild.members:
return await message.channel.send('This user is no longer in the server and is thus unreachable.')
await asyncio.gather(

tasks = [
self.send(message, self.channel, from_mod=True), # in thread channel
self.send(message, self.recipient, from_mod=True) # to user
)
]

if self.close_task is not None and not self.close_task.cancelled():
self.close_task.cancel() # cancel closing if a thread message is sent.
tasks.append(self.channel.send(embed=discord.Embed(color=discord.Color.red(), description='Scheduled close has been cancelled.')))

await asyncio.gather(*tasks)

async def send(self, message, destination=None, from_mod=False, delete_message=True):
if self.close_task is not None and not self.close_task.cancelled():
self.close_task.cancel() # cancel closing if a thread message is sent.
await self.channel.send(embed=discord.Embed(color=discord.Color.red(), description='Scheduled close has been cancelled.'))
if not self.ready:
await self.wait_until_ready()

Expand Down Expand Up @@ -123,6 +189,7 @@ async def send(self, message, destination=None, from_mod=False, delete_message=T

file_upload_count = 1


for att in attachments:
em.add_field(name=f'File upload ({file_upload_count})', value=f'[{att[1]}]({att[0]})')
file_upload_count += 1
Expand Down Expand Up @@ -224,8 +291,8 @@ async def create(self, recipient, *, creator=None):
"""Creates a modmail thread"""

em = discord.Embed(
title='Thread started' if creator else 'Thanks for the message!',
description='The moderation team will get back to you as soon as possible!',
title='Thread created!',
description=self.bot.config.get('thread_creation_response', 'The moderation team will get back to you as soon as possible!'),
color=discord.Color.green()
)

Expand Down
Loading

0 comments on commit 4d20da4

Please sign in to comment.