Skip to content

Commit 6ba1711

Browse files
committed
Merge from "master"
2 parents fb31870 + f7f5a74 commit 6ba1711

File tree

6 files changed

+148
-53
lines changed

6 files changed

+148
-53
lines changed

CHANGELOG.md

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,24 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

77

8+
# v2.10.0
9+
10+
### Added
11+
- `anonreply` command to anonymously reply to the recipient.
12+
The username of the anonymous user defaults to the `mod_tag` (the footer text of a mod reply). The avatar defaults the guild icon url. However you can change both of these via the `anon_username`, `anon_avatar_url` and `anon_tag` config variables.
13+
14+
### Changed
15+
- Your bot now logs all messages sent in a thread channel, including discussions that take place. You can now toggle to view them in the log viewer app.
16+
817
# v2.9.4
9-
Fixed a small bug due to a typo.
18+
19+
### Fixed
20+
- Small bug due to a typo.
1021

1122
# v2.9.3
12-
Forgot to enable custom embed colors.
23+
24+
### Changed
25+
- Forgot to enable custom embed colors.
1326

1427
### Added
1528
- Ability to set a custom `mod_tag` (the text in the footer of the mod reply embed, which by default says "Moderator")

bot.py

Lines changed: 43 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,15 @@
2222
SOFTWARE.
2323
"""
2424

25-
__version__ = '2.9.4'
25+
__version__ = '2.10.0'
2626

2727
import discord
2828
from discord.enums import ActivityType
2929
from discord.ext import commands
3030
from discord.ext.commands.view import StringView
3131

3232
from os import listdir
33-
from asyncio import sleep
33+
from asyncio import sleep, Event
3434
from textwrap import dedent
3535
from datetime import datetime
3636
from types import SimpleNamespace
@@ -63,7 +63,8 @@ def __init__(self):
6363
self.threads = ThreadManager(self)
6464
self.session = ClientSession(loop=self.loop)
6565
self.config = ConfigManager(self)
66-
self.self_hosted = self.config.get('mongo_uri') is not None
66+
self.self_hosted = bool(self.config.get('mongo_uri', ''))
67+
self._connected = Event()
6768

6869
if self.self_hosted:
6970
self.db = AsyncIOMotorClient(self.config.mongo_uri).modmail_bot
@@ -213,6 +214,7 @@ async def on_connect(self):
213214
print(line)
214215
else:
215216
print('Mode: Self-hosting logs.')
217+
await self.validate_database_connection()
216218
print(line)
217219
print(Fore.CYAN + 'Connected to gateway.')
218220

@@ -232,18 +234,19 @@ async def on_connect(self):
232234
activity = discord.Activity(type=activity_type, name=message,
233235
url=url)
234236
await self.change_presence(activity=activity)
237+
self._connected.set()
235238

236239
async def on_ready(self):
237240
"""Bot startup, sets uptime."""
241+
await self._connected.wait()
238242
print(dedent(f"""
239-
{line}
240-
{Fore.CYAN}Client ready.
241-
{line}
242-
{Fore.CYAN}Logged in as: {self.user}
243-
{Fore.CYAN}User ID: {self.user.id}
244-
{Fore.CYAN}Guild ID: {self.guild.id if self.guild else 0}
245-
{line}
246-
""").strip())
243+
{line}
244+
{Fore.CYAN}Client ready.
245+
{line}
246+
{Fore.CYAN}Logged in as: {self.user}
247+
{Fore.CYAN}User ID: {self.user.id}
248+
{Fore.CYAN}Guild ID: {self.guild.id if self.guild else 0}
249+
{line}""").strip())
247250

248251
if not self.guild:
249252
print(f'{Fore.RED}{Style.BRIGHT}WARNING - The GUILD_ID '
@@ -388,7 +391,13 @@ async def on_message(self, message):
388391
if cmd in self.snippets:
389392
message.content = f'{prefix}reply {self.snippets[cmd]}'
390393

391-
await self.process_commands(message)
394+
ctx = await self.get_context(message)
395+
if ctx.command:
396+
return await self.invoke(ctx)
397+
398+
thread = await self.threads.find(channel=ctx.channel)
399+
if thread is not None:
400+
await self.modmail_api.append_log(message, type_='internal')
392401

393402
async def on_guild_channel_delete(self, channel):
394403
if channel.guild != self.modmail_guild:
@@ -461,11 +470,9 @@ def overwrites(self, ctx): # TODO: can this be static?
461470
overwrites[role] = discord.PermissionOverwrite(
462471
read_messages=True
463472
)
464-
465473
return overwrites
466474

467475
async def validate_api_token(self):
468-
valid = True
469476
try:
470477
self.config.modmail_api_token
471478
except KeyError:
@@ -477,21 +484,31 @@ async def validate_api_token(self):
477484
'input a MONGO_URI config variable.')
478485
print('A modmail api token is not needed '
479486
'if you are self-hosting logs.')
480-
valid = False
487+
return await self.logout()
481488
else:
482489
valid = await self.modmail_api.validate_token()
483490
if not valid:
484-
print(Fore.RED + Style.BRIGHT, end='')
491+
print(Fore.RED, end='')
485492
print('Invalid MODMAIL_API_TOKEN - get one '
486493
'from https://dashboard.modmail.tk')
487-
finally:
488-
if not valid:
489-
await self.logout()
490-
else:
491-
user = await self.modmail_api.get_user_info()
492-
username = user['user']['username']
493-
print(Style.RESET_ALL + Fore.CYAN + 'Validated token.')
494-
print('GitHub user: ' + username + Style.RESET_ALL)
494+
return await self.logout()
495+
496+
user = await self.modmail_api.get_user_info()
497+
username = user['user']['username']
498+
print(Style.RESET_ALL + Fore.CYAN + 'Validated token.')
499+
print('GitHub user: ' + username + Style.RESET_ALL)
500+
501+
async def validate_database_connection(self):
502+
try:
503+
await self.db.command('buildinfo')
504+
except Exception as e:
505+
print(Fore.RED, end='')
506+
print('Something went wrong while connecting to the database.')
507+
print(type(e).__name__, e, sep=': ')
508+
return await self.logout()
509+
else:
510+
print(Style.RESET_ALL + Fore.CYAN +
511+
'Successfully connected to the database.')
495512

496513
async def data_loop(self):
497514
await self.wait_until_ready()
@@ -524,11 +541,13 @@ async def autoupdate_loop(self):
524541

525542
if self.config.get('disable_autoupdates'):
526543
print('Autoupdates disabled.')
544+
print(line)
527545
return
528546

529547
if self.self_hosted and not self.config.get('github_access_token'):
530548
print('Github access token not found.')
531549
print('Autoupdates disabled.')
550+
print(line)
532551
return
533552

534553
while True:

cogs/modmail.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,6 @@ async def logs(self, ctx, *, member: User = None):
412412
await session.run()
413413

414414
@commands.command()
415-
@trigger_typing
416415
async def reply(self, ctx, *, msg=''):
417416
"""Reply to users using this command.
418417
@@ -422,15 +421,24 @@ async def reply(self, ctx, *, msg=''):
422421
ctx.message.content = msg
423422
thread = await self.bot.threads.find(channel=ctx.channel)
424423
if thread:
424+
await ctx.trigger_typing()
425425
await thread.reply(ctx.message)
426426

427427
@commands.command()
428-
@trigger_typing
428+
async def anonreply(self, ctx, *, msg=''):
429+
ctx.message.content = msg
430+
thread = await self.bot.threads.find(channel=ctx.channel)
431+
if thread:
432+
await ctx.trigger_typing()
433+
await thread.reply(ctx.message, anonymous=True)
434+
435+
@commands.command()
429436
async def note(self, ctx, *, msg=''):
430437
"""Take a note about the current thread, useful for noting context."""
431438
ctx.message.content = msg
432439
thread = await self.bot.threads.find(channel=ctx.channel)
433440
if thread:
441+
await ctx.trigger_typing()
434442
await thread.note(ctx.message)
435443

436444
@commands.command()

core/clients.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -195,8 +195,15 @@ def append_log(self, message: Message, channel_id: Union[str, int] = '',
195195
},
196196
# message properties
197197
'content': message.content,
198-
'attachments': [i.url for i in message.attachments],
199-
'type': type_
198+
'type': type_,
199+
'attachments': [
200+
{
201+
'id': a.id,
202+
'filename': a.filename,
203+
'is_image': a.width is not None,
204+
'size': a.size,
205+
'url': a.url
206+
} for a in message.attachments]
200207
}
201208
}
202209
return self.request(self.LOGS + f'/{channel_id}',
@@ -287,18 +294,23 @@ async def append_log(self, message: Message,
287294
payload = {
288295
'timestamp': str(message.created_at),
289296
'message_id': str(message.id),
290-
# author
291297
'author': {
292298
'id': str(message.author.id),
293299
'name': message.author.name,
294300
'discriminator': message.author.discriminator,
295301
'avatar_url': message.author.avatar_url,
296302
'mod': not isinstance(message.channel, DMChannel),
297303
},
298-
# message properties
299304
'content': message.content,
300-
'attachments': [i.url for i in message.attachments],
301-
'type': type_
305+
'type': type_,
306+
'attachments': [
307+
{
308+
'id': a.id,
309+
'filename': a.filename,
310+
'is_image': a.width is not None,
311+
'size': a.size,
312+
'url': a.url
313+
} for a in message.attachments]
302314
}
303315

304316
return await self.logs.find_one_and_update(

core/config.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,18 @@ class ConfigManager:
1010
"""Class that manages a cached configuration"""
1111

1212
allowed_to_change_in_command = {
13-
'activity_message', 'activity_type', 'log_channel_id',
14-
'mention', 'disable_autoupdates', 'prefix',
15-
'main_category_id', 'sent_emoji', 'blocked_emoji',
16-
'thread_creation_response', 'twitch_url', 'mod_color',
17-
'recipient_color', 'mod_tag'
13+
# activity
14+
'activity_message', 'activity_type', 'twitch_url',
15+
# bot settings
16+
'main_category_id', 'disable_autoupdates', 'prefix', 'mention',
17+
# logging
18+
'log_channel_id',
19+
# threads
20+
'sent_emoji', 'blocked_emoji', 'thread_creation_response',
21+
# moderation
22+
'recipient_color', 'mod_tag', 'mod_color',
23+
# anonymous message
24+
'anon_username', 'anon_avatar_url', 'anon_tag'
1825
}
1926

2027
internal_keys = {

core/thread.py

Lines changed: 50 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ async def note(self, message):
196196
self.send(message, self.channel, note=True)
197197
)
198198

199-
async def reply(self, message):
199+
async def reply(self, message, anonymous=False):
200200
if not message.content and not message.attachments:
201201
raise UserInputError
202202
if all(not g.get_member(self.id) for g in self.bot.guilds):
@@ -210,12 +210,22 @@ async def reply(self, message):
210210

211211
tasks = [
212212
# in thread channel
213-
self.send(message, self.channel, from_mod=True),
213+
self.send(message,
214+
destination=self.channel,
215+
from_mod=True,
216+
anonymous=anonymous),
214217
# to user
215-
self.send(message, self.recipient, from_mod=True),
218+
self.send(message,
219+
destination=self.recipient,
220+
from_mod=True,
221+
anonymous=anonymous)
216222
]
217223

218-
await self.bot.modmail_api.append_log(message, self.channel.id)
224+
await self.bot.modmail_api.append_log(
225+
message,
226+
self.channel.id,
227+
type_='anonymous' if anonymous else 'thread_message'
228+
)
219229

220230
if self.close_task is not None:
221231
# cancel closing if a thread message is sent.
@@ -232,7 +242,7 @@ async def reply(self, message):
232242
await asyncio.gather(*tasks)
233243

234244
async def send(self, message, destination=None,
235-
from_mod=False, note=False):
245+
from_mod=False, note=False, anonymous=False):
236246
if self.close_task is not None:
237247
# cancel closing if a thread message is sent.
238248
await self.cancel_closure()
@@ -260,17 +270,31 @@ async def send(self, message, destination=None,
260270
system_avatar_url = 'https://discordapp.com/assets/' \
261271
'f78426a064bc9dd24847519259bc42af.png'
262272

263-
# store message id in hidden url
264273
if not note:
265-
embed.set_author(name=author,
266-
icon_url=author.avatar_url,
274+
if anonymous and from_mod and \
275+
not isinstance(destination, discord.TextChannel):
276+
# Anonymously sending to the user.
277+
name = self.bot.config.get(
278+
'anon_username',
279+
self.bot.config.get('mod_tag', 'Moderator')
280+
)
281+
avatar_url = self.bot.config.get(
282+
'anon_avatar_url',
283+
self.bot.guild.icon_url
284+
)
285+
else:
286+
# Normal message
287+
name = str(author)
288+
avatar_url = author.avatar_url
289+
290+
embed.set_author(name=name,
291+
icon_url=avatar_url,
267292
url=message.jump_url)
268293
else:
269-
embed.set_author(
270-
name=f'Note ({author.name})',
271-
icon_url=system_avatar_url,
272-
url=message.jump_url
273-
)
294+
# Special note messages
295+
embed.set_author(name=f'Note ({author.name})',
296+
icon_url=system_avatar_url,
297+
url=message.jump_url)
274298

275299
delete_message = not bool(message.attachments)
276300

@@ -318,7 +342,19 @@ async def send(self, message, destination=None,
318342

319343
if from_mod:
320344
embed.color = self.bot.mod_color
321-
embed.set_footer(text=self.bot.config.get('mod_tag', 'Moderator'))
345+
# Anonymous reply sent in thread channel
346+
if anonymous and isinstance(destination, discord.TextChannel):
347+
embed.set_footer(text='Anonymous Reply')
348+
# Normal messages
349+
elif not anonymous:
350+
embed.set_footer(
351+
text=self.bot.config.get('mod_tag', 'Moderator')
352+
)
353+
# Anonymous reply sent to user
354+
else:
355+
embed.set_footer(
356+
text=self.bot.config.get('anon_tag', 'Response')
357+
)
322358
elif note:
323359
embed.color = discord.Color.blurple()
324360
else:

0 commit comments

Comments
 (0)