コード例 #1
0
ファイル: params_lib.py プロジェクト: jellzilla/hypebot
def _ParseChannel(channel):
    """Parse a single messy definition of a channel into a Channel proto.

  Parsing:
    * If a Channel, return as is.
    * If a string, convert into a public channel with id equal to the string.
    * If a dictionary or HypeParams object, convert into a public channel with
      the matching fields set.

  Args:
    channel: The messy object to convert into a channel.

  Returns:
    Channel or None if it failed to parse.
  """
    if isinstance(channel, channel_pb2.Channel):
        return channel
    if isinstance(channel, HypeParams):
        channel = channel.AsDict()
    if isinstance(channel, dict):
        try:
            return channel_pb2.Channel(visibility=channel_pb2.Channel.PUBLIC,
                                       **channel)
        except KeyError:
            logging.error('Failed to parse %s as a Channel', channel)
            return None
    if isinstance(channel, six.string_types):
        return channel_pb2.Channel(visibility=channel_pb2.Channel.PUBLIC,
                                   id=channel,
                                   name=channel)
    return None
コード例 #2
0
 def _ExtractOverrides(self, raw_message):
     """Returns nothing, or the overrides parsed for a channel, user, and msg."""
     message_regex = re.compile(r'^\[(#?\w+)(?:\|(#?\w+))?\]\s*(.+)')
     match = message_regex.match(raw_message)
     if not match:
         return
     username = self._params.default_username
     channel_name = self._params.default_channel.name
     visibility = channel_pb2.Channel.PUBLIC
     for i in range(1, 3):
         user_or_channel = match.group(i)
         if not user_or_channel:
             break
         if user_or_channel.startswith('#'):
             channel_name = user_or_channel
             if channel_name.startswith('#sys'):
                 visibility = channel_pb2.Channel.SYSTEM
         else:
             username = user_or_channel
     if username.lower() == channel_name.strip('#').lower():
         visibility = channel_pb2.Channel.PRIVATE
     message = match.group(3)
     return (channel_pb2.Channel(id=channel_name,
                                 visibility=visibility,
                                 name=channel_name),
             user_pb2.User(user_id=username,
                           display_name=username,
                           bot=username.endswith('bot')), message)
コード例 #3
0
    def PublishMessage(self,
                       topic: Text,
                       msg: hype_types.CommandResponse,
                       notice: bool = False) -> None:
        """Sends a message to the channels subscribed to a topic.

    Args:
      topic: Name of the topic on which to publish the message.
      msg: The message to send.
      notice: If true, use interface.Notice instead of interface.SendMessage.
    """
        if not msg:
            return
        if not topic:
            logging.warning('Attempted to publish message with no topic: %s',
                            msg)
            return
        channels = self.params.subscriptions.get(topic, [])
        if not channels:
            logging.info('No subscriptions for topic %s, dropping: %s', topic,
                         msg)
            return
        message = util_lib.MakeMessage(msg)
        for channel in channels:
            channel = channel_pb2.Channel(
                visibility=channel_pb2.Channel.PUBLIC, **channel)
            if notice:
                self.interface.Notice(channel, message)
            else:
                self.interface.SendMessage(channel, message)
コード例 #4
0
ファイル: command_lib.py プロジェクト: jellzilla/hypebot
  def _Ratelimit(self, channel: channel_pb2.Channel, user: user_pb2.User, *args,
                 **kwargs):
    """Ratelimits calls/responses from Handling the message.

    In general this, prevents the same user, channel, or global triggering a
    command in quick succession. This works by timing calls and verifying that
    future calls have exceeded the interval before invocation.

    Some commands like to handle every message, but only respond to a few. E.g.,
    MissingPing needs every message to record the most recent user, but only
    sends a response when someone types '?'. In this case, we always execute the
    command and only ratelimit the response. The restriction being, that it is
    safe to call on every invocation. E.g., do not transfer hypecoins.

    Args:
      channel: Passed through to _Handle.
      user: Passed through to _Handle.
      *args: Passed through to _Handle.
      **kwargs: Passed through to _Handle.

    Returns:
      Optional message(s) to reply to the channel.
    """
    if (not self._params.ratelimit.enabled or channel.visibility in [
        channel_pb2.Channel.PRIVATE, channel_pb2.Channel.SYSTEM
    ]):
      return self._Handle(channel, user, *args, **kwargs)

    scoped_channel = channel_pb2.Channel()
    scoped_channel.CopyFrom(channel)
    scoped_user_id = user.user_id
    if self._params.ratelimit.scope == 'GLOBAL':
      scoped_channel.id = self._DEFAULT_SCOPE
      scoped_user_id = self._DEFAULT_SCOPE
    elif self._params.ratelimit.scope == 'CHANNEL':
      scoped_user_id = self._DEFAULT_SCOPE

    with self._ratelimit_lock:
      t = time.time()
      delta_t = t - self._last_called[scoped_channel.id][scoped_user_id]
      response = None
      if self._params.ratelimit.return_only:
        response = self._Handle(channel, user, *args, **kwargs)
        if not response:
          return

      if delta_t < self._params.ratelimit.interval:
        logging.info('Call to %s._Handle ratelimited in %s for %s: %s < %s',
                     self.__class__.__name__, scoped_channel.id, scoped_user_id,
                     delta_t, self._params.ratelimit.interval)
        self._Reply(user, random.choice(messages.RATELIMIT_MEMES))
        return

      self._last_called[scoped_channel.id][scoped_user_id] = t
      return response or self._Handle(channel, user, *args, **kwargs)
コード例 #5
0
 async def on_message(message):
     # Discord doesn't protect us from responding to ourself.
     if message.author == self._client.user:
         return
     logging.info('Message from: %s - %s#%s - %s', message.author.name,
                  message.author.display_name,
                  message.author.discriminator, message.author.id)
     user = user_pb2.User(user_id=str(message.author.id),
                          display_name=message.author.display_name)
     # Discord has DMChannel for single user interaction and GroupChannel for
     # group DMs outside of traditional TextChannels within a guild. We only
     # consider the DMChannel (single user) as private to prevent spam in Group
     # conversations.
     if isinstance(message.channel, discord.DMChannel):
         channel = channel_pb2.Channel(
             id=str(message.channel.id),
             visibility=channel_pb2.Channel.PRIVATE,
             name=message.channel.recipient.name)
     else:
         channel = channel_pb2.Channel(
             id=str(message.channel.id),
             visibility=channel_pb2.Channel.PUBLIC,
             name=message.channel.name)
     self._on_message_fn(channel, user, self._CleanContent(message))
コード例 #6
0
ファイル: basebot.py プロジェクト: jellzilla/hypebot
    def _ProcessNestedCalls(self, channel, user, msg):
        """Evaluate nested commands within $(...)."""
        m = self.NESTED_PATTERN.search(msg)
        while m:
            backup_interface = self._core.interface
            self._core.interface = interface_factory.Create(
                'CaptureInterface', {})

            # Pretend it's Private to avoid ratelimit.
            nested_channel = channel_pb2.Channel(
                id=channel.id,
                visibility=channel_pb2.Channel.PRIVATE,
                name=channel.name)
            self.HandleMessage(nested_channel, user, m.group(1))
            response = self._core.interface.MessageLog()

            msg = msg[:m.start()] + response + msg[m.end():]
            self._core.interface = backup_interface
            m = self.NESTED_PATTERN.search(msg)
        return msg
コード例 #7
0
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals

import unittest

from hypebot import basebot
from hypebot import hypecore
from hypebot.core import params_lib
from hypebot.interfaces import interface_factory
from hypebot.protos import channel_pb2
from hypebot.protos import user_pb2

TEST_CHANNEL = channel_pb2.Channel(id='#test',
                                   name='Test',
                                   visibility=channel_pb2.Channel.PUBLIC)
TEST_USER = user_pb2.User(user_id='_test', display_name='user')


def ForCommand(command_cls):
    """Decorator to enable setting the command for each test class."""
    def _Internal(test_cls):
        test_cls._command_cls = command_cls
        return test_cls

    return _Internal


class BaseCommandTestCase(unittest.TestCase):