Skip to content

Commit

Permalink
Added ?autotrigger to specify keywords to trigger commands, resolve #…
Browse files Browse the repository at this point in the history
…130, resolve #649
  • Loading branch information
fourjr committed Nov 10, 2020
1 parent 69d60ab commit 27afe7d
Show file tree
Hide file tree
Showing 7 changed files with 205 additions and 10 deletions.
7 changes: 4 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@ however, insignificant breaking changes do not guarantee a major version bump, s
- Added a way to block roles. ([GH #2753](https://github.com/kyb3r/modmail/issues/2753))
- Added `cooldown_thread_title`, `cooldown_thread_response` to customise message sent when user is on a creating thread cooldown. ([GH #2865](https://github.com/kyb3r/modmail/issues/2865))
- Added `?selfcontact` to allow users to open a thread. ([GH #2762](https://github.com/kyb3r/modmail/issues/2762))
- Support stickers and reject non-messages (i.e. pin_add)
- Added support for thread titles, `?title` ([GH #2838](https://github.com/kyb3r/modmail/issues/2838))
- Added `data_collection` to specify if bot metadata should be collected by Modmail developers
- Support stickers and reject non-messages. (i.e. pin_add)
- Added support for thread titles, `?title`. ([GH #2838](https://github.com/kyb3r/modmail/issues/2838))
- Added `data_collection` to specify if bot metadata should be collected by Modmail developers.
- Added `?autotrigger` to specify keywords to trigger commands. ([GH #130](https://github.com/kyb3r/modmail/issues/130), [GH #649](https://github.com/kyb3r/modmail/issues/649))

### Fixed

Expand Down
50 changes: 50 additions & 0 deletions bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@


import asyncio
import copy
import logging
import os
import re
Expand Down Expand Up @@ -244,6 +245,10 @@ def snippets(self) -> typing.Dict[str, str]:
def aliases(self) -> typing.Dict[str, str]:
return self.config["aliases"]

@property
def auto_triggers(self) -> typing.Dict[str, str]:
return self.config["auto_triggers"]

@property
def token(self) -> str:
token = self.config["token"]
Expand Down Expand Up @@ -852,6 +857,51 @@ async def get_contexts(self, message, *, cls=commands.Context):
ctx.command = self.all_commands.get(invoker)
return [ctx]

async def trigger_auto_triggers(self, message, channel, *, cls=commands.Context):
message.author = self.modmail_guild.me
message.channel = channel

view = StringView(message.content)
ctx = cls(prefix=self.prefix, view=view, bot=self, message=message)
thread = await self.threads.find(channel=ctx.channel)

invoked_prefix = self.prefix
invoker = view.get_word().lower()

# Check if there is any aliases being called.
alias = self.auto_triggers[
next(filter(lambda x: x in message.content, self.auto_triggers.keys()))
]
if alias is None:
ctx.thread = thread
ctx.invoked_with = invoker
ctx.command = self.all_commands.get(invoker)
ctxs = [ctx]
else:
ctxs = []
aliases = normalize_alias(alias, message.content[len(f"{invoked_prefix}{invoker}") :])
if not aliases:
logger.warning("Alias %s is invalid as called in automove.", invoker)

for alias in aliases:
view = StringView(invoked_prefix + alias)
ctx_ = cls(prefix=self.prefix, view=view, bot=self, message=message)
ctx_.thread = thread
discord.utils.find(view.skip_string, await self.get_prefix())
ctx_.invoked_with = view.get_word().lower()
ctx_.command = self.all_commands.get(ctx_.invoked_with)
ctxs += [ctx_]

for ctx in ctxs:
if ctx.command:
old_checks = copy.copy(ctx.command.checks)
ctx.command.checks = [checks.has_permissions(PermissionLevel.INVALID)]

await self.invoke(ctx)

ctx.command.checks = old_checks
continue

async def get_context(self, message, *, cls=commands.Context):
"""
Returns the invocation context from the message.
Expand Down
5 changes: 4 additions & 1 deletion cogs/modmail.py
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,10 @@ async def move(self, ctx, *, arguments):
await thread.channel.send(f"{mention}, thread has been moved.")

sent_emoji, _ = await self.bot.retrieve_emoji()
await self.bot.add_reaction(ctx.message, sent_emoji)
try:
await self.bot.add_reaction(ctx.message, sent_emoji)
except discord.NotFound:
pass

async def send_scheduled_close_message(self, ctx, after, silent=False):
human_delta = human_timedelta(after.dt)
Expand Down
121 changes: 120 additions & 1 deletion cogs/utility.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,13 @@

from core import checks, utils
from core.changelog import Changelog
from core.models import InvalidConfigError, PermissionLevel, UnseenFormatter, getLogger
from core.models import (
InvalidConfigError,
PermissionLevel,
SimilarCategoryConverter,
UnseenFormatter,
getLogger,
)
from core.paginator import EmbedPaginatorSession, MessagePaginatorSession


Expand Down Expand Up @@ -1698,6 +1704,119 @@ async def oauth_show(self, ctx):

await ctx.send(embed=embed)

@commands.group(invoke_without_command=True)
@checks.has_permissions(PermissionLevel.OWNER)
async def autotrigger(self, ctx):
"""Automatically trigger alias-like commands based on a certain keyword"""
await ctx.send_help(ctx.command)

@autotrigger.command(name="add")
@checks.has_permissions(PermissionLevel.OWNER)
async def autotrigger_add(self, ctx, keyword, *, command):
"""Adds a trigger to automatically trigger an alias-like command"""
if keyword in self.bot.auto_triggers:
embed = discord.Embed(
title="Error",
color=self.bot.error_color,
description=f"Another autotrigger with the same name already exists: `{name}`.",
)
else:
self.bot.auto_triggers[keyword] = command
await self.bot.config.update()

embed = discord.Embed(
title="Success",
color=self.bot.main_color,
description=f"Keyword `{keyword}` has been linked to `{command}`.",
)

await ctx.send(embed=embed)

@autotrigger.command(name="edit")
@checks.has_permissions(PermissionLevel.OWNER)
async def autotrigger_edit(self, ctx, keyword, *, command):
"""Edits a pre-existing trigger to automatically trigger an alias-like command"""
if keyword not in self.bot.auto_triggers:
embed = utils.create_not_found_embed(
keyword, self.bot.auto_triggers.keys(), "Autotrigger"
)
else:
self.bot.auto_triggers[keyword] = command
await self.bot.config.update()

embed = discord.Embed(
title="Success",
color=self.bot.main_color,
description=f"Keyword `{keyword}` has been linked to `{command}`.",
)

await ctx.send(embed=embed)

@autotrigger.command(name="remove")
@checks.has_permissions(PermissionLevel.OWNER)
async def autotrigger_remove(self, ctx, keyword):
"""Removes a trigger to automatically trigger an alias-like command"""
try:
del self.bot.auto_triggers[keyword]
except KeyError:
embed = discord.Embed(
title="Error",
color=self.bot.error_color,
description=f"Keyword `{keyword}` could not be found.",
)
await ctx.send(embed=embed)
else:
await self.bot.config.update()

embed = discord.Embed(
title="Success",
color=self.bot.main_color,
description=f"Keyword `{keyword}` has been removed.",
)
await ctx.send(embed=embed)

@autotrigger.command(name="test")
@checks.has_permissions(PermissionLevel.OWNER)
async def autotrigger_test(self, ctx, *, text):
"""Tests a string against the current autotrigger setup"""
for keyword in list(self.bot.auto_triggers):
if keyword in text:
alias = self.bot.auto_triggers[keyword]
embed = discord.Embed(
title="Keyword Found",
color=self.bot.main_color,
description=f"autotrigger keyword `{keyword}` found. Command executed: `{alias}`",
)
return await ctx.send(embed=embed)

embed = discord.Embed(
title="Keyword Not Found",
color=self.bot.error_color,
description=f"No autotrigger keyword found. Thread will stay in {self.bot.main_category}.",
)
return await ctx.send(embed=embed)

@autotrigger.command(name="list")
@checks.has_permissions(PermissionLevel.OWNER)
async def autotrigger_list(self, ctx):
"""Lists all autotriggers set up"""
embeds = []
for keyword in list(self.bot.auto_triggers):
command = self.bot.auto_triggers[keyword]
embed = discord.Embed(title=keyword, color=self.bot.main_color, description=command,)
embeds.append(embed)

if not embeds:
embeds.append(
discord.Embed(
title="No autotrigger set",
color=self.bot.error_color,
description=f"Use `{self.bot.prefix}autotrigger add` to add new autotriggers.",
)
)

await EmbedPaginatorSession(ctx, *embeds).run()

@commands.command(hidden=True, name="eval")
@checks.has_permissions(PermissionLevel.OWNER)
async def eval_(self, ctx, *, body: str):
Expand Down
1 change: 1 addition & 0 deletions core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ class ConfigManager:
# misc
"plugins": [],
"aliases": {},
"auto_triggers": {},
}

protected_keys = {
Expand Down
5 changes: 4 additions & 1 deletion core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,8 +196,11 @@ async def convert(self, ctx, argument):
try:
return await super().convert(ctx, argument)
except commands.ChannelNotFound:

def check(c):
return isinstance(c, discord.CategoryChannel) and c.name.lower().startswith(argument.lower())
return isinstance(c, discord.CategoryChannel) and c.name.lower().startswith(
argument.lower()
)

if guild:
result = discord.utils.find(check, guild.categories)
Expand Down
26 changes: 22 additions & 4 deletions core/thread.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import asyncio
import copy
import io
import re
import typing
Expand All @@ -10,7 +11,8 @@
import discord
from discord.ext.commands import MissingRequiredArgument, CommandError

from core.models import getLogger
from core import checks
from core.models import PermissionLevel, getLogger
from core.time import human_timedelta
from core.utils import (
is_image_url,
Expand Down Expand Up @@ -101,7 +103,7 @@ def cancelled(self, flag: bool):
for i in self.wait_tasks:
i.cancel()

async def setup(self, *, creator=None, category=None):
async def setup(self, *, creator=None, category=None, initial_message=None):
"""Create the thread channel and other io related initialisation tasks"""
self.bot.dispatch("thread_initiate", self)
recipient = self.recipient
Expand Down Expand Up @@ -197,7 +199,19 @@ async def send_recipient_genesis_message():
close_emoji = await self.bot.convert_emoji(close_emoji)
await self.bot.add_reaction(msg, close_emoji)

await asyncio.gather(send_genesis_message(), send_recipient_genesis_message())
async def activate_auto_triggers():
message = copy.copy(initial_message)
if message:
for keyword in list(self.bot.auto_triggers):
if keyword in message.content:
try:
return await self.bot.trigger_auto_triggers(message, channel)
except StopIteration:
pass

await asyncio.gather(
send_genesis_message(), send_recipient_genesis_message(), activate_auto_triggers(),
)
self.bot.dispatch("thread_ready", self)

def _format_info_embed(self, user, log_url, log_count, color):
Expand Down Expand Up @@ -880,6 +894,8 @@ async def send(
if delete_message and destination == self.channel:
try:
await message.delete()
except discord.NotFound:
pass
except Exception as e:
logger.warning("Cannot delete message: %s.", e)

Expand Down Expand Up @@ -1138,7 +1154,9 @@ async def create(
del self.cache[recipient.id]
return thread

self.bot.loop.create_task(thread.setup(creator=creator, category=category))
self.bot.loop.create_task(
thread.setup(creator=creator, category=category, initial_message=message)
)
return thread

async def find_or_create(self, recipient) -> Thread:
Expand Down

0 comments on commit 27afe7d

Please sign in to comment.