Skip to content

Commit f19025a

Browse files
committed
Merge branch 'master' of https://github.com/verixx/modmail
2 parents 8393eef + 985d0c6 commit f19025a

File tree

12 files changed

+320
-174
lines changed

12 files changed

+320
-174
lines changed

CHANGELOG.md

Lines changed: 80 additions & 89 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,6 @@ Have you sent something with the `?reply` command by accident? Don't fret, you c
5656

5757
Thread conversations are automatically logged with a generated viewable website of the complete thread. Logs are rendered with styled HTML and presented in an aesthetically pleasing way—it blends seamlessly with the mobile version of Discord. An example of a logged conversation: https://logs.modmail.tk/02032d65a6f3
5858

59-
## Automatic Updates
60-
61-
The bot checks for new updates every hour and will automatically update to the newest version. Modmail is under active development, which means that you can always look forward to new and useful features! To disable this functionality, for example, when you customized your fork, you can do so by adding a `disable_autoupdates` config variable and set it to `yes` or `true`.
62-
63-
---
6459

6560
# Contributing
6661

app.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@
2626
"LOG_URL": {
2727
"description": "The url of the log viewer app for viewing self-hosted logs.",
2828
"required": true
29+
},
30+
"HASTE_URL": {
31+
"description": "If You Have Your Own Self Hosted Hastebin then give the link here.",
32+
"required": false
2933
}
3034
}
3135
}

bot.py

Lines changed: 89 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -37,18 +37,20 @@
3737
from discord.ext import commands
3838
from discord.ext.commands.view import StringView
3939

40+
import isodate
41+
4042
from aiohttp import ClientSession
4143
from colorama import init, Fore, Style
4244
from emoji import UNICODE_EMOJI
4345
from motor.motor_asyncio import AsyncIOMotorClient
4446

4547
from core.changelog import Changelog
46-
from core.clients import ModmailApiClient, SelfHostedClient
47-
from core.clients import PluginDatabaseClient
48+
from core.clients import ModmailApiClient, SelfHostedClient, PluginDatabaseClient
4849
from core.config import ConfigManager
4950
from core.utils import info, error
5051
from core.models import Bot
5152
from core.thread import ThreadManager
53+
from core.time import human_timedelta
5254

5355

5456
init()
@@ -431,33 +433,102 @@ async def on_ready(self):
431433
)
432434
logger.info(LINE)
433435

434-
async def process_modmail(self, message):
435-
"""Processes messages sent to the bot."""
436-
436+
async def retrieve_emoji(self):
437437
ctx = SimpleNamespace(bot=self, guild=self.modmail_guild)
438438
converter = commands.EmojiConverter()
439439

440-
blocked_emoji = self.config.get('blocked_emoji', '🚫')
441440
sent_emoji = self.config.get('sent_emoji', '✅')
441+
blocked_emoji = self.config.get('blocked_emoji', '🚫')
442+
443+
if sent_emoji not in UNICODE_EMOJI:
444+
try:
445+
sent_emoji = await converter.convert(
446+
ctx, sent_emoji.strip(':')
447+
)
448+
except commands.BadArgument:
449+
logger.warning(info(f'Sent Emoji ({sent_emoji}) '
450+
f'is not a valid emoji.'))
451+
del self.config.cache['sent_emoji']
452+
await self.config.update()
442453

443454
if blocked_emoji not in UNICODE_EMOJI:
444455
try:
445456
blocked_emoji = await converter.convert(
446457
ctx, blocked_emoji.strip(':')
447458
)
448459
except commands.BadArgument:
449-
pass
460+
logger.warning(info(f'Blocked emoji ({blocked_emoji}) '
461+
'is not a valid emoji.'))
462+
del self.config.cache['blocked_emoji']
463+
await self.config.update()
464+
return sent_emoji, blocked_emoji
450465

451-
if sent_emoji not in UNICODE_EMOJI:
466+
async def process_modmail(self, message):
467+
"""Processes messages sent to the bot."""
468+
sent_emoji, blocked_emoji = await self.retrieve_emoji()
469+
470+
account_age = self.config.get('account_age')
471+
if account_age is None:
472+
account_age = isodate.duration.Duration()
473+
else:
452474
try:
453-
sent_emoji = await converter.convert(
454-
ctx, sent_emoji.strip(':')
455-
)
456-
except commands.BadArgument:
457-
pass
475+
account_age = isodate.parse_duration(account_age)
476+
except isodate.ISO8601Error:
477+
logger.warning('The account age limit needs to be a '
478+
'ISO-8601 duration formatted duration string '
479+
f'greater than 0 days, not "%s".', str(account_age))
480+
del self.config.cache['account_age']
481+
await self.config.update()
482+
account_age = isodate.duration.Duration()
458483

459-
if str(message.author.id) in self.blocked_users:
484+
reason = self.blocked_users.get(str(message.author.id))
485+
if reason is None:
486+
reason = ''
487+
try:
488+
min_account_age = message.author.created_at + account_age
489+
except ValueError as e:
490+
logger.warning(e.args[0])
491+
del self.config.cache['account_age']
492+
await self.config.update()
493+
min_account_age = message.author.created_at
494+
495+
if min_account_age > datetime.utcnow():
496+
# user account has not reached the required time
460497
reaction = blocked_emoji
498+
changed = False
499+
delta = human_timedelta(min_account_age)
500+
501+
if str(message.author.id) not in self.blocked_users:
502+
new_reason = f'System Message: New Account. Required to wait for {delta}.'
503+
self.config.blocked[str(message.author.id)] = new_reason
504+
await self.config.update()
505+
changed = True
506+
507+
if reason.startswith('System Message: New Account.') or changed:
508+
await message.channel.send(embed=discord.Embed(
509+
title='Message not sent!',
510+
description=f'Your must wait for {delta} '
511+
f'before you can contact {self.user.mention}.',
512+
color=discord.Color.red()
513+
))
514+
515+
elif str(message.author.id) in self.blocked_users:
516+
reaction = blocked_emoji
517+
if reason.startswith('System Message: New Account.'):
518+
# Met the age limit already
519+
reaction = sent_emoji
520+
del self.config.blocked[str(message.author.id)]
521+
await self.config.update()
522+
else:
523+
end_time = re.search(r'%(.+?)%$', reason)
524+
if end_time is not None:
525+
after = (datetime.fromisoformat(end_time.group(1)) -
526+
datetime.utcnow()).total_seconds()
527+
if after <= 0:
528+
# No longer blocked
529+
reaction = sent_emoji
530+
del self.config.blocked[str(message.author.id)]
531+
await self.config.update()
461532
else:
462533
reaction = sent_emoji
463534

@@ -542,8 +613,8 @@ async def on_message(self, message):
542613
'Command "{}" is not found'.format(ctx.invoked_with)
543614
)
544615
self.dispatch('command_error', ctx, exc)
545-
546-
async def on_typing(self, channel, user, when):
616+
617+
async def on_typing(self, channel, user, _):
547618
if isinstance(channel, discord.DMChannel):
548619
if not self.config.get('user_typing'):
549620
return
@@ -720,13 +791,13 @@ async def autoupdate_loop(self):
720791

721792
if self.config.get('disable_autoupdates'):
722793
logger.warning(info('Autoupdates disabled.'))
723-
logger.warning(LINE)
794+
logger.info(LINE)
724795
return
725796

726797
if self.self_hosted and not self.config.get('github_access_token'):
727798
logger.warning(info('GitHub access token not found.'))
728799
logger.warning(info('Autoupdates disabled.'))
729-
logger.warning(LINE)
800+
logger.info(LINE)
730801
return
731802

732803
while not self.is_closed():

cogs/modmail.py

Lines changed: 75 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import discord
66
from discord.ext import commands
77

8+
import re
9+
810
from dateutil import parser
911
from natural.date import duration
1012

@@ -603,7 +605,7 @@ async def contact(self, ctx,
603605
category: Optional[discord.CategoryChannel] = None, *,
604606
user: Union[discord.Member, discord.User]):
605607
"""Create a thread with a specified member.
606-
608+
607609
If the optional category argument is passed, the thread
608610
will be created in the specified category.
609611
"""
@@ -630,7 +632,7 @@ async def contact(self, ctx,
630632
embed = discord.Embed(
631633
title='Created thread',
632634
description=f'Thread started in {thread.channel.mention} '
633-
f'for {user.mention}',
635+
f'for {user.mention}.',
634636
color=self.bot.main_color
635637
)
636638

@@ -665,15 +667,22 @@ async def blocked(self, ctx):
665667
embed.add_field(name='Unknown', value=val, inline=False)
666668

667669
if not users and not not_reachable:
668-
embed.description = 'Currently there are no blocked users'
670+
embed.description = 'Currently there are no blocked users.'
669671

670672
await ctx.send(embed=embed)
671673

672674
@commands.command()
673675
@trigger_typing
674676
@checks.has_permissions(manage_channels=True)
675-
async def block(self, ctx, user: User = None, *, reason=None):
676-
"""Block a user from using Modmail."""
677+
async def block(self, ctx, user: Optional[User] = None, *,
678+
after: UserFriendlyTime = None):
679+
"""
680+
Block a user from using Modmail.
681+
682+
Note: reasons that start with "System Message: " are reserved for internal
683+
use only.
684+
"""
685+
reason = ''
677686

678687
if user is None:
679688
thread = ctx.thread
@@ -682,22 +691,48 @@ async def block(self, ctx, user: User = None, *, reason=None):
682691
else:
683692
raise commands.UserInputError
684693

694+
if after is not None:
695+
reason = after.arg
696+
if reason.startswith('System Message: '):
697+
raise commands.UserInputError
698+
elif re.search(r'%(.+?)%$', reason) is not None:
699+
raise commands.UserInputError
700+
elif after.dt > after.now:
701+
reason = f'{reason} %{after.dt.isoformat()}%'
702+
703+
if not reason:
704+
reason = None
705+
685706
mention = user.mention if hasattr(user, 'mention') else f'`{user.id}`'
686707

687-
if str(user.id) not in self.bot.blocked_users:
708+
extend = f' for `{reason}`' if reason is not None else ''
709+
msg = self.bot.blocked_users.get(str(user.id))
710+
if msg is None:
711+
msg = ''
712+
713+
if str(user.id) not in self.bot.blocked_users or extend or msg.startswith('System Message: '):
714+
if str(user.id) in self.bot.blocked_users:
715+
716+
old_reason = msg.strip().rstrip('.') or 'no reason'
717+
embed = discord.Embed(
718+
title='Success',
719+
description=f'{mention} was previously blocked for '
720+
f'"{old_reason}". {mention} is now blocked{extend}.',
721+
color=self.bot.main_color
722+
)
723+
else:
724+
embed = discord.Embed(
725+
title='Success',
726+
color=self.bot.main_color,
727+
description=f'{mention} is now blocked{extend}.'
728+
)
688729
self.bot.config.blocked[str(user.id)] = reason
689730
await self.bot.config.update()
690-
extend = f'for `{reason}`' if reason else ''
691-
embed = discord.Embed(
692-
title='Success',
693-
color=self.bot.main_color,
694-
description=f'{mention} is now blocked ' + extend
695-
)
696731
else:
697732
embed = discord.Embed(
698733
title='Error',
699734
color=discord.Color.red(),
700-
description=f'{mention} is already blocked'
735+
description=f'{mention} is already blocked.'
701736
)
702737

703738
return await ctx.send(embed=embed)
@@ -706,7 +741,12 @@ async def block(self, ctx, user: User = None, *, reason=None):
706741
@trigger_typing
707742
@checks.has_permissions(manage_channels=True)
708743
async def unblock(self, ctx, *, user: User = None):
709-
"""Unblocks a user from using Modmail."""
744+
"""
745+
Unblocks a user from using Modmail.
746+
747+
Note: reasons start with "System Message: " are reserved for internal
748+
use only.
749+
"""
710750

711751
if user is None:
712752
thread = ctx.thread
@@ -718,17 +758,32 @@ async def unblock(self, ctx, *, user: User = None):
718758
mention = user.mention if hasattr(user, 'mention') else f'`{user.id}`'
719759

720760
if str(user.id) in self.bot.blocked_users:
761+
msg = self.bot.blocked_users.get(str(user.id))
762+
if msg is None:
763+
msg = ''
721764
del self.bot.config.blocked[str(user.id)]
722765
await self.bot.config.update()
723-
embed = discord.Embed(
724-
title='Success',
725-
color=self.bot.main_color,
726-
description=f'{mention} is no longer blocked'
727-
)
766+
767+
if msg.startswith('System Message: '):
768+
# If the user is blocked internally (for example: below minimum account age)
769+
# Show an extended message stating the original internal message
770+
reason = msg[16:].strip().rstrip('.') or 'no reason'
771+
embed = discord.Embed(
772+
title='Success',
773+
description=f'{mention} was previously blocked internally due to '
774+
f'"{reason}". {mention} is no longer blocked.',
775+
color=self.bot.main_color
776+
)
777+
else:
778+
embed = discord.Embed(
779+
title='Success',
780+
color=self.bot.main_color,
781+
description=f'{mention} is no longer blocked.'
782+
)
728783
else:
729784
embed = discord.Embed(
730785
title='Error',
731-
description=f'{mention} is not blocked',
786+
description=f'{mention} is not blocked.',
732787
color=discord.Color.red()
733788
)
734789

0 commit comments

Comments
 (0)