Skip to content
This repository has been archived by the owner on Jan 30, 2023. It is now read-only.

Commit

Permalink
Commands are now registered with decorators. Closes #4.
Browse files Browse the repository at this point in the history
  • Loading branch information
Retzudo committed Aug 2, 2017
1 parent 1d23955 commit c138dfe
Show file tree
Hide file tree
Showing 6 changed files with 65 additions and 39 deletions.
7 changes: 6 additions & 1 deletion servoskull/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
from datetime import datetime

__version__ = 'v1.7.1'
__version__ = 'v1.8.0'

start_time = datetime.now()


class ServoSkullError(Exception):
pass

from servoskull.commands.meta import *
from servoskull.commands.regular import *
from servoskull.commands.sound import *
from servoskull.commands.passive import *
13 changes: 7 additions & 6 deletions servoskull/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def get_command_by_mention(message_string):

def get_closest_command(command):
"""Given a string, return the command that's most similar to it."""
available_commands = registry.commands.keys()
available_commands = {**registry.get_regular_commands(), **registry.get_sound_commands()}

closest_commands = get_close_matches(command.lower(), available_commands, 1)
if len(closest_commands) >= 1:
Expand Down Expand Up @@ -83,7 +83,8 @@ async def on_message(message):


async def execute_command(command, arguments, message):
if command not in registry.commands:
commands = {**registry.get_regular_commands(), **registry.get_sound_commands()}
if command not in commands:
logger.debug('User {} issued non-existing command "{}"'.format(message.author, command))
response = 'No such command "{}".'.format(command, get_closest_command(command))
closest_command = get_closest_command(command)
Expand All @@ -93,12 +94,12 @@ async def execute_command(command, arguments, message):
if AUTOGIF:
# If AUTOGIF is enable with an env var, also respond with a GIF that matches
# the command + arguments
gif = await regular.CommandGif(arguments=[command] + arguments).execute()
gif = await registry.commands['gif']['class'](arguments=[command] + arguments).execute()
if 'no gif found' not in gif.lower():
response += "\nAnyway, here's a GIF that matches your request:\n{}".format(gif)
logger.info(response)
else:
class_ = registry.commands[command]
class_ = registry.commands[command]['class']
logger.debug('Executing command "{}"'.format(command))
command = class_(arguments=arguments, message=message, client=client)
response = await command.execute()
Expand All @@ -114,8 +115,8 @@ async def execute_command(command, arguments, message):


async def execute_passive_commands(message):
for command_class in registry.passive_commands.commands.values():
command = command_class(message=message)
for command_class in registry.get_passive_commands().values():
command = command_class['class'](message=message)
response = None

if command.is_triggered():
Expand Down
28 changes: 17 additions & 11 deletions servoskull/commands/meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,34 @@ class CommandHelp(Command):

async def execute(self) -> str:
"""Respond with a help message containing all available commands."""
regular_commands = registry.get_regular_commands()
sound_commands = registry.get_sound_commands()
passive_commands = registry.get_passive_commands()

response = 'Available commands:'
for command, class_ in registry.commands.items():
for command, dct in regular_commands.items():
class_ = dct.get('class')
arguments = ''
for argument in class_.required_arguments:
arguments += '**<{}>** '.format(argument)

response += '\n **{}{}** {}- {}'.format(CMD_PREFIX, command, arguments, class_.help_text)

# TODO
# response += '\n\nAvailable sound commands:'
# for command, class_ in sound_commands.items():
# arguments = ''
# for argument in class_.required_arguments:
# arguments += '**<{}>** '.format(argument)
#
# response += '\n **{}{}** {}- {}'.format(CMD_PREFIX, command, arguments, class_.help_text)
response += '\n\nAvailable sound commands:'
for command, dct in sound_commands.items():
class_ = dct.get('class')
arguments = ''
for argument in class_.required_arguments:
arguments += '**<{}>** '.format(argument)

response += '\n **{}{}** {}- {}'.format(CMD_PREFIX, command, arguments, class_.help_text)

response += ('\n\nAvailable passive commands '
'(these trigger automatically if a message fulfills certain conditions):')
for text, class_ in registry.passive_commands.items():
for text, dct in passive_commands.items():
class_ = dct.get('class')
response += '\n **{}** - {}'.format(text, class_.help_text)

response += '\n\nEither prepend your command with `{}` or mention the bot using `@`.'.format(CMD_PREFIX)

return response
return response
2 changes: 1 addition & 1 deletion servoskull/commands/passive.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def is_triggered(self) -> bool:
raise NotImplementedError()


@registry.register('Reddit comment')
@registry.register('Reddit comment', passive=True)
class RedditCommentCommand(PassiveCommand):
"""If a user posts a link to a Reddit comment, respond with the comment
text and some info about the Reddit post."""
Expand Down
26 changes: 18 additions & 8 deletions servoskull/commands/registry.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,20 @@
from functools import wraps
from servoskull.commands.passive import PassiveCommand

commands = {}
passive_commands = {}


def register(trigger):
def register(trigger, passive=False, sound=False):
"""A decorator that registers commands
trigger: the string prepended by the command prefix users have
to enter to trigger the command.
passive: Whether the command is a passive command."""
def decorator(cls):
is_passive = issubclass(cls, PassiveCommand)
if is_passive:
passive_commands[trigger] = cls
else:
commands[trigger] = cls
commands[trigger] = {
'passive': passive,
'sound': sound,
'class': cls,
}

@wraps(cls)
def wrapper(*args, **kwargs):
Expand All @@ -25,3 +23,15 @@ def wrapper(*args, **kwargs):
return wrapper

return decorator


def get_regular_commands():
return {key: value for key, value in commands.items() if not value.get('passive') and not value.get('sound')}


def get_sound_commands():
return {key: value for key, value in commands.items() if not value.get('passive') and value.get('sound')}


def get_passive_commands():
return {key: value for key, value in commands.items() if value.get('passive') and not value.get('sound')}
28 changes: 16 additions & 12 deletions servoskull/commands/sound.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Commands that are actively triggered by a user and require the bot to be connected to a voice channel."""
import discord
import youtube_dl

from servoskull.commands import registry
from servoskull.commands.regular import Command
Expand All @@ -26,7 +27,7 @@ def _get_voice_client(self):
return self.message.server.voice_client


@registry.register('summon')
@registry.register('summon', sound=True)
class CommandSummon(SoundCommand):
help_text = "Summons the bot to the user's voice channel or to the voice channel of the user you mention with `@`."
required_arguments = ['user']
Expand Down Expand Up @@ -73,7 +74,7 @@ async def execute(self) -> str:
return 'Could not connect to your voice channel: {}'.format(e)


@registry.register('disconnect')
@registry.register('disconnect', sound=True)
class CommandDisconnect(SoundCommand):
help_text = 'Disconnects the bot from the current voice channel'

Expand All @@ -83,15 +84,15 @@ async def execute_sound(self):
await voice_client.disconnect()


@registry.register('sound')
@registry.register('sound', sound=True)
class CommandSound(SoundCommand):
help_text = 'Play a sound (`sounds` for a list)'
required_arguments = ['sound']

# List of available sounds
sounds = {
'horn': {
'url': 'https://www.youtube.com/watch?v=1ytCEuuW2_A',
'url': 'https://www.youtube.com/watch?v=9Jz1TjCphXE',
'volume': 0.1,
},
}
Expand All @@ -106,17 +107,20 @@ async def execute_sound(self) -> str:
sound_name = self.arguments[0]
sound = CommandSound.sounds.get(sound_name)
if not sound:
return 'No such sound "{}". Use `sounds` for a list of sounds'.format(sound)
return 'No such sound "{}". Use `sounds` for a list of sounds'.format(sound_name)

player = await voice_client.create_ytdl_player(
sound.get('url'),
use_avconv=USE_AVCONV,
options='-af "volume={}"'.format(sound.get('volume', 1.0))
)
player.start()
try:
player = await voice_client.create_ytdl_player(
sound.get('url'),
use_avconv=USE_AVCONV,
options='-af "volume={}"'.format(sound.get('volume', 1.0))
)
player.start()
except youtube_dl.utils.DownloadError as e:
return str(e)


@registry.register('sounds')
@registry.register('sounds', sound=True)
class CommandSounds(Command):
help_text = 'Respond with a list of available sounds for voice channels'

Expand Down

0 comments on commit c138dfe

Please sign in to comment.