Skip to content

Commit f5b1fc5

Browse files
committed
Merge branch 'master' of https://github.com/verixx/modmail
2 parents 8ed9c88 + 95d3b87 commit f5b1fc5

File tree

5 files changed

+110
-80
lines changed

5 files changed

+110
-80
lines changed

README.md

Lines changed: 46 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<div align="center">
22
<img src="https://i.imgur.com/o558Qnq.png" align="center">
33
<br>
4-
<strong><i>A simple and functional Modmail bot for Discord.</i></strong>
4+
<strong><i>A feature rich Modmail bot for Discord.</i></strong>
55
<br>
66
<br>
77

@@ -25,31 +25,59 @@
2525
</div>
2626

2727

28-
## How Does Modmail Work?
29-
30-
<img src="https://i.imgur.com/GGukNDs.png" align="right" height="350">
31-
32-
When a user sends a direct message to the bot, a channel is created within an isolated category. This channel is where messages will be relayed. To reply to the message, simply use the command `?reply` in the channel. A full list of commands can be found in the [wiki](https://github.com/kyb3r/modmail/wiki) or by using the `?help` command.
28+
## What is Modmail?
29+
30+
Modmail's core functionality provides an efficient communications interface between server members and staff. When a member sends a direct message to the bot, a channel is created within an isolated category for that member. This channel is where messages will be relayed and where any available staff member can respond to that user.
31+
32+
## Features
33+
34+
* **Highly Customisable**
35+
* Bot activity, prefix, category, log channel, etc.
36+
* Fully customisable command permission system.
37+
* Interface elements (color, responses, reactions, etc.)
38+
* Snippets and *command aliases*
39+
* Minimum account/guild age in order to create a thread.
40+
* **Thread logs**
41+
* When you close a thread, a [log link](https://logs.modmail.tk/example) is generated and posted to your log channel.
42+
* Rendered in styled HTML like Discord.
43+
* Optional login in via Discord to protect your logs.
44+
* See past logs of a user with `?logs`
45+
* Searchable by text queries using `?logs search`
46+
* **Robust implementation**
47+
* Scheduled tasks in human time, e.g. `?close in 2 hours silently`.
48+
* Editing and deleting messages is synced on both ends.
49+
* Support for the full range of message content (mutliple images, files).
50+
* Paginated commands interfaces via reactions.
51+
52+
This list is ever growing thanks to active development and our exceptional contributors. See a full list of documented commands by using the `help` command.
3353

3454
## Installation
3555

36-
Currently, the easiest and fastest way to set up the bot is by using Heroku, which is a service that offers a free plan for hosting applications. If you choose to install the bot using Heroku, you will not need to download anything. The [**installation guide**](https://github.com/kyb3r/modmail/wiki/Installation) will guide you through the entire installation process. If you run into any problems, join our [Discord server](https://discord.gg/etJNHCQ) for help and support. Even if you don't have any issues, you should come and check out our awesome Discord community! :wink:
37-
38-
Interactive installation: [**https://taaku18.github.io/modmail/installation**](https://taaku18.github.io/modmail/installation)
39-
40-
41-
# Notable Features
56+
### Heroku
57+
Currently, the easiest way to set up the bot is by using Heroku, a container-based cloud platform. Installation via Heroku is done in your web browser and keeps the bot online 24/7 for free. The [**installation guide**](https://github.com/kyb3r/modmail/wiki/Installation) will guide you through the entire installation process. If you run into any problems, join the [development server](https://discord.gg/etJNHCQ) for help and support.
4258

43-
## Customizability
59+
### Locally
60+
Installation locally for development reasons or otherwise is as follows, you will need `python 3.7` and `pipenv`.
4461

45-
Modmail has a range of configuration variables that you can dynamically alter with the `?config` command. You can use them to change the different aspects of the bot, for example, the embed color, responses, reactions, status, etc. Snippets and custom command aliases are also supported. The level of customization is ever growing thanks to our exceptional contributors.
62+
Clone the repo
63+
```console
64+
$ git clone https://github.com/kyb3r/modmail
65+
$ cd modmail
66+
```
4667

47-
## Thread Logs
68+
Install dependancies
69+
```console
70+
$ pipenv install
71+
```
4872

49-
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. You have the ability to query and search through logs using the bot via commands. An example of a logged conversation: https://logs.modmail.tk/example.
73+
Rename the `config.json.example` to `config.json` and fill out the fields.
74+
And finally, run the bot.
75+
```console
76+
$ pipenv run python3 bot.py
77+
```
5078

51-
# Contributing
79+
## Contributing
5280

5381
This project is licenced under MIT. If you have any new ideas, create an issue or a pull request. Contributions to Modmail are always welcome, whether it be improvements to the documentation or new functionality, please feel free make the change.
5482

55-
If you use Modmail and love it, consider supporting me on **[Patreon](https://www.patreon.com/kyber)** :heart:
83+
If you use Modmail and love it, consider becoming a patron on **[Patreon](https://www.patreon.com/kyber)** :smile:

cogs/modmail.py

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ async def snippets(self, ctx):
106106

107107
@snippets.command(name='add')
108108
@checks.has_permissions(PermissionLevel.SUPPORTER)
109-
async def add_(self, ctx, name: str.lower, *, value):
109+
async def snippets_add(self, ctx, name: str.lower, *, value):
110110
"""Add a snippet to the bot config."""
111111
if 'snippets' not in self.bot.config.cache:
112112
self.bot.config['snippets'] = {}
@@ -124,7 +124,7 @@ async def add_(self, ctx, name: str.lower, *, value):
124124

125125
@snippets.command(name='remove', aliases=['del', 'delete', 'rm'])
126126
@checks.has_permissions(PermissionLevel.SUPPORTER)
127-
async def remove_(self, ctx, *, name: str.lower):
127+
async def snippets_remove(self, ctx, *, name: str.lower):
128128
"""Removes a snippet from bot config."""
129129

130130
if self.bot.config.snippets.get(name):
@@ -386,7 +386,6 @@ def format_log_embeds(self, logs, avatar_url):
386386
if prefix == 'NONE':
387387
prefix = ''
388388

389-
390389
log_url = self.bot.config.log_url.strip('/') + f'{prefix}/{key}'
391390

392391
username = entry['recipient']['name'] + '#'
@@ -450,9 +449,9 @@ async def logs(self, ctx, *, member: User = None):
450449
session = PaginatorSession(ctx, *embeds)
451450
await session.run()
452451

453-
@logs.command(name='closed-by')
452+
@logs.command(name='closed-by', aliases=['closeby'])
454453
@checks.has_permissions(PermissionLevel.SUPPORTER)
455-
async def closed_by(self, ctx, *, user: User = None):
454+
async def logs_closed_by(self, ctx, *, user: User = None):
456455
"""Returns all logs closed by a user."""
457456
user = user or ctx.author
458457

@@ -481,9 +480,9 @@ async def closed_by(self, ctx, *, user: User = None):
481480
session = PaginatorSession(ctx, *embeds)
482481
await session.run()
483482

484-
@logs.command()
483+
@logs.command(name='search', aliases=['find'])
485484
@checks.has_permissions(PermissionLevel.SUPPORTER)
486-
async def search(self, ctx, limit: Optional[int] = None, *, query):
485+
async def logs_search(self, ctx, limit: Optional[int] = None, *, query):
487486
"""Searches all logs for a message that contains your query."""
488487

489488
await ctx.trigger_typing()
@@ -518,7 +517,7 @@ async def search(self, ctx, limit: Optional[int] = None, *, query):
518517
@commands.command()
519518
@checks.has_permissions(PermissionLevel.SUPPORTER)
520519
@checks.thread_only()
521-
async def reply(self, ctx, *, msg=''):
520+
async def reply(self, ctx, *, msg: str):
522521
"""Reply to users using this command.
523522
524523
Supports attachments and images as well as
@@ -531,7 +530,7 @@ async def reply(self, ctx, *, msg=''):
531530
@commands.command()
532531
@checks.has_permissions(PermissionLevel.SUPPORTER)
533532
@checks.thread_only()
534-
async def anonreply(self, ctx, *, msg=''):
533+
async def anonreply(self, ctx, *, msg: str):
535534
"""Reply to a thread anonymously.
536535
537536
You can edit the anonymous user's name,
@@ -547,7 +546,7 @@ async def anonreply(self, ctx, *, msg=''):
547546
@commands.command()
548547
@checks.has_permissions(PermissionLevel.SUPPORTER)
549548
@checks.thread_only()
550-
async def note(self, ctx, *, msg=''):
549+
async def note(self, ctx, *, msg: str):
551550
"""Take a note about the current thread, useful for noting context."""
552551
ctx.message.content = msg
553552
async with ctx.typing():
@@ -579,7 +578,7 @@ async def find_linked_message(self, ctx, message_id):
579578
@checks.has_permissions(PermissionLevel.SUPPORTER)
580579
@checks.thread_only()
581580
async def edit(self, ctx, message_id: Optional[int] = None,
582-
*, new_message):
581+
*, new_message: str):
583582
"""Edit a message that was sent using the reply command.
584583
585584
If no `message_id` is provided, the
@@ -758,7 +757,6 @@ async def unblock(self, ctx, *, user: User = None):
758757
use only.
759758
"""
760759

761-
762760
if user is None:
763761
thread = ctx.thread
764762
if thread:
@@ -803,18 +801,19 @@ async def unblock(self, ctx, *, user: User = None):
803801
@commands.command()
804802
@checks.has_permissions(PermissionLevel.SUPPORTER)
805803
@checks.thread_only()
806-
async def delete(self, ctx, message_id = None):
804+
async def delete(self, ctx, message_id: Optional[int] = None):
807805
"""Delete a message that was sent using the reply command.
808806
809807
Deletes the previous message, unless a message ID is provided, which in that case,
810808
deletes the message with that message ID.
811809
"""
812810
thread = ctx.thread
813811

814-
try:
815-
message_id = int(message_id)
816-
except ValueError:
817-
raise commands.BadArgument('An integer message ID needs to be specified.')
812+
if message_id is not None:
813+
try:
814+
message_id = int(message_id)
815+
except ValueError:
816+
raise commands.BadArgument('An integer message ID needs to be specified.')
818817

819818
linked_message_id = await self.find_linked_message(ctx, message_id)
820819

cogs/plugins.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,9 @@ async def plugin(self, ctx):
124124
cmd = self.bot.get_command('help')
125125
await ctx.invoke(cmd, command='plugin')
126126

127-
@plugin.command()
127+
@plugin.command(name='add')
128128
@checks.has_permissions(PermissionLevel.OWNER)
129-
async def add(self, ctx, *, plugin_name):
129+
async def plugin_add(self, ctx, *, plugin_name):
130130
"""Adds a plugin"""
131131
if plugin_name in self.bot.config.plugins:
132132
return await ctx.send('Plugin already installed')
@@ -164,9 +164,9 @@ async def add(self, ctx, *, plugin_name):
164164
await message.edit(content='Invalid plugin name format. '
165165
'Use username/repo/plugin.')
166166

167-
@plugin.command()
167+
@plugin.command(name='remove', aliases=['del', 'delete', 'rm'])
168168
@checks.has_permissions(PermissionLevel.OWNER)
169-
async def remove(self, ctx, *, plugin_name):
169+
async def plugin_remove(self, ctx, *, plugin_name):
170170
"""Removes a certain plugin"""
171171
if plugin_name in self.bot.config.plugins:
172172
username, repo, name = self.parse_plugin(plugin_name)
@@ -199,9 +199,9 @@ def onerror(func, path, exc_info):
199199
else:
200200
await ctx.send('Plugin not installed.')
201201

202-
@plugin.command()
202+
@plugin.command(name='update')
203203
@checks.has_permissions(PermissionLevel.OWNER)
204-
async def update(self, ctx, *, plugin_name):
204+
async def plugin_update(self, ctx, *, plugin_name):
205205
"""Updates a certain plugin"""
206206
if plugin_name not in self.bot.config.plugins:
207207
return await ctx.send('Plugin not installed')
@@ -232,9 +232,9 @@ async def update(self, ctx, *, plugin_name):
232232
except DownloadError as exc:
233233
await ctx.send(f'Unable to start plugin: `{exc}`')
234234

235-
@plugin.command(name='list')
235+
@plugin.command(name='list', aliases=['show', 'view'])
236236
@checks.has_permissions(PermissionLevel.OWNER)
237-
async def list_(self, ctx):
237+
async def plugin_list(self, ctx):
238238
"""Shows a list of currently enabled plugins"""
239239
if self.bot.config.plugins:
240240
msg = '```\n' + '\n'.join(self.bot.config.plugins) + '\n```'

0 commit comments

Comments
 (0)