18 changed files with 423 additions and 27 deletions
@ -0,0 +1 @@ |
|||||
|
from disco.bot.bot import Bot |
@ -0,0 +1,116 @@ |
|||||
|
import re |
||||
|
|
||||
|
|
||||
|
class BotConfig(object): |
||||
|
# Whether the bot must be mentioned to respond to a command |
||||
|
command_require_mention = True |
||||
|
|
||||
|
# Rules about what mentions trigger the bot |
||||
|
command_mention_rules = { |
||||
|
# 'here': False, |
||||
|
'everyone': False, |
||||
|
'role': True, |
||||
|
'user': True, |
||||
|
} |
||||
|
|
||||
|
# The prefix required for EVERY command |
||||
|
command_prefix = '' |
||||
|
|
||||
|
# Whether an edited message can trigger a command |
||||
|
command_allow_edit = True |
||||
|
|
||||
|
|
||||
|
class Bot(object): |
||||
|
def __init__(self, client, config=None): |
||||
|
self.client = client |
||||
|
self.config = config or BotConfig() |
||||
|
|
||||
|
self.plugins = {} |
||||
|
|
||||
|
self.client.events.on('MessageCreate', self.on_message_create) |
||||
|
self.client.events.on('MessageUpdate', self.on_message_update) |
||||
|
|
||||
|
# Stores the last message for every single channel |
||||
|
self.last_message_cache = {} |
||||
|
|
||||
|
# Stores a giant regex matcher for all commands |
||||
|
self.command_matches_re = None |
||||
|
|
||||
|
@property |
||||
|
def commands(self): |
||||
|
for plugin in self.plugins.values(): |
||||
|
for command in plugin.commands: |
||||
|
yield command |
||||
|
|
||||
|
def compute_command_matches_re(self): |
||||
|
re_str = '|'.join(command.regex for command in self.commands) |
||||
|
print re_str |
||||
|
if re_str: |
||||
|
self.command_matches_re = re.compile(re_str) |
||||
|
else: |
||||
|
self.command_matches_re = None |
||||
|
|
||||
|
def handle_message(self, msg): |
||||
|
content = msg.content |
||||
|
|
||||
|
if self.config.command_require_mention: |
||||
|
match = any(( |
||||
|
self.config.command_mention_rules['user'] and msg.is_mentioned(self.client.state.me), |
||||
|
self.config.command_mention_rules['everyone'] and msg.mention_everyone, |
||||
|
self.config.command_mention_rules['role'] and any(map(msg.is_mentioned, |
||||
|
msg.guild.get_member(self.client.state.me).roles |
||||
|
)))) |
||||
|
|
||||
|
if not match: |
||||
|
return False |
||||
|
|
||||
|
content = msg.without_mentions.strip() |
||||
|
|
||||
|
if self.config.command_prefix and not content.startswith(self.config.command_prefix): |
||||
|
return False |
||||
|
|
||||
|
if not self.command_matches_re or not self.command_matches_re.match(content): |
||||
|
return False |
||||
|
|
||||
|
for command in self.commands: |
||||
|
if command.compiled_regex.match(content): |
||||
|
command.execute(msg) |
||||
|
|
||||
|
return False |
||||
|
|
||||
|
def on_message_create(self, event): |
||||
|
if self.config.command_allow_edit: |
||||
|
self.last_message_cache[event.message.channel_id] = (event.message, False) |
||||
|
|
||||
|
self.handle_message(event.message) |
||||
|
|
||||
|
def on_message_update(self, event): |
||||
|
if self.config.command_allow_edit: |
||||
|
msg = self.last_message_cache.get(event.message.channel_id) |
||||
|
if msg and event.message.id == msg[0].id: |
||||
|
triggered = msg[1] |
||||
|
|
||||
|
if not triggered: |
||||
|
triggered = self.handle_message(event.message) |
||||
|
|
||||
|
self.last_message_cache[event.message.channel_id] = (event.message, triggered) |
||||
|
|
||||
|
def add_plugin(self, cls): |
||||
|
if cls.__name__ in self.plugins: |
||||
|
raise Exception('Cannot add already added plugin: {}'.format(cls.__name__)) |
||||
|
|
||||
|
self.plugins[cls.__name__] = cls(self) |
||||
|
self.plugins[cls.__name__].load() |
||||
|
self.compute_command_matches_re() |
||||
|
|
||||
|
def rmv_plugin(self, cls): |
||||
|
if cls.__name__ not in self.plugins: |
||||
|
raise Exception('Cannot remove non-existant plugin: {}'.format(cls.__name__)) |
||||
|
|
||||
|
self.plugins[cls.__name__].unload() |
||||
|
self.plugins[cls.__name__].destroy() |
||||
|
del self.plugins[cls.__name__] |
||||
|
self.compute_command_matches_re() |
||||
|
|
||||
|
def run_forever(self): |
||||
|
self.client.run_forever() |
@ -0,0 +1,29 @@ |
|||||
|
import re |
||||
|
|
||||
|
from disco.util.cache import cached_property |
||||
|
|
||||
|
ARGS_REGEX = '( (.*)$|$)' |
||||
|
|
||||
|
|
||||
|
class Command(object): |
||||
|
def __init__(self, func, trigger, aliases=None, group=None, is_regex=False): |
||||
|
self.func = func |
||||
|
self.triggers = [trigger] + (aliases or []) |
||||
|
|
||||
|
self.group = group |
||||
|
self.is_regex = is_regex |
||||
|
|
||||
|
def execute(self, msg): |
||||
|
self.func(msg) |
||||
|
|
||||
|
@cached_property |
||||
|
def compiled_regex(self): |
||||
|
return re.compile(self.regex) |
||||
|
|
||||
|
@property |
||||
|
def regex(self): |
||||
|
if self.is_regex: |
||||
|
return '|'.join(self.triggers) |
||||
|
else: |
||||
|
group = self.group + ' ' if self.group else '' |
||||
|
return '|'.join(['^' + group + trigger for trigger in self.triggers]) + ARGS_REGEX |
@ -0,0 +1,65 @@ |
|||||
|
import inspect |
||||
|
|
||||
|
from disco.bot.command import Command |
||||
|
|
||||
|
|
||||
|
class PluginDeco(object): |
||||
|
@staticmethod |
||||
|
def listen(event_name): |
||||
|
def deco(f): |
||||
|
if not hasattr(f, 'meta'): |
||||
|
f.meta = [] |
||||
|
|
||||
|
f.meta.append({ |
||||
|
'type': 'listener', |
||||
|
'event_name': event_name, |
||||
|
}) |
||||
|
|
||||
|
return f |
||||
|
return deco |
||||
|
|
||||
|
@staticmethod |
||||
|
def command(*args, **kwargs): |
||||
|
def deco(f): |
||||
|
if not hasattr(f, 'meta'): |
||||
|
f.meta = [] |
||||
|
|
||||
|
f.meta.append({ |
||||
|
'type': 'command', |
||||
|
'args': args, |
||||
|
'kwargs': kwargs, |
||||
|
}) |
||||
|
|
||||
|
return f |
||||
|
return deco |
||||
|
|
||||
|
|
||||
|
class Plugin(PluginDeco): |
||||
|
def __init__(self, bot): |
||||
|
self.bot = bot |
||||
|
|
||||
|
self.listeners = [] |
||||
|
self.commands = [] |
||||
|
|
||||
|
for name, member in inspect.getmembers(self, predicate=inspect.ismethod): |
||||
|
if hasattr(member, 'meta'): |
||||
|
for meta in member.meta: |
||||
|
if meta['type'] == 'listener': |
||||
|
self.register_listener(member, meta['event_name']) |
||||
|
elif meta['type'] == 'command': |
||||
|
self.register_command(member, *meta['args'], **meta['kwargs']) |
||||
|
|
||||
|
def register_listener(self, func, name): |
||||
|
self.listeners.append(self.bot.client.events.on(name, func)) |
||||
|
|
||||
|
def register_command(self, func, *args, **kwargs): |
||||
|
self.commands.append(Command(func, *args, **kwargs)) |
||||
|
|
||||
|
def destroy(self): |
||||
|
map(lambda k: k.remove(), self._events) |
||||
|
|
||||
|
def load(self): |
||||
|
pass |
||||
|
|
||||
|
def unload(self): |
||||
|
pass |
@ -0,0 +1,52 @@ |
|||||
|
|
||||
|
|
||||
|
class State(object): |
||||
|
def __init__(self, client): |
||||
|
self.client = client |
||||
|
|
||||
|
self.me = None |
||||
|
|
||||
|
self.channels = {} |
||||
|
self.guilds = {} |
||||
|
|
||||
|
self.client.events.on('Ready', self.on_ready) |
||||
|
|
||||
|
# Guilds |
||||
|
self.client.events.on('GuildCreate', self.on_guild_create) |
||||
|
self.client.events.on('GuildUpdate', self.on_guild_update) |
||||
|
self.client.events.on('GuildDelete', self.on_guild_delete) |
||||
|
|
||||
|
# Channels |
||||
|
self.client.events.on('ChannelCreate', self.on_channel_create) |
||||
|
self.client.events.on('ChannelUpdate', self.on_channel_update) |
||||
|
self.client.events.on('ChannelDelete', self.on_channel_delete) |
||||
|
|
||||
|
def on_ready(self, event): |
||||
|
self.me = event.user |
||||
|
|
||||
|
def on_guild_create(self, event): |
||||
|
self.guilds[event.guild.id] = event.guild |
||||
|
|
||||
|
for channel in event.guild.channels: |
||||
|
self.channels[channel.id] = channel |
||||
|
|
||||
|
def on_guild_update(self, event): |
||||
|
# TODO |
||||
|
pass |
||||
|
|
||||
|
def on_guild_delete(self, event): |
||||
|
if event.guild_id in self.guilds: |
||||
|
del self.guilds[event.guild_id] |
||||
|
|
||||
|
# CHANNELS? |
||||
|
|
||||
|
def on_channel_create(self, event): |
||||
|
self.channels[event.channel.id] = event.channel |
||||
|
|
||||
|
def on_channel_update(self, event): |
||||
|
# TODO |
||||
|
pass |
||||
|
|
||||
|
def on_channel_delete(self, event): |
||||
|
if event.channel.id in self.channels: |
||||
|
del self.channels[event.channel.id] |
@ -0,0 +1,5 @@ |
|||||
|
import skema |
||||
|
|
||||
|
|
||||
|
class BaseType(skema.Model): |
||||
|
pass |
@ -1,5 +1,7 @@ |
|||||
import skema |
import skema |
||||
|
|
||||
|
from disco.types.base import BaseType |
||||
|
|
||||
class VoiceState(skema.Model): |
|
||||
|
class VoiceState(BaseType): |
||||
id = skema.SnowflakeType() |
id = skema.SnowflakeType() |
||||
|
@ -0,0 +1,18 @@ |
|||||
|
|
||||
|
|
||||
|
def recursive_find_matching(base, match_clause): |
||||
|
result = [] |
||||
|
|
||||
|
if hasattr(base, '__dict__'): |
||||
|
values = base.__dict__.values() |
||||
|
else: |
||||
|
values = list(base) |
||||
|
|
||||
|
for v in values: |
||||
|
if match_clause(v): |
||||
|
result.append(v) |
||||
|
|
||||
|
if hasattr(v, '__dict__') or hasattr(v, '__iter__'): |
||||
|
result += recursive_find_matching(v, match_clause) |
||||
|
|
||||
|
return result |
@ -0,0 +1,7 @@ |
|||||
|
|
||||
|
|
||||
|
def cached_property(f): |
||||
|
def deco(self, *args, **kwargs): |
||||
|
self.__dict__[f.__name__] = f(self, *args, **kwargs) |
||||
|
return self.__dict__[f.__name__] |
||||
|
return property(deco) |
@ -0,0 +1,18 @@ |
|||||
|
from disco.cli import disco_main |
||||
|
from disco.bot import Bot |
||||
|
from disco.bot.plugin import Plugin |
||||
|
|
||||
|
|
||||
|
class BasicPlugin(Plugin): |
||||
|
@Plugin.listen('MessageCreate') |
||||
|
def on_message_create(self, event): |
||||
|
print 'Message Created: {}'.format(event.message.content) |
||||
|
|
||||
|
@Plugin.command('test') |
||||
|
def on_test_command(self, event): |
||||
|
print 'wtf' |
||||
|
|
||||
|
if __name__ == '__main__': |
||||
|
bot = Bot(disco_main()) |
||||
|
bot.add_plugin(BasicPlugin) |
||||
|
bot.run_forever() |
Loading…
Reference in new issue