Commits

Marc-Alexandre Chan committed 2ea8f08

Bugfixes; mostly events PromptCommandBase, AddPromptCommand, CheckMessageEvent; utils

Comments (0)

Files changed (3)

                               post_id=str(self.r_post_id),
                               tsubmit=str(self.submit_time),
                               user=self.user,
-                              approver=self.approver.c.uname if self.approver else '',
+                              approver=self.approver.uname if self.approver else '',
                               title=self.title,
                               excerpt=self.text[0:128]\
                                      +('...' if len(self.text) > 128 else ''))

minibot/events.py

 "DailyPromptBot? It's open-source! Here's [the DailyPromptBot "
 "repository](https://bitbucket.org/Laogeodritt/dailypromptbot).)")
 
-# some private global methods
-def __handle_command_parameter_error(exc, msg, scheduler):
-    """ Handle a CommandParameterError. Sends a reply to ``msg`` giving details
-    of the exception object ``exc`̀  and queues the event in the ``scheduler``.
-    """
-    reply_event = SendReplyCommand(msg, re_subject, re_body)
-
 
 class CommandBase(object):
     """ Abstract base class for command events (single-shot events).
 
     def _process_message(self, msg):
         """ Process a message. May throw CommandParseError, CommandNameError,
-        CommandParameterError, BadCommandError. """
+        CommandParameterError, MissingParameterError BadCommandError. """
         if not self._is_valid_message(msg):
             raise BadCommandError('CheckMessageEvent: bad command message', msg)
-
         # raises CommandParseError
         msg_data = self._parse_message(msg)
-        # build event obj. raises CommandNameError/CommandParameterError
-        msg_command = self._make_command(msg, msg_data)
-        # queue the event in the scheduler
-        self.owner.queue_event(msg_command)
+
+        if not self._is_msg_queued(msg, msg_data):
+            # build event obj. raises CommandNameError/CommandParameterError
+            # or MissingParameterError
+            msg_command = self._make_command(msg, msg_data)
+            # queue the event in the scheduler
+            self.owner.queue_event(msg_command)
 
     def _is_valid_message(self, msg):
         """ Check whether message source is appropriate for a command message
 
         return field_data
 
+    def _is_msg_queued(self, msg, data):
+        """ Check whether an event is already queued in the event scheduler
+        for this message. Returns a boolean. """
+        if data['action'] in self.cmd_actions.keys():
+            CommandClass = self.cmd_actions[data.get('action')]
+            for ev in self.owner.get_events(CommandClass):
+                if ev.msg.id == msg.id:
+                    return True
+            return False
+        else:
+            raise CommandNameError(
+                    ''.join(["Unknown command '", data['action'], "' in ",
+                             self._exc_msg_data(msg)]))
+
+
     def _make_command(self, msg, data):
-        """ Builds a bot command object from ``_parse_message()``'s message
+        """ Build a bot command object from ``_parse_message()``'s message
         data. If the command name is invalid, raises a CommandNameError. If the
         command parameters are invalid or incomplete, raises a
         CommandParametersError (from the command constructor). """
         if data['action'] in self.cmd_actions.keys():
             CommandClass = self.cmd_actions[data.pop('action')]
             cmd_obj = CommandClass(msg, **data)
+            cmd_obj.start_time = 0
         else:
             raise CommandNameError(
                     ''.join(["Unknown command '", data['action'], "' in ",
         * ``start_time`` = 0 (immediately)
         * ``duration`` = -1
         * ``interval`` = 300
-        * ``priority`` = 50
+        * ``priority`` = 100
 
         """
     required_res = ['reddit', 'dbsession', 'config.minibot', 'logger']
     start_time = 0
     duration = -1
     interval = 300
-    priority = 50
+    priority = 100
 
     def __init__(self):
         pass
         * ``start_time`` = 0 (immediately)
         * ``duration`` = 0
         * ``interval`` = 0
+        * ``priority`` = 50
 
     """
     required_res = ['reddit', 'dbsession', 'config.minibot', 'logger']
     start_time = 0 # execute ASAP
+    priority = 50
 
     PARAMS = ['title', 'text', 'user', 'submission', 'date', 'time']
 
         on missing or invalid parameters (respectively). """
 
         self.msg = msg
-        self.raw_params = kwargs
+        self.raw_params = kwargs.copy()
         self.params = dict()
         self._check_param_fields()
 
-    # TODO: test me
     def __repr__(self):
         """ Return a string representing this object. """
         return '<{cls}: message {id_} ({author}), {args}>'.format(
 
         return True
 
-    # TODO: test me
     def _parse_params(self):
         """ Parse params. Stores a dict ``params`` containing parsed parameters
         with defaults set. Parameters: title, text, user, r_user_id,
         reddit = self.res['reddit']
 
         # store params
-        p['title'] = raw.pop('title', None)
-        p['text'] = raw.pop('text', None)
+        p['title'] = raw.get('title', None)
+        p['text'] = raw.get('text', None)
 
         # post time
         p['post_time'] = self.__parse_param_post_time(
-                            raw.pop('date', None), raw.pop('time', None))
+                            raw.get('date', None), raw.get('time', None))
 
         # submitter (user)
-        tmp_user = raw.pop('user', None)
+        tmp_user = raw.get('user', None)
         if tmp_user:
             try:
                 p['r_user_id'] = reddit.get_redditor(tmp_user).id
             p['r_user_id'] = None
 
         # source url - check for reddit domain
-        tmp_submission = raw.pop('submission', None)
+        tmp_submission = raw.get('submission', None)
         if tmp_submission:
             source_url_parsed = urlparse(tmp_submission)
             domain = source_url_parsed.netloc
         # approver
         p['r_approver_id'] = self.msg.author.id
 
-    # TODO: test me
     def __parse_param_post_time(self, datestr, timestr):
         """ Parse date/time params. Returns post time UTC (with default values
         set if params not specified). """
         if use_default_date and use_default_time:
             localtime = datetime.combine(
                             self._get_autodate(), self._get_autotime())
-        elif self.date_default:
+        elif use_default_date:
             # in this case, param_datetime is a dt_time object instead
             localtime = datetime.combine(
                             self._get_autodate(), param_datetime)
-        elif self.time_default:
+        elif use_default_time:
             localtime = datetime.combine(
                             param_datetime.date(), self._get_autotime())
+        else:
+            localtime = param_datetime
 
         return self._utctime(localtime)
 
             err_cmd = SendErrorMessageCommand.command_parameter(e, self.msg)
             self.owner.queue_event(err_cmd)
             self.msg.mark_as_read()
+            self.res['dbsession'].rollback()
             return self.owner.EXC_HANDLED_FINAL
         else:
+            self.res['dbsession'].rollback()
             return self.owner.EXC_UNHANDLED
 
     def _get_autodate(self):
         * ``start_time`` = 0 (immediately)
         * ``duration`` = 0
         * ``interval`` = 0
+        * ``priority`` =48
 
     """
+    priority = 48
     def __init__(self, msg, **kwargs):
         """ Constructor. Keyword arguments are strings defined as per the
         `CheckCommandSpec` class documentation; the text block is expected as
         a 'text' argument. Throws MissingParameterError or CommandParameterError
         on missing or invalid parameters (respectively). """
-        PromptCommandBase.__init__(msg, **kwargs)
+        self.added = False
+        self.times_delayed = 0
+        PromptCommandBase.__init__(self, msg, **kwargs)
 
     def _check_param_fields(self):
         """ Overriden from PromptCommandBase. """
-        try:
-            self.raw_params.get('text')
-        except KeyError:
+        if self.raw_params.get('text', None) is None:
             raise MissingParameterError(
                 "The required text block parameter is missing.")
-        return PromptCommandBase._check_param_fields()
+        return PromptCommandBase._check_param_fields(self)
 
     def start(self):
         self.log = self.res['logger']
     def run(self):
         # shorthand
         p = self.params
+        db = self.res['dbsession']
 
-        # first check User table for approver
-        self._make_approver_user(p['r_approver_id'], self.msg.author.name)
+        # guard against re-adding prompt to DB if Reddit errors occur and the
+        # event is requeued
+        if not self.added:
+            # first check User table for approver
+            self._make_approver_user(p['r_approver_id'], self.msg.author.name)
 
-        # now make new prompt entry in queue
-        new_prompt = Prompt(
-                            p['title'], p['text'], p['r_approver_id'],
-                            datetime.utcfromtimestamp(self.msg.created_utc),
-                            user=p['user'], user_id=p['r_user_id'],
-                            source_url=p['source_url'])
-        new_prompt.queue(p['post_time'])
-        db.add(new_prompt)
+            # now make new prompt entry in queue
+            new_prompt = Prompt(
+                                p['title'], p['text'], p['r_approver_id'],
+                                datetime.utcfromtimestamp(self.msg.created_utc),
+                                user=p['user'], user_id=p['r_user_id'],
+                                source_url=p['r_source_url'])
+            new_prompt.queue(p['post_time']) # already in UTC
+            db.add(new_prompt)
+            db.commit()
+            self.added = True
 
-        db.commit()
         self.log.info("%s: Added prompt '%s' (%d) to database queue.",
-            classname(self), self.title, new_prompt.id)
+            classname(self), p['title'], new_prompt.id)
         self.log.debug("%s: %s", classname(self), repr(new_prompt))
 
         self.msg.mark_as_read()
         reply_text  = ''.join([
                         u"Your prompt has been added.\n\n___\n\n",
                         self._format_prompt(new_prompt)])
-        self.owner.queue_command(
+        self.owner.queue_event(
             SendReplyCommand(self.msg, reply_topic, reply_text))
 
 
+
 class RemovePromptCommand(CommandBase, DataFormatMixin):
     """ Command to remove a prompt from the queue.
 
         * ``start_time`` = 0 (immediately)
         * ``duration`` = 0
         * ``interval`` = 0
+        * ``priority`` = 50
 
     """
     required_res = ['reddit', 'dbsession', 'logger']
     start_time = 0 # execute ASAP
+    priority=50
 
     def __init__(self, msg, **kwargs):
         """ Constructor. Keyword arguments are strings defined as per the
         * ``start_time`` = 0 (immediately)
         * ``duration`` = 0
         * ``interval`` = 0
+        * ``priority`` = 100
 
     """
+    priority = 49
+
     def __init__(self, msg, **kwargs):
         """ Constructor. Keyword arguments are strings defined as per the
         `CheckCommandSpec` class documentation; the text block is expected as
         * ``start_time`` = 0 (immediately)
         * ``duration`` = 0
         * ``interval`` = 0
+        * ``priority`` = 60
 
     """
     required_res = ['reddit', 'logger']
     start_time = 0
+    priority = 60
 
     def __init__(self, user, title, text):
         """ Constructor. Throws MissingParameterError or CommandParameterError
         * ``start_time`` = 0 (immediately)
         * ``duration`` = 0
         * ``interval`` = 0
+        * ``priority`` = 60
 
     """
     required_res = ['logger']
     start_time = 0 # execute ASAP
+    priority = 60
 
     def __init__(self, source_msg, subject, text):
         """ Constructor. Throws MissingParameterError or CommandParameterError
         on missing or invalid parameters (respectively). Note that subject is
         unused for replies. """
         self.msg = source_msg
-        self.title = title
+        self.title = subject
         self.text = text
 
         if not isinstance(self.msg, reddit.objects.Inboxable):
         * ``start_time`` = 0 (immediately)
         * ``duration`` = 0
         * ``interval`` = 0
+        * ``priority`` = 60
 
     """
     start_time = 0 # execute ASAP
+    priority = 60
 
     def __init__(self, source_msg, subject, text):
         """ Constructor. Throws MissingParameterError or CommandParameterError
         * ``start_time`` = 0 (immediately)
         * ``duration`` = 0
         * ``interval`` = 0
+        * ``priority`` = 60
 
     """
 
     SHORT_TEXT_LENGTH = 100
     MAX_ENTRIES = 30
     start_time = 0 # execute ASAP
+    priority = 60
     required_res = ['reddit', 'dbsession', 'logger']
 
     def __init__(self, msg, **kwargs):
             if self.user is not None:
                 query = query.filter(Prompt.user == self.user)
             if self.approver is not None:
-                query = query.filter(Prompt.approver.c.uname == self.approver)
+                query = query.filter(Prompt.approver.uname == self.approver)
 
         if not self.by_id:
             query = query.order_by(Prompt.post_time)
         * ``start_time`` = 0 (immediately)
         * ``duration`` = 0
         * ``interval`` = 0
+        * ``priority`` = 60
 
     """
     def __init__(self, msg, **kwargs):
         * ``start_time`` = 0 (immediately)
         * ``duration`` = 0
         * ``interval`` = 0
+        * ``priority`` = 55
 
     """
     TITLE_PRETEXT  = u"The Daily Prompt for {date_}"
     SOURCE_ONLY_TEXT = u"Originally suggested at [permalink]({permalink})."
     APPROVER_TEXT  = u"Selected by {approver}."
     required_res = ['reddit', 'dbsession', 'config.reddit', 'logger']
+    priority = 55
 
     def __init__(self, post_id, post_time):
         """ Constructor. Throws MissingParameterError or CommandParameterError
 
         # body: approver
         text_parts.append(self.APPROVER_TEXT.format(
-            approver=self.prompt.approver.c.uname))
+            approver=self.prompt.approver.uname))
 
         # body: prompt text
         text_parts.append('\n\n')
         * ``start_time`` = 0 (immediately)
         * ``duration`` = 0
         * ``interval`` = 0
+        * ``priority`` = 56
 
     """
     TITLE_PRETEXT  = u"The Daily Prompt Suggestions for {date_}"
                 u"| :-: | :-: | :-: |\n"
                 u"| « {prev} | .:{cur}:. | {next} » |\n"
                 u"| | | |\n")
+    priority = 56
 
     required_res = ['reddit', 'dbsession', 'config.reddit', 'logger']
     def __init__(self, post_id, post_time):
             else:
                 str_data.append(prompt.user)
             # approver
-            str_data.append(prompt.approver.c.uname)
+            str_data.append(prompt.approver.uname)
             # text
             if len(prompt.text) > self.SHORT_TEXT_LENGTH:
                 str_data.append(prompt.text[0:self.SHORT_TEXT_LENGTH-3] + '...')
             str_data.append(u'\n')
             # approver
             str_data.append(
-                ''.join(['Approver: ', prompt.approver.c.uname, u'\n']))
+                ''.join(['Approver: ', prompt.approver.uname, u'\n']))
             # text
             str_data.append(u'\n')
             str_data.append(prompt.text)