class OAuth2RedditTest(PRAWTest):
    def setUp(self):
        self.configure()
        self.r = Reddit(USER_AGENT, site_name='reddit_oauth_test',
                        disable_update_check=True)

    def test_authorize_url(self):
        self.r.set_oauth_app_info(None, None, None)
        self.assertRaises(errors.OAuthAppRequired, self.r.get_authorize_url,
                          'dummy_state')
        self.r.set_oauth_app_info(self.r.config.client_id,
                                  self.r.config.client_secret,
                                  self.r.config.redirect_uri)
        url, params = self.r.get_authorize_url('...').split('?', 1)
        self.assertTrue('api/v1/authorize/' in url)
        params = dict(x.split('=', 1) for x in params.split('&'))
        expected = {'client_id': self.r.config.client_id,
                    'duration': 'temporary',
                    'redirect_uri': ('https%3A%2F%2F127.0.0.1%3A65010%2F'
                                     'authorize_callback'),
                    'response_type': 'code', 'scope': 'identity',
                    'state': '...'}
        self.assertEqual(expected, params)

    @betamax()
    def test_get_access_information(self):
        # If this test fails, the following URL will need to be visted in order
        # to obtain a new code to pass to `get_access_information`:
        # self.r.get_authorize_url('...')
        token = self.r.get_access_information('MQALrr1di8GzcnT8szbTWhLcBUQ')
        expected = {'access_token': self.r.access_token,
                    'refresh_token': None,
                    'scope': set(('identity',))}
        self.assertEqual(expected, token)
        self.assertEqual('PyAPITestUser2', text_type(self.r.user))

    @betamax()
    def test_get_access_information_with_invalid_code(self):
        self.assertRaises(errors.OAuthInvalidGrant,
                          self.r.get_access_information, 'invalid_code')

    def test_invalid_app_access_token(self):
        self.r.clear_authentication()
        self.r.set_oauth_app_info(None, None, None)
        self.assertRaises(errors.OAuthAppRequired,
                          self.r.get_access_information, 'dummy_code')

    def test_invalid_app_authorize_url(self):
        self.r.clear_authentication()
        self.r.set_oauth_app_info(None, None, None)
        self.assertRaises(errors.OAuthAppRequired,
                          self.r.get_authorize_url, 'dummy_state')

    @betamax()
    def test_invalid_set_access_credentials(self):
        self.assertRaises(errors.OAuthInvalidToken,
                          self.r.set_access_credentials,
                          set(('identity',)), 'dummy_access_token')

    def test_oauth_scope_required(self):
        self.r.set_oauth_app_info('dummy_client', 'dummy_secret', 'dummy_url')
        self.r.set_access_credentials(set('dummy_scope',), 'dummy_token')
        self.assertRaises(errors.OAuthScopeRequired, self.r.get_me)

    @betamax()
    def test_scope_edit(self):
        self.r.refresh_access_information(self.refresh_token['edit'])
        submission = Submission.from_id(self.r, self.submission_edit_id)
        self.assertEqual(submission, submission.edit('Edited text'))

    @betamax()
    def test_scope_history(self):
        self.r.refresh_access_information(self.refresh_token['history'])
        self.assertTrue(list(self.r.get_redditor(self.un).get_upvoted()))

    @betamax()
    def test_scope_identity(self):
        self.r.refresh_access_information(self.refresh_token['identity'])
        self.assertEqual(self.un, self.r.get_me().name)

    @betamax()
    def test_scope_modconfig(self):
        self.r.refresh_access_information(self.refresh_token['modconfig'])
        self.r.get_subreddit(self.sr).set_settings('foobar')
        retval = self.r.get_subreddit(self.sr).get_stylesheet()
        self.assertTrue('images' in retval)

    @betamax()
    def test_scope_modflair(self):
        self.r.refresh_access_information(self.refresh_token['modflair'])
        self.r.get_subreddit(self.sr).set_flair(self.un, 'foobar')

    @betamax()
    def test_scope_modlog(self):
        num = 50
        self.r.refresh_access_information(self.refresh_token['modlog'])
        result = self.r.get_subreddit(self.sr).get_mod_log(limit=num)
        self.assertEqual(num, len(list(result)))

    @betamax()
    def test_scope_modothers_modself(self):
        subreddit = self.r.get_subreddit(self.sr)
        self.r.refresh_access_information(self.refresh_token['modothers'])
        subreddit.add_moderator(self.other_user_name)

        # log in as other user
        self.r.refresh_access_information(self.other_refresh_token['modself'])
        self.r.accept_moderator_invite(self.sr)

        # now return to original user.
        self.r.refresh_access_information(self.refresh_token['modothers'])
        subreddit.remove_moderator(self.other_user_name)

    @betamax()
    def test_scope_modposts(self):
        self.r.refresh_access_information(self.refresh_token['modposts'])
        Submission.from_id(self.r, self.submission_edit_id).remove()

    @betamax()
    def test_scope_mysubreddits(self):
        self.r.refresh_access_information(self.refresh_token['mysubreddits'])
        self.assertTrue(list(self.r.get_my_moderation()))

    @betamax()
    def test_scope_creddits(self):
        # Assume there are insufficient creddits.
        self.r.refresh_access_information(
            self.refresh_token['creddits'])
        redditor = self.r.get_redditor('bboe')
        sub = self.r.get_submission(url=self.comment_url)

        # Test error conditions
        self.assertRaises(TypeError, sub.gild, months=1)
        for value in (False, 0, -1, '0', '-1'):
            self.assertRaises(TypeError, redditor.gild, value)

        # Test object gilding
        self.assertRaises(errors.InsufficientCreddits, redditor.gild)
        self.assertRaises(errors.InsufficientCreddits, sub.gild)
        self.assertRaises(errors.InsufficientCreddits, sub.comments[0].gild)

    @betamax()
    def test_scope_privatemessages(self):
        self.r.refresh_access_information(
            self.refresh_token['privatemessages'])
        self.assertTrue(list(self.r.get_inbox()))

    @betamax()
    def test_scope_read(self):
        self.r.refresh_access_information(self.refresh_token['read'])
        self.assertTrue(self.r.get_subreddit(self.priv_sr).subscribers > 0)
        fullname = '{0}_{1}'.format(self.r.config.by_object[Submission],
                                    self.priv_submission_id)
        method1 = self.r.get_info(thing_id=fullname)
        method2 = self.r.get_submission(submission_id=self.priv_submission_id)
        self.assertEqual(method1, method2)

    @betamax()
    def test_scope_read_get_front_page(self):
        self.r.refresh_access_information(self.refresh_token['mysubreddits'])
        subscribed = list(self.r.get_my_subreddits(limit=None))
        self.r.refresh_access_information(self.refresh_token['read'])
        for post in self.r.get_front_page():
            self.assertTrue(post.subreddit in subscribed)

    @betamax()
    def test_scope_read_get_sub_listingr(self):
        self.r.refresh_access_information(self.refresh_token['read'])
        subreddit = self.r.get_subreddit(self.priv_sr)
        self.assertTrue(list(subreddit.get_top()))

    @betamax()
    def test_scope_read_get_submission_by_url(self):
        url = ("https://www.reddit.com/r/reddit_api_test_priv/comments/16kbb7/"
               "google/")
        self.r.refresh_access_information(self.refresh_token['read'])
        submission = Submission.from_url(self.r, url)
        self.assertTrue(submission.num_comments != 0)

    @betamax()
    def test_scope_read_priv_sr_comments(self):
        self.r.refresh_access_information(self.refresh_token['read'])
        self.assertTrue(list(self.r.get_comments(self.priv_sr)))

    @betamax()
    def test_scope_wikiread_wiki_page(self):
        self.r.refresh_access_information(self.refresh_token['wikiread'])
        self.assertTrue(self.r.get_wiki_page(self.sr, 'index'))

    @betamax()
    def test_scope_read_priv_sub_comments(self):
        self.r.refresh_access_information(self.refresh_token['read'])
        submission = Submission.from_id(self.r, self.priv_submission_id)
        self.assertTrue(submission.comments)

    @betamax()
    def test_scope_submit(self):
        self.r.refresh_access_information(self.refresh_token['submit'])
        result = self.r.submit(self.sr, 'OAuth Submit', text='Foo')
        self.assertTrue(isinstance(result, Submission))

    @betamax()
    def test_scope_subscribe(self):
        self.r.refresh_access_information(self.refresh_token['subscribe'])
        self.r.get_subreddit(self.sr).subscribe()

    @betamax()
    def test_scope_vote(self):
        self.r.refresh_access_information(self.refresh_token['vote'])
        submission = Submission.from_id(self.r, self.submission_edit_id)
        submission.clear_vote()

    @betamax()
    def test_set_access_credentials(self):
        self.assertTrue(self.r.user is None)
        result = self.r.refresh_access_information(
            self.refresh_token['identity'], update_session=False)
        self.assertTrue(self.r.user is None)
        self.r.set_access_credentials(**result)
        self.assertFalse(self.r.user is None)

    @betamax()
    def test_oauth_without_identy_doesnt_set_user(self):
        self.assertTrue(self.r.user is None)
        self.r.refresh_access_information(self.refresh_token['edit'])
        self.assertTrue(self.r.user is None)
示例#2
0
class OAuth2RedditTest(PRAWTest):
    def setUp(self):
        self.configure()
        self.r = Reddit(USER_AGENT,
                        site_name='reddit_oauth_test',
                        disable_update_check=True)

    def test_authorize_url(self):
        self.r.set_oauth_app_info(None, None, None)
        self.assertRaises(errors.OAuthAppRequired, self.r.get_authorize_url,
                          'dummy_state')
        self.r.set_oauth_app_info(self.r.config.client_id,
                                  self.r.config.client_secret,
                                  self.r.config.redirect_uri)
        url, params = self.r.get_authorize_url('...').split('?', 1)
        self.assertTrue('api/v1/authorize/' in url)
        params = dict(x.split('=', 1) for x in params.split('&'))
        expected = {
            'client_id':
            self.r.config.client_id,
            'duration':
            'temporary',
            'redirect_uri': ('https%3A%2F%2F127.0.0.1%3A65010%2F'
                             'authorize_callback'),
            'response_type':
            'code',
            'scope':
            'identity',
            'state':
            '...'
        }
        self.assertEqual(expected, params)

    @betamax()
    @mock_sys_stream("stdin")
    def test_empty_captcha_file(self):
        # Use the alternate account because it has low karma,
        # so we can test the captcha.
        self.r.refresh_access_information(self.other_refresh_token['submit'])
        self.assertRaises(errors.InvalidCaptcha, self.r.submit, self.sr,
                          'captcha test will fail', 'body')

    @betamax()
    def test_get_access_information(self):
        # If this test fails, the following URL will need to be visted in order
        # to obtain a new code to pass to `get_access_information`:
        # self.r.get_authorize_url('...')
        token = self.r.get_access_information('MQALrr1di8GzcnT8szbTWhLcBUQ')
        expected = {
            'access_token': self.r.access_token,
            'refresh_token': None,
            'scope': set(('identity', ))
        }
        self.assertEqual(expected, token)
        self.assertEqual('PyAPITestUser2', text_type(self.r.user))

    @betamax()
    def test_get_access_information_with_invalid_code(self):
        self.assertRaises(errors.OAuthInvalidGrant,
                          self.r.get_access_information, 'invalid_code')

    @betamax()
    @mock_sys_stream("stdin")
    def test_inject_captcha_into_kwargs_and_raise(self):
        # Use the alternate account because it has low karma,
        # so we can test the captcha.
        self.r.refresh_access_information(self.other_refresh_token['submit'])

        # praw doesn't currently add the captcha into kwargs so lets
        # write a function in which it would and alias it to Reddit.submit
        @decorators.restrict_access(scope='submit')
        @decorators.require_captcha
        def submit_alias(r, sr, title, text, **kw):
            return self.r.submit.__wrapped__.__wrapped__(
                r, sr, title, text, captcha=kw.get('captcha'))

        self.assertRaises(errors.InvalidCaptcha, submit_alias, self.r, self.sr,
                          'captcha test will fail', 'body')

    def test_invalid_app_access_token(self):
        self.r.clear_authentication()
        self.r.set_oauth_app_info(None, None, None)
        self.assertRaises(errors.OAuthAppRequired,
                          self.r.get_access_information, 'dummy_code')

    def test_invalid_app_authorize_url(self):
        self.r.clear_authentication()
        self.r.set_oauth_app_info(None, None, None)
        self.assertRaises(errors.OAuthAppRequired, self.r.get_authorize_url,
                          'dummy_state')

    @betamax()
    def test_invalid_set_access_credentials(self):
        self.assertRaises(errors.OAuthInvalidToken,
                          self.r.set_access_credentials, set(
                              ('identity', )), 'dummy_access_token')

    def test_oauth_scope_required(self):
        self.r.set_oauth_app_info('dummy_client', 'dummy_secret', 'dummy_url')
        self.r.set_access_credentials(set('dummy_scope', ), 'dummy_token')
        self.assertRaises(errors.OAuthScopeRequired, self.r.get_me)

    def test_raise_client_exception(self):
        def raise_client_exception(*args):
            raise errors.ClientException(*args)

        self.assertRaises(errors.ClientException, raise_client_exception)
        self.assertRaises(errors.ClientException, raise_client_exception,
                          'test')

        ce_message = errors.ClientException('Test')
        ce_no_message = errors.ClientException()
        self.assertEqual(ce_message.message, str(ce_message))
        self.assertEqual(ce_no_message.message, str(ce_no_message))

    def test_raise_http_exception(self):
        def raise_http_exception():
            raise errors.HTTPException('fakeraw')

        self.assertRaises(errors.HTTPException, raise_http_exception)
        http_exception = errors.HTTPException('fakeraw')
        self.assertEqual(http_exception.message, str(http_exception))

    def test_raise_oauth_exception(self):
        oerrormessage = "fakemessage"
        oerrorurl = "http://oauth.reddit.com/"

        def raise_oauth_exception():
            raise errors.OAuthException(oerrormessage, oerrorurl)

        self.assertRaises(errors.OAuthException, raise_oauth_exception)
        oauth_exception = errors.OAuthException(oerrormessage, oerrorurl)
        self.assertEqual(
            oauth_exception.message +
            " on url {0}".format(oauth_exception.url), str(oauth_exception))

    def test_raise_redirect_exception(self):
        apiurl = "http://api.reddit.com/"
        oauthurl = "http://oauth.reddit.com/"

        def raise_redirect_exception():
            raise errors.RedirectException(apiurl, oauthurl)

        self.assertRaises(errors.RedirectException, raise_redirect_exception)
        redirect_exception = errors.RedirectException(apiurl, oauthurl)
        self.assertEqual(redirect_exception.message, str(redirect_exception))

    @betamax()
    def test_scope_history(self):
        self.r.refresh_access_information(self.refresh_token['history'])
        self.assertTrue(list(self.r.get_redditor(self.un).get_upvoted()))

    @betamax()
    def test_scope_identity(self):
        self.r.refresh_access_information(self.refresh_token['identity'])
        self.assertEqual(self.un, self.r.get_me().name)

    @betamax()
    def test_scope_mysubreddits(self):
        self.r.refresh_access_information(self.refresh_token['mysubreddits'])
        self.assertTrue(list(self.r.get_my_moderation()))

    @betamax()
    def test_scope_creddits(self):
        # Assume there are insufficient creddits.
        self.r.refresh_access_information(self.refresh_token['creddits'])
        redditor = self.r.get_redditor('bboe')
        sub = self.r.get_submission(url=self.comment_url)

        # Test error conditions
        self.assertRaises(TypeError, sub.gild, months=1)
        for value in (False, 0, -1, '0', '-1', 37, '37'):
            self.assertRaises(TypeError, redditor.gild, value)

        # Test object gilding
        self.assertRaises(errors.InsufficientCreddits, redditor.gild)
        self.assertRaises(errors.InsufficientCreddits, sub.gild)
        self.assertRaises(errors.InsufficientCreddits, sub.comments[0].gild)

    @betamax()
    def test_scope_privatemessages(self):
        self.r.refresh_access_information(
            self.refresh_token['privatemessages'])
        self.assertTrue(list(self.r.get_inbox()))

    @betamax()
    def test_scope_read(self):
        self.r.refresh_access_information(self.refresh_token['read'])
        self.assertTrue(self.r.get_subreddit(self.priv_sr).subscribers > 0)
        fullname = '{0}_{1}'.format(self.r.config.by_object[Submission],
                                    self.priv_submission_id)
        method1 = self.r.get_info(thing_id=fullname)
        method2 = self.r.get_submission(submission_id=self.priv_submission_id)
        self.assertEqual(method1, method2)

    @betamax()
    def test_scope_read_get_front_page(self):
        self.r.refresh_access_information(self.refresh_token['mysubreddits'])
        subscribed = list(self.r.get_my_subreddits(limit=None))
        self.r.refresh_access_information(self.refresh_token['read'])
        for post in self.r.get_front_page():
            self.assertTrue(post.subreddit in subscribed)

    @betamax()
    def test_set_access_credentials(self):
        self.assertTrue(self.r.user is None)
        result = self.r.refresh_access_information(
            self.refresh_token['identity'], update_session=False)
        self.assertTrue(self.r.user is None)
        self.r.set_access_credentials(**result)
        self.assertFalse(self.r.user is None)

    @betamax()
    def test_set_access_credentials_with_list(self):
        self.assertTrue(self.r.user is None)
        result = self.r.refresh_access_information(
            self.refresh_token['identity'], update_session=False)
        self.assertTrue(self.r.user is None)
        result['scope'] = list(result['scope'])
        self.r.set_access_credentials(**result)
        self.assertFalse(self.r.user is None)

    @betamax()
    def test_set_access_credentials_with_string(self):
        self.assertTrue(self.r.user is None)
        result = self.r.refresh_access_information(
            self.refresh_token['identity'], update_session=False)
        self.assertTrue(self.r.user is None)
        result['scope'] = ' '.join(result['scope'])
        self.r.set_access_credentials(**result)
        self.assertFalse(self.r.user is None)

    @betamax()
    @mock_sys_stream("stdin", "ljgtoo")
    def test_solve_captcha(self):
        # Use the alternate account because it has low karma,
        # so we can test the captcha.
        self.r.refresh_access_information(self.other_refresh_token['submit'])
        self.r.submit(self.sr, 'captcha test', 'body')

    @betamax()
    @mock_sys_stream("stdin", "DFIRSW")
    def test_solve_captcha_on_bound_subreddit(self):
        # Use the alternate account because it has low karma,
        # so we can test the captcha.
        self.r.refresh_access_information(self.other_refresh_token['submit'])
        subreddit = self.r.get_subreddit(self.sr)

        # praw doesn't currently have a function in which require_captcha
        # gets a reddit instance from a subreddit and uses it, so lets
        # write a function in which it would and alias it to Reddit.submit
        @decorators.restrict_access(scope='submit')
        @decorators.require_captcha
        def submit_alias(sr, title, text, **kw):
            return self.r.submit.__wrapped__.__wrapped__(
                self.r, sr, title, text, captcha=kw.get('captcha'))

        submit_alias(subreddit, 'captcha test on bound subreddit', 'body')

    @betamax()
    def test_oauth_without_identy_doesnt_set_user(self):
        self.assertTrue(self.r.user is None)
        self.r.refresh_access_information(self.refresh_token['edit'])
        self.assertTrue(self.r.user is None)
示例#3
0
class SubRedditStats(object):

    """Contain all the functionality of the subreddit_stats command."""

    post_prefix = tt('Subreddit Stats:')
    post_header = tt('---\n###{0}\n')
    post_footer = tt('>Generated with [BBoe](/u/bboe)\'s [Subreddit Stats]'
                     '(https://github.com/praw-dev/prawtools)  \n{0}'
                     'SRS Marker: {1}')
    re_marker = re.compile(r'SRS Marker: (\d+)')

    @staticmethod
    def _previous_max(submission):
        try:
            val = SubRedditStats.re_marker.findall(submission.selftext)[-1]
            return float(val)
        except (IndexError, TypeError):
            print('End marker not found in previous submission. Aborting')
            sys.exit(1)

    @staticmethod
    def _permalink(permalink):
        tokens = permalink.split('/')
        if tokens[8] == '':  # submission
            return tt('/comments/{0}/_/').format(tokens[6])
        else:  # comment
            return tt('/comments/{0}/_/{1}?context=1').format(tokens[6],
                                                              tokens[8])

    @staticmethod
    def _pts(points):
        return '1 pt' if points == 1 else '{0} pts'.format(points)

    @staticmethod
    def _user(user):
        if user is None:
            return '_deleted_'
        elif isinstance(user, Redditor):
            user = str(user)
        return tt('[{0}](/user/{1})').format(user.replace('_', r'\_'), user)

    @staticmethod
    def _submit(func, *args, **kwargs):
        def sleep(sleep_time):
            print('\tSleeping for {0} seconds'.format(sleep_time))
            time.sleep(sleep_time)

        while True:
            try:
                return func(*args, **kwargs)
            except RateLimitExceeded as error:
                sleep(error.sleep_time)
            except ExceptionList as exception_list:
                for error in exception_list.errors:
                    if isinstance(error, RateLimitExceeded):
                        sleep(error.sleep_time)
                        break
                else:
                    raise

    def __init__(self, subreddit, site, verbosity, distinguished):
        """Initialize the SubRedditStats instance with config options."""
        self.reddit = Reddit(str(self), site, disable_update_check=True)
        self.subreddit = self.reddit.get_subreddit(subreddit)
        self.verbosity = verbosity
        self.distinguished = distinguished
        self.submissions = []
        self.comments = []
        self.submitters = defaultdict(list)
        self.commenters = defaultdict(list)
        self.min_date = 0
        self.max_date = time.time() - DAYS_IN_SECONDS * 3
        self.prev_srs = None

    def login(self, user, pswd):
        """Login and provide debugging output if so wanted."""
        if self.verbosity > 0:
            print('Logging in')
        self.reddit.login(user, pswd)

    def msg(self, msg, level, overwrite=False):
        """Output a messaage to the screen if the verbosity is sufficient."""
        if self.verbosity and self.verbosity >= level:
            sys.stdout.write(msg)
            if overwrite:
                sys.stdout.write('\r')
                sys.stdout.flush()
            else:
                sys.stdout.write('\n')

    def prev_stat(self, prev_url):
        """Load the previous subreddit stats page."""
        submission = self.reddit.get_submission(prev_url)
        self.min_date = self._previous_max(submission)
        self.prev_srs = prev_url

    def fetch_recent_submissions(self, max_duration, after, exclude_self,
                                 exclude_link, since_last=True):
        """Fetch recent submissions in subreddit with boundaries.

        Does not include posts within the last three days as their scores may
        not be representative.

        :param max_duration: When set, specifies the number of days to include
        :param after: When set, fetch all submission after this submission id.
        :param exclude_self: When true, don't include self posts.
        :param exclude_link:  When true, don't include links.
        :param since_last: When true use info from last submission to determine
            the stop point
        :returns: True if any submissions were found.

        """
        if exclude_self and exclude_link:
            raise TypeError('Cannot set both exclude_self and exclude_link.')
        if max_duration:
            self.min_date = self.max_date - DAYS_IN_SECONDS * max_duration
        params = {'after': after} if after else None
        self.msg('DEBUG: Fetching submissions', 1)
        for submission in self.subreddit.get_new(limit=None, params=params):
            if submission.created_utc > self.max_date:
                continue
            if submission.created_utc <= self.min_date:
                break
            if since_last and str(submission.author) == str(self.reddit.user) \
                    and submission.title.startswith(self.post_prefix):
                # Use info in this post to update the min_date
                # And don't include this post
                self.msg(tt('Found previous: {0}')
                         .format(safe_title(submission)), 2)
                if self.prev_srs is None:  # Only use the most recent
                    self.min_date = max(self.min_date,
                                        self._previous_max(submission))
                    self.prev_srs = submission.permalink
                continue
            if exclude_self and submission.is_self:
                continue
            if exclude_link and not submission.is_self:
                continue
            self.submissions.append(submission)
        num_submissions = len(self.submissions)
        self.msg('DEBUG: Found {0} submissions'.format(num_submissions), 1)
        if num_submissions == 0:
            return False

        # Update real min and max dates
        self.submissions.sort(key=lambda x: x.created_utc)
        self.min_date = self.submissions[0].created_utc
        self.max_date = self.submissions[-1].created_utc
        return True

    def fetch_top_submissions(self, top, exclude_self, exclude_link):
        """Fetch top 1000 submissions by some top value.

        :param top: One of week, month, year, all
        :param exclude_self: When true, don't include self posts.
        :param exclude_link: When true, include only self posts
        :returns: True if any submissions were found.

        """
        if exclude_self and exclude_link:
            raise TypeError('Cannot set both exclude_self and exclude_link.')
        if top not in ('day', 'week', 'month', 'year', 'all'):
            raise TypeError('{0!r} is not a valid top value'.format(top))
        self.msg('DEBUG: Fetching submissions', 1)
        params = {'t': top}
        for submission in self.subreddit.get_top(limit=None, params=params):
            if exclude_self and submission.is_self:
                continue
            if exclude_link and not submission.is_self:
                continue
            self.submissions.append(submission)
        num_submissions = len(self.submissions)
        self.msg('DEBUG: Found {0} submissions'.format(num_submissions), 1)
        if num_submissions == 0:
            return False

        # Update real min and max dates
        self.submissions.sort(key=lambda x: x.created_utc)
        self.min_date = self.submissions[0].created_utc
        self.max_date = self.submissions[-1].created_utc
        return True

    def process_submitters(self):
        """Group submissions by author."""
        self.msg('DEBUG: Processing Submitters', 1)
        for submission in self.submissions:
            if submission.author and (self.distinguished or
                                      submission.distinguished is None):
                self.submitters[str(submission.author)].append(submission)

    def process_commenters(self):
        """Group comments by author."""
        num = len(self.submissions)
        self.msg('DEBUG: Processing Commenters on {0} submissions'.format(num),
                 1)
        for i, submission in enumerate(self.submissions):
            # Explicitly fetch as many comments as possible by top sort
            # Note that this is the first time the complete submission object
            # is obtained. Only a partial object was returned when getting the
            # subreddit listings.
            try:
                submission = self.reddit.get_submission(submission.permalink,
                                                        comment_limit=None,
                                                        comment_sort='top')
            except HTTPError as exc:
                print('Ignoring comments on {0} due to HTTP status {1}'
                      .format(submission.url, exc.response.status_code))
                continue
            self.msg('{0}/{1} submissions'.format(i + 1, num), 2,
                     overwrite=True)
            if submission.num_comments == 0:
                continue
            skipped = submission.replace_more_comments()
            if skipped:
                skip_num = sum(x.count for x in skipped)
                print('Ignored {0} comments ({1} MoreComment objects)'
                      .format(skip_num, len(skipped)))
            comments = [x for x in flatten_tree(submission.comments) if
                        self.distinguished or x.distinguished is None]
            self.comments.extend(comments)
            # pylint: disable=W0212
            for orphans in itervalues(submission._orphaned):
                self.comments.extend(orphans)
            # pylint: enable=W0212
        for comment in self.comments:
            if comment.author:
                self.commenters[str(comment.author)].append(comment)

    def basic_stats(self):
        """Return a markdown representation of simple statistics."""
        sub_score = sum(x.score for x in self.submissions)
        comm_score = sum(x.score for x in self.comments)
        sub_duration = self.max_date - self.min_date
        sub_rate = (86400. * len(self.submissions) / sub_duration
                    if sub_duration else len(self.submissions))

        # Compute comment rate
        if self.comments:
            self.comments.sort(key=lambda x: x.created_utc)
            duration = (self.comments[-1].created_utc -
                        self.comments[0].created_utc)
            comm_rate = (86400. * len(self.comments) / duration
                         if duration else len(self.comments))
        else:
            comm_rate = 0

        values = [('Total', len(self.submissions), len(self.comments)),
                  ('Rate (per day)', '{0:.2f}'.format(sub_rate),
                   '{0:.2f}'.format(comm_rate)),
                  ('Unique Redditors', len(self.submitters),
                   len(self.commenters)),
                  ('Combined Score', sub_score, comm_score)]

        retval = 'Period: {0:.2f} days\n\n'.format(sub_duration / 86400.)
        retval += '||Submissions|Comments|\n:-:|--:|--:\n'
        for quad in values:
            # pylint: disable=W0142
            retval += '__{0}__|{1}|{2}\n'.format(*quad)
            # pylint: enable=W0142
        return retval + '\n'

    def top_submitters(self, num, num_submissions):
        """Return a markdown representation of the top submitters."""
        num = min(num, len(self.submitters))
        if num <= 0:
            return ''

        top_submitters = sorted(iteritems(self.submitters), reverse=True,
                                key=lambda x: (sum(y.score for y in x[1]),
                                               len(x[1])))[:num]

        retval = self.post_header.format('Top Submitters\' Top Submissions')
        for (author, submissions) in top_submitters:
            retval += '0. {0}, {1} submission{2}: {3}\n'.format(
                self._pts(sum(x.score for x in submissions)), len(submissions),
                's' if len(submissions) > 1 else '', self._user(author))
            for sub in sorted(submissions, reverse=True,
                              key=lambda x: x.score)[:num_submissions]:
                title = safe_title(sub)
                if sub.permalink != sub.url:
                    retval += tt('  0. [{0}]({1})').format(title, sub.url)
                else:
                    retval += tt('  0. {0}').format(title)
                retval += ' ({0}, [{1} comment{2}]({3}))\n'.format(
                    self._pts(sub.score), sub.num_comments,
                    's' if sub.num_comments > 1 else '',
                    self._permalink(sub.permalink))
            retval += '\n'
        return retval

    def top_commenters(self, num):
        """Return a markdown representation of the top commenters."""
        score = lambda x: x.score

        num = min(num, len(self.commenters))
        if num <= 0:
            return ''

        top_commenters = sorted(iteritems(self.commenters), reverse=True,
                                key=lambda x: (sum(score(y) for y in x[1]),
                                               len(x[1])))[:num]

        retval = self.post_header.format('Top Commenters')
        for author, comments in top_commenters:
            retval += '0. {0} ({1}, {2} comment{3})\n'.format(
                self._user(author), self._pts(sum(score(x) for x in comments)),
                len(comments), 's' if len(comments) > 1 else '')
        return '{0}\n'.format(retval)

    def top_submissions(self, num):
        """Return a markdown representation of the top submissions."""
        num = min(num, len(self.submissions))
        if num <= 0:
            return ''

        top_submissions = sorted(
            [x for x in self.submissions if self.distinguished or
             x.distinguished is None],
            reverse=True, key=lambda x: x.score)[:num]

        if not top_submissions:
            return ''

        retval = self.post_header.format('Top Submissions')
        for sub in top_submissions:
            title = safe_title(sub)
            if sub.permalink != sub.url:
                retval += tt('0. [{0}]({1})').format(title, sub.url)
            else:
                retval += tt('0. {0}').format(title)
            retval += ' by {0} ({1}, [{2} comment{3}]({4}))\n'.format(
                self._user(sub.author), self._pts(sub.score), sub.num_comments,
                's' if sub.num_comments > 1 else '',
                self._permalink(sub.permalink))
        return tt('{0}\n').format(retval)

    def top_comments(self, num):
        """Return a markdown representation of the top comments."""
        score = lambda x: x.score

        num = min(num, len(self.comments))
        if num <= 0:
            return ''

        top_comments = sorted(self.comments, reverse=True,
                              key=score)[:num]
        retval = self.post_header.format('Top Comments')
        for comment in top_comments:
            title = safe_title(comment.submission)
            retval += tt('0. {0}: {1}\'s [comment]({2}) in {3}\n').format(
                self._pts(score(comment)), self._user(comment.author),
                self._permalink(comment.permalink), title)
        return tt('{0}\n').format(retval)

    def publish_results(self, subreddit, submitters, commenters, submissions,
                        comments, top, debug=False):
        """Submit the results to the subreddit. Has no return value (None)."""
        def timef(timestamp, date_only=False):
            """Return a suitable string representaation of the timestamp."""
            dtime = datetime.fromtimestamp(timestamp)
            if date_only:
                retval = dtime.strftime('%Y-%m-%d')
            else:
                retval = dtime.strftime('%Y-%m-%d %H:%M PDT')
            return retval

        if self.prev_srs:
            prev = '[Prev SRS]({0})  \n'.format(self._permalink(self.prev_srs))
        else:
            prev = ''

        basic = self.basic_stats()
        t_commenters = self.top_commenters(commenters)
        t_submissions = self.top_submissions(submissions)
        t_comments = self.top_comments(comments)
        footer = self.post_footer.format(prev, self.max_date)

        body = ''
        num_submissions = 10
        while body == '' or len(body) > MAX_BODY_SIZE and num_submissions > 2:
            t_submitters = self.top_submitters(submitters, num_submissions)
            body = (basic + t_submitters + t_commenters + t_submissions +
                    t_comments + footer)
            num_submissions -= 1

        if len(body) > MAX_BODY_SIZE:
            print('The resulting message is too big. Not submitting.')
            debug = True

        # Set the initial title
        base_title = '{0} {1} {2}posts from {3} to {4}'.format(
            self.post_prefix, str(self.subreddit),
            'top ' if top else '', timef(self.min_date, True),
            timef(self.max_date))

        submitted = False
        while not debug and not submitted:
            if subreddit:  # Verify the user wants to submit to the subreddit
                msg = ('You are about to submit to subreddit {0!r} as {1!r}.\n'
                       'Are you sure? yes/[no]: '
                       .format(subreddit, str(self.reddit.user)))
                sys.stdout.write(msg)
                sys.stdout.flush()
                if sys.stdin.readline().strip().lower() not in ['y', 'yes']:
                    subreddit = None
            elif not subreddit:  # Prompt for the subreddit to submit to
                msg = ('Please enter a subreddit to submit to (press return to'
                       ' abort): ')
                sys.stdout.write(msg)
                sys.stdout.flush()
                subreddit = sys.stdin.readline().strip()
                if not subreddit:
                    print('Submission aborted\n')
                    debug = True

            # Vary the title depending on where posting
            if str(self.subreddit) == subreddit:
                title = '{0} {1}posts from {2} to {3}'.format(
                    self.post_prefix, 'top ' if top else '',
                    timef(self.min_date, True), timef(self.max_date))
            else:
                title = base_title

            if subreddit:
                # Attempt to make the submission
                try:
                    res = self._submit(self.reddit.submit, subreddit, title,
                                       text=body)
                    print(res.permalink)
                    submitted = True
                except Exception as error:  # pylint: disable=W0703
                    print('The submission failed:' + str(error))
                    subreddit = None

        if not submitted:
            print(base_title)
            print(body)

    def save_csv(self, filename):
        """Create csv file containing comments and submissions by author."""
        redditors = set(self.submitters.keys()).union(self.commenters.keys())
        mapping = dict((x.lower(), x) for x in redditors)
        with codecs.open(filename, 'w', encoding='utf-8') as outfile:
            outfile.write('username, type, permalink, score\n')
            for _, redditor in sorted(mapping.items()):
                for submission in self.submitters.get(redditor, []):
                    outfile.write(u'{0}, submission, {1}, {2}\n'
                                  .format(redditor, submission.permalink,
                                          submission.score))
                for comment in self.commenters.get(redditor, []):
                    outfile.write(u'{0}, comment, {1}, {2}\n'
                                  .format(redditor, comment.permalink,
                                          comment.score))
示例#4
0
class OAuth2RedditTest(PRAWTest):
    def setUp(self):
        self.configure()
        self.r = Reddit(USER_AGENT,
                        site_name='reddit_oauth_test',
                        disable_update_check=True)

    def test_authorize_url(self):
        self.r.set_oauth_app_info(None, None, None)
        self.assertRaises(errors.OAuthAppRequired, self.r.get_authorize_url,
                          'dummy_state')
        self.r.set_oauth_app_info(self.r.config.client_id,
                                  self.r.config.client_secret,
                                  self.r.config.redirect_uri)
        url, params = self.r.get_authorize_url('...').split('?', 1)
        self.assertTrue('api/v1/authorize/' in url)
        params = dict(x.split('=', 1) for x in params.split('&'))
        expected = {
            'client_id':
            self.r.config.client_id,
            'duration':
            'temporary',
            'redirect_uri': ('https%3A%2F%2F127.0.0.1%3A65010%2F'
                             'authorize_callback'),
            'response_type':
            'code',
            'scope':
            'identity',
            'state':
            '...'
        }
        self.assertEqual(expected, params)

    # @betamax() is currently broken for this test because the cassettes
    # are caching too aggressively and not performing a token refresh.
    def test_auto_refresh_token(self):
        self.r.refresh_access_information(self.refresh_token['identity'])
        old_token = self.r.access_token

        self.r.access_token += 'x'  # break the token
        self.r.user.refresh()
        current_token = self.r.access_token
        self.assertNotEqual(old_token, current_token)

        self.r.user.refresh()
        self.assertEqual(current_token, self.r.access_token)

    @betamax()
    def test_get_access_information(self):
        # If this test fails, the following URL will need to be visted in order
        # to obtain a new code to pass to `get_access_information`:
        # self.r.get_authorize_url('...')
        token = self.r.get_access_information('MQALrr1di8GzcnT8szbTWhLcBUQ')
        expected = {
            'access_token': self.r.access_token,
            'refresh_token': None,
            'scope': set(('identity', ))
        }
        self.assertEqual(expected, token)
        self.assertEqual('PyAPITestUser2', text_type(self.r.user))

    @betamax()
    def test_get_access_information_with_invalid_code(self):
        self.assertRaises(errors.OAuthInvalidGrant,
                          self.r.get_access_information, 'invalid_code')

    def test_invalid_app_access_token(self):
        self.r.clear_authentication()
        self.r.set_oauth_app_info(None, None, None)
        self.assertRaises(errors.OAuthAppRequired,
                          self.r.get_access_information, 'dummy_code')

    def test_invalid_app_authorize_url(self):
        self.r.clear_authentication()
        self.r.set_oauth_app_info(None, None, None)
        self.assertRaises(errors.OAuthAppRequired, self.r.get_authorize_url,
                          'dummy_state')

    @betamax()
    def test_invalid_set_access_credentials(self):
        self.assertRaises(errors.OAuthInvalidToken,
                          self.r.set_access_credentials, set(
                              ('identity', )), 'dummy_access_token')

    def test_oauth_scope_required(self):
        self.r.set_oauth_app_info('dummy_client', 'dummy_secret', 'dummy_url')
        self.r.set_access_credentials(set('dummy_scope', ), 'dummy_token')
        self.assertRaises(errors.OAuthScopeRequired, self.r.get_me)

    def test_raise_client_exception(self):
        def raise_client_exception(*args):
            raise errors.ClientException(*args)

        self.assertRaises(errors.ClientException, raise_client_exception)
        self.assertRaises(errors.ClientException, raise_client_exception,
                          'test')

        ce_message = errors.ClientException('Test')
        ce_no_message = errors.ClientException()
        self.assertEqual(ce_message.message, str(ce_message))
        self.assertEqual(ce_no_message.message, str(ce_no_message))

    def test_raise_http_exception(self):
        def raise_http_exception():
            raise errors.HTTPException('fakeraw')

        self.assertRaises(errors.HTTPException, raise_http_exception)
        http_exception = errors.HTTPException('fakeraw')
        self.assertEqual(http_exception.message, str(http_exception))

    def test_raise_oauth_exception(self):
        oerrormessage = "fakemessage"
        oerrorurl = "http://oauth.reddit.com/"

        def raise_oauth_exception():
            raise errors.OAuthException(oerrormessage, oerrorurl)

        self.assertRaises(errors.OAuthException, raise_oauth_exception)
        oauth_exception = errors.OAuthException(oerrormessage, oerrorurl)
        self.assertEqual(
            oauth_exception.message +
            " on url {0}".format(oauth_exception.url), str(oauth_exception))

    def test_raise_redirect_exception(self):
        apiurl = "http://api.reddit.com/"
        oauthurl = "http://oauth.reddit.com/"

        def raise_redirect_exception():
            raise errors.RedirectException(apiurl, oauthurl)

        self.assertRaises(errors.RedirectException, raise_redirect_exception)
        redirect_exception = errors.RedirectException(apiurl, oauthurl)
        self.assertEqual(redirect_exception.message, str(redirect_exception))

    @betamax()
    def test_scope_history(self):
        self.r.refresh_access_information(self.refresh_token['history'])
        self.assertTrue(list(self.r.get_redditor(self.un).get_upvoted()))

    @betamax()
    def test_scope_identity(self):
        self.r.refresh_access_information(self.refresh_token['identity'])
        self.assertEqual(self.un, self.r.get_me().name)

    @betamax()
    def test_scope_mysubreddits(self):
        self.r.refresh_access_information(self.refresh_token['mysubreddits'])
        self.assertTrue(list(self.r.get_my_moderation()))

    @betamax()
    def test_scope_creddits(self):
        # Assume there are insufficient creddits.
        self.r.refresh_access_information(self.refresh_token['creddits'])
        redditor = self.r.get_redditor('bboe')
        sub = self.r.get_submission(url=self.comment_url)

        # Test error conditions
        self.assertRaises(TypeError, sub.gild, months=1)
        for value in (False, 0, -1, '0', '-1'):
            self.assertRaises(TypeError, redditor.gild, value)

        # Test object gilding
        self.assertRaises(errors.InsufficientCreddits, redditor.gild)
        self.assertRaises(errors.InsufficientCreddits, sub.gild)
        self.assertRaises(errors.InsufficientCreddits, sub.comments[0].gild)

    @betamax()
    def test_scope_privatemessages(self):
        self.r.refresh_access_information(
            self.refresh_token['privatemessages'])
        self.assertTrue(list(self.r.get_inbox()))

    @betamax()
    def test_scope_read(self):
        self.r.refresh_access_information(self.refresh_token['read'])
        self.assertTrue(self.r.get_subreddit(self.priv_sr).subscribers > 0)
        fullname = '{0}_{1}'.format(self.r.config.by_object[Submission],
                                    self.priv_submission_id)
        method1 = self.r.get_info(thing_id=fullname)
        method2 = self.r.get_submission(submission_id=self.priv_submission_id)
        self.assertEqual(method1, method2)

    @betamax()
    def test_scope_read_get_front_page(self):
        self.r.refresh_access_information(self.refresh_token['mysubreddits'])
        subscribed = list(self.r.get_my_subreddits(limit=None))
        self.r.refresh_access_information(self.refresh_token['read'])
        for post in self.r.get_front_page():
            self.assertTrue(post.subreddit in subscribed)

    @betamax()
    def test_set_access_credentials(self):
        self.assertTrue(self.r.user is None)
        result = self.r.refresh_access_information(
            self.refresh_token['identity'], update_session=False)
        self.assertTrue(self.r.user is None)
        self.r.set_access_credentials(**result)
        self.assertFalse(self.r.user is None)

    @betamax()
    def test_solve_captcha(self):
        # Use the alternate account because it has low karma,
        # so we can test the captcha.
        self.r.refresh_access_information(self.other_refresh_token['submit'])
        original_stdin = sys.stdin
        sys.stdin = FakeStdin('ljgtoo')  # Comment this line when rebuilding
        self.r.submit(self.sr, 'captcha test', 'body')
        sys.stdin = original_stdin

    @betamax()
    def test_oauth_without_identy_doesnt_set_user(self):
        self.assertTrue(self.r.user is None)
        self.r.refresh_access_information(self.refresh_token['edit'])
        self.assertTrue(self.r.user is None)
示例#5
0
class OAuth2RedditTest(PRAWTest):
    def setUp(self):
        self.configure()
        self.r = Reddit(USER_AGENT, site_name='reddit_oauth_test',
                        disable_update_check=True)

    def test_authorize_url(self):
        self.r.set_oauth_app_info(None, None, None)
        self.assertRaises(errors.OAuthAppRequired, self.r.get_authorize_url,
                          'dummy_state')
        self.r.set_oauth_app_info(self.r.config.client_id,
                                  self.r.config.client_secret,
                                  self.r.config.redirect_uri)
        url, params = self.r.get_authorize_url('...').split('?', 1)
        self.assertTrue('api/v1/authorize/' in url)
        params = dict(x.split('=', 1) for x in params.split('&'))
        expected = {'client_id': self.r.config.client_id,
                    'duration': 'temporary',
                    'redirect_uri': ('https%3A%2F%2F127.0.0.1%3A65010%2F'
                                     'authorize_callback'),
                    'response_type': 'code', 'scope': 'identity',
                    'state': '...'}
        self.assertEqual(expected, params)

    # @betamax() is currently broken for this test because the cassettes
    # are caching too aggressively and not performing a token refresh.
    def test_auto_refresh_token(self):
        self.r.refresh_access_information(self.refresh_token['identity'])
        old_token = self.r.access_token

        self.r.access_token += 'x'  # break the token
        self.r.user.refresh()
        current_token = self.r.access_token
        self.assertNotEqual(old_token, current_token)

        self.r.user.refresh()
        self.assertEqual(current_token, self.r.access_token)

    @betamax()
    def test_get_access_information(self):
        # If this test fails, the following URL will need to be visted in order
        # to obtain a new code to pass to `get_access_information`:
        # self.r.get_authorize_url('...')
        token = self.r.get_access_information('MQALrr1di8GzcnT8szbTWhLcBUQ')
        expected = {'access_token': self.r.access_token,
                    'refresh_token': None,
                    'scope': set(('identity',))}
        self.assertEqual(expected, token)
        self.assertEqual('PyAPITestUser2', text_type(self.r.user))

    @betamax()
    def test_get_access_information_with_invalid_code(self):
        self.assertRaises(errors.OAuthInvalidGrant,
                          self.r.get_access_information, 'invalid_code')

    def test_invalid_app_access_token(self):
        self.r.clear_authentication()
        self.r.set_oauth_app_info(None, None, None)
        self.assertRaises(errors.OAuthAppRequired,
                          self.r.get_access_information, 'dummy_code')

    def test_invalid_app_authorize_url(self):
        self.r.clear_authentication()
        self.r.set_oauth_app_info(None, None, None)
        self.assertRaises(errors.OAuthAppRequired,
                          self.r.get_authorize_url, 'dummy_state')

    @betamax()
    def test_invalid_set_access_credentials(self):
        self.assertRaises(errors.OAuthInvalidToken,
                          self.r.set_access_credentials,
                          set(('identity',)), 'dummy_access_token')

    def test_oauth_scope_required(self):
        self.r.set_oauth_app_info('dummy_client', 'dummy_secret', 'dummy_url')
        self.r.set_access_credentials(set('dummy_scope',), 'dummy_token')
        self.assertRaises(errors.OAuthScopeRequired, self.r.get_me)

    def test_raise_client_exception(self):
        def raise_client_exception(*args):
            raise errors.ClientException(*args)

        self.assertRaises(errors.ClientException, raise_client_exception)
        self.assertRaises(errors.ClientException, raise_client_exception,
                          'test')

        ce_message = errors.ClientException('Test')
        ce_no_message = errors.ClientException()
        self.assertEqual(ce_message.message, str(ce_message))
        self.assertEqual(ce_no_message.message, str(ce_no_message))

    def test_raise_http_exception(self):
        def raise_http_exception():
            raise errors.HTTPException('fakeraw')

        self.assertRaises(errors.HTTPException, raise_http_exception)
        http_exception = errors.HTTPException('fakeraw')
        self.assertEqual(http_exception.message, str(http_exception))

    def test_raise_oauth_exception(self):
        oerrormessage = "fakemessage"
        oerrorurl = "http://oauth.reddit.com/"

        def raise_oauth_exception():
            raise errors.OAuthException(oerrormessage, oerrorurl)

        self.assertRaises(errors.OAuthException, raise_oauth_exception)
        oauth_exception = errors.OAuthException(oerrormessage, oerrorurl)
        self.assertEqual(oauth_exception.message +
                         " on url {0}".format(oauth_exception.url),
                         str(oauth_exception))

    def test_raise_redirect_exception(self):
        apiurl = "http://api.reddit.com/"
        oauthurl = "http://oauth.reddit.com/"

        def raise_redirect_exception():
            raise errors.RedirectException(apiurl, oauthurl)

        self.assertRaises(errors.RedirectException, raise_redirect_exception)
        redirect_exception = errors.RedirectException(apiurl, oauthurl)
        self.assertEqual(redirect_exception.message, str(redirect_exception))

    @betamax()
    def test_scope_history(self):
        self.r.refresh_access_information(self.refresh_token['history'])
        self.assertTrue(list(self.r.get_redditor(self.un).get_upvoted()))

    @betamax()
    def test_scope_identity(self):
        self.r.refresh_access_information(self.refresh_token['identity'])
        self.assertEqual(self.un, self.r.get_me().name)

    @betamax()
    def test_scope_mysubreddits(self):
        self.r.refresh_access_information(self.refresh_token['mysubreddits'])
        self.assertTrue(list(self.r.get_my_moderation()))

    @betamax()
    def test_scope_creddits(self):
        # Assume there are insufficient creddits.
        self.r.refresh_access_information(
            self.refresh_token['creddits'])
        redditor = self.r.get_redditor('bboe')
        sub = self.r.get_submission(url=self.comment_url)

        # Test error conditions
        self.assertRaises(TypeError, sub.gild, months=1)
        for value in (False, 0, -1, '0', '-1'):
            self.assertRaises(TypeError, redditor.gild, value)

        # Test object gilding
        self.assertRaises(errors.InsufficientCreddits, redditor.gild)
        self.assertRaises(errors.InsufficientCreddits, sub.gild)
        self.assertRaises(errors.InsufficientCreddits, sub.comments[0].gild)

    @betamax()
    def test_scope_privatemessages(self):
        self.r.refresh_access_information(
            self.refresh_token['privatemessages'])
        self.assertTrue(list(self.r.get_inbox()))

    @betamax()
    def test_scope_read(self):
        self.r.refresh_access_information(self.refresh_token['read'])
        self.assertTrue(self.r.get_subreddit(self.priv_sr).subscribers > 0)
        fullname = '{0}_{1}'.format(self.r.config.by_object[Submission],
                                    self.priv_submission_id)
        method1 = self.r.get_info(thing_id=fullname)
        method2 = self.r.get_submission(submission_id=self.priv_submission_id)
        self.assertEqual(method1, method2)

    @betamax()
    def test_scope_read_get_front_page(self):
        self.r.refresh_access_information(self.refresh_token['mysubreddits'])
        subscribed = list(self.r.get_my_subreddits(limit=None))
        self.r.refresh_access_information(self.refresh_token['read'])
        for post in self.r.get_front_page():
            self.assertTrue(post.subreddit in subscribed)

    @betamax()
    def test_set_access_credentials(self):
        self.assertTrue(self.r.user is None)
        result = self.r.refresh_access_information(
            self.refresh_token['identity'], update_session=False)
        self.assertTrue(self.r.user is None)
        self.r.set_access_credentials(**result)
        self.assertFalse(self.r.user is None)

    @betamax()
    def test_solve_captcha(self):
        # Use the alternate account because it has low karma,
        # so we can test the captcha.
        self.r.refresh_access_information(self.other_refresh_token['submit'])
        original_stdin = sys.stdin
        sys.stdin = FakeStdin('ljgtoo')  # Comment this line when rebuilding
        self.r.submit(self.sr, 'captcha test', 'body')
        sys.stdin = original_stdin

    @betamax()
    def test_oauth_without_identy_doesnt_set_user(self):
        self.assertTrue(self.r.user is None)
        self.r.refresh_access_information(self.refresh_token['edit'])
        self.assertTrue(self.r.user is None)
示例#6
0
class OAuth2RedditTest(PRAWTest):
    def setUp(self):
        self.configure()
        site_name = (os.getenv('REDDIT_SITE') or 'reddit') + '_oauth_test'
        self.r = Reddit(USER_AGENT,
                        site_name=site_name,
                        disable_update_check=True)

    def test_authorize_url(self):
        self.r.set_oauth_app_info(None, None, None)
        self.assertRaises(errors.OAuthAppRequired, self.r.get_authorize_url,
                          'dummy_state')
        self.r.set_oauth_app_info(self.r.config.client_id,
                                  self.r.config.client_secret,
                                  self.r.config.redirect_uri)
        url, params = self.r.get_authorize_url('...').split('?', 1)
        self.assertTrue('api/v1/authorize/' in url)
        params = dict(x.split('=', 1) for x in params.split('&'))
        expected = {
            'client_id':
            self.r.config.client_id,
            'duration':
            'temporary',
            'redirect_uri': ('https%3A%2F%2F127.0.0.1%3A65010%2F'
                             'authorize_callback'),
            'response_type':
            'code',
            'scope':
            'identity',
            'state':
            '...'
        }
        self.assertEqual(expected, params)

    @betamax
    def test_get_access_information(self):
        # If this test fails, the following URL will need to be visted in order
        # to obtain a new code to pass to `get_access_information`:
        # self.r.get_authorize_url('...')
        token = self.r.get_access_information('MQALrr1di8GzcnT8szbTWhLcBUQ')
        expected = {
            'access_token': self.r.access_token,
            'refresh_token': None,
            'scope': set(('identity', ))
        }
        self.assertEqual(expected, token)
        self.assertEqual('PyAPITestUser2', text_type(self.r.user))

    @betamax
    def test_get_access_information_with_invalid_code(self):
        self.assertRaises(errors.OAuthInvalidGrant,
                          self.r.get_access_information, 'invalid_code')

    def test_invalid_app_access_token(self):
        self.r.clear_authentication()
        self.r.set_oauth_app_info(None, None, None)
        self.assertRaises(errors.OAuthAppRequired,
                          self.r.get_access_information, 'dummy_code')

    def test_invalid_app_authorize_url(self):
        self.r.clear_authentication()
        self.r.set_oauth_app_info(None, None, None)
        self.assertRaises(errors.OAuthAppRequired, self.r.get_authorize_url,
                          'dummy_state')

    @betamax
    def test_invalid_set_access_credentials(self):
        self.assertRaises(errors.OAuthInvalidToken,
                          self.r.set_access_credentials, set(
                              ('identity', )), 'dummy_access_token')

    def test_oauth_scope_required(self):
        self.r.set_oauth_app_info('dummy_client', 'dummy_secret', 'dummy_url')
        self.r.set_access_credentials(set('dummy_scope', ), 'dummy_token')
        self.assertRaises(errors.OAuthScopeRequired, self.r.get_me)

    @betamax
    def test_scope_edit(self):
        self.r.refresh_access_information(self.refresh_token['edit'])
        submission = Submission.from_id(self.r, self.submission_edit_id)
        self.assertEqual(submission, submission.edit('Edited text'))

    @betamax
    def test_scope_history(self):
        self.r.refresh_access_information(self.refresh_token['history'])
        self.assertTrue(list(self.r.get_redditor(self.un).get_liked()))

    @betamax
    def test_scope_identity(self):
        self.r.refresh_access_information(self.refresh_token['identity'])
        self.assertEqual(self.un, self.r.get_me().name)

    @betamax
    def test_scope_modconfig(self):
        self.r.refresh_access_information(self.refresh_token['modconfig'])
        self.r.get_subreddit(self.sr).set_settings('foobar')
        retval = self.r.get_subreddit(self.sr).get_stylesheet()
        self.assertTrue('images' in retval)

    @betamax
    def test_scope_modflair(self):
        self.r.refresh_access_information(self.refresh_token['modflair'])
        self.r.get_subreddit(self.sr).set_flair(self.un, 'foobar')

    @betamax
    def test_scope_modlog(self):
        num = 50
        self.r.refresh_access_information(self.refresh_token['modlog'])
        result = self.r.get_subreddit(self.sr).get_mod_log(limit=num)
        self.assertEqual(num, len(list(result)))

    @betamax
    def test_scope_modposts(self):
        self.r.refresh_access_information(self.refresh_token['modposts'])
        Submission.from_id(self.r, self.submission_edit_id).remove()

    @betamax
    def test_scope_mysubreddits(self):
        self.r.refresh_access_information(self.refresh_token['mysubreddits'])
        self.assertTrue(list(self.r.get_my_moderation()))

    @betamax
    def test_scope_creddits(self):
        # Assume there are insufficient creddits.
        self.r.refresh_access_information(self.refresh_token['creddits'])
        redditor = self.r.get_redditor('bboe')
        sub = self.r.get_submission(url=self.comment_url)

        # Test error conditions
        self.assertRaises(TypeError, sub.gild, months=1)
        for value in (False, 0, -1, '0', '-1'):
            self.assertRaises(TypeError, redditor.gild, value)

        # Test object gilding
        self.assertRaises(errors.InsufficientCreddits, redditor.gild)
        self.assertRaises(errors.InsufficientCreddits, sub.gild)
        self.assertRaises(errors.InsufficientCreddits, sub.comments[0].gild)

    @betamax
    def test_scope_privatemessages(self):
        self.r.refresh_access_information(
            self.refresh_token['privatemessages'])
        self.assertTrue(list(self.r.get_inbox()))

    @betamax
    def test_scope_read(self):
        self.r.refresh_access_information(self.refresh_token['read'])
        self.assertTrue(self.r.get_subreddit(self.priv_sr).subscribers > 0)
        fullname = '{0}_{1}'.format(self.r.config.by_object[Submission],
                                    self.priv_submission_id)
        method1 = self.r.get_info(thing_id=fullname)
        method2 = self.r.get_submission(submission_id=self.priv_submission_id)
        self.assertEqual(method1, method2)

    @betamax
    def test_scope_read_get_front_page(self):
        self.r.refresh_access_information(self.refresh_token['mysubreddits'])
        subscribed = list(self.r.get_my_subreddits(limit=None))
        self.r.refresh_access_information(self.refresh_token['read'])
        for post in self.r.get_front_page():
            self.assertTrue(post.subreddit in subscribed)

    @betamax
    def test_scope_read_get_sub_listingr(self):
        self.r.refresh_access_information(self.refresh_token['read'])
        subreddit = self.r.get_subreddit(self.priv_sr)
        self.assertTrue(list(subreddit.get_top()))

    @betamax
    def test_scope_read_get_submission_by_url(self):
        url = ("https://www.reddit.com/r/reddit_api_test_priv/comments/16kbb7/"
               "google/")
        self.r.refresh_access_information(self.refresh_token['read'])
        submission = Submission.from_url(self.r, url)
        self.assertTrue(submission.num_comments != 0)

    @betamax
    def test_scope_read_priv_sr_comments(self):
        self.r.refresh_access_information(self.refresh_token['read'])
        self.assertTrue(list(self.r.get_comments(self.priv_sr)))

    @betamax
    def test_scope_read_priv_sub_comments(self):
        self.r.refresh_access_information(self.refresh_token['read'])
        submission = Submission.from_id(self.r, self.priv_submission_id)
        self.assertTrue(submission.comments)

    @betamax
    def test_scope_submit(self):
        self.r.refresh_access_information(self.refresh_token['submit'])
        result = self.r.submit(self.sr, 'OAuth Submit', text='Foo')
        self.assertTrue(isinstance(result, Submission))

    @betamax
    def test_scope_subscribe(self):
        self.r.refresh_access_information(self.refresh_token['subscribe'])
        self.r.get_subreddit(self.sr).subscribe()

    @betamax
    def test_scope_vote(self):
        self.r.refresh_access_information(self.refresh_token['vote'])
        submission = Submission.from_id(self.r, self.submission_edit_id)
        submission.clear_vote()

    @betamax
    def test_set_access_credentials(self):
        self.assertTrue(self.r.user is None)
        result = self.r.refresh_access_information(
            self.refresh_token['identity'], update_session=False)
        self.assertTrue(self.r.user is None)
        self.r.set_access_credentials(**result)
        self.assertFalse(self.r.user is None)

    @betamax
    def test_oauth_without_identy_doesnt_set_user(self):
        self.assertTrue(self.r.user is None)
        self.r.refresh_access_information(self.refresh_token['edit'])
        self.assertTrue(self.r.user is None)
示例#7
0
class OAuth2RedditTest(PRAWTest):
    def setUp(self):
        self.configure()
        self.r = Reddit(USER_AGENT, site_name="reddit_oauth_test", disable_update_check=True)

    def test_authorize_url(self):
        self.r.set_oauth_app_info(None, None, None)
        self.assertRaises(errors.OAuthAppRequired, self.r.get_authorize_url, "dummy_state")
        self.r.set_oauth_app_info(self.r.config.client_id, self.r.config.client_secret, self.r.config.redirect_uri)
        url, params = self.r.get_authorize_url("...").split("?", 1)
        self.assertTrue("api/v1/authorize/" in url)
        params = dict(x.split("=", 1) for x in params.split("&"))
        expected = {
            "client_id": self.r.config.client_id,
            "duration": "temporary",
            "redirect_uri": ("https%3A%2F%2F127.0.0.1%3A65010%2F" "authorize_callback"),
            "response_type": "code",
            "scope": "identity",
            "state": "...",
        }
        self.assertEqual(expected, params)

    # @betamax() is currently broken for this test
    def test_auto_refresh_token(self):
        self.r.refresh_access_information(self.refresh_token["identity"])
        old_token = self.r.access_token

        self.r.access_token += "x"  # break the token
        self.r.user.refresh()
        current_token = self.r.access_token
        self.assertNotEqual(old_token, current_token)

        self.r.user.refresh()
        self.assertEqual(current_token, self.r.access_token)

    @betamax()
    def test_get_access_information(self):
        # If this test fails, the following URL will need to be visted in order
        # to obtain a new code to pass to `get_access_information`:
        # self.r.get_authorize_url('...')
        token = self.r.get_access_information("MQALrr1di8GzcnT8szbTWhLcBUQ")
        expected = {"access_token": self.r.access_token, "refresh_token": None, "scope": set(("identity",))}
        self.assertEqual(expected, token)
        self.assertEqual("PyAPITestUser2", text_type(self.r.user))

    @betamax()
    def test_get_access_information_with_invalid_code(self):
        self.assertRaises(errors.OAuthInvalidGrant, self.r.get_access_information, "invalid_code")

    def test_invalid_app_access_token(self):
        self.r.clear_authentication()
        self.r.set_oauth_app_info(None, None, None)
        self.assertRaises(errors.OAuthAppRequired, self.r.get_access_information, "dummy_code")

    def test_invalid_app_authorize_url(self):
        self.r.clear_authentication()
        self.r.set_oauth_app_info(None, None, None)
        self.assertRaises(errors.OAuthAppRequired, self.r.get_authorize_url, "dummy_state")

    @betamax()
    def test_invalid_set_access_credentials(self):
        self.assertRaises(
            errors.OAuthInvalidToken, self.r.set_access_credentials, set(("identity",)), "dummy_access_token"
        )

    def test_oauth_scope_required(self):
        self.r.set_oauth_app_info("dummy_client", "dummy_secret", "dummy_url")
        self.r.set_access_credentials(set("dummy_scope"), "dummy_token")
        self.assertRaises(errors.OAuthScopeRequired, self.r.get_me)

    @betamax()
    def test_scope_edit(self):
        self.r.refresh_access_information(self.refresh_token["edit"])
        submission = Submission.from_id(self.r, self.submission_edit_id)
        self.assertEqual(submission, submission.edit("Edited text"))

    @betamax()
    def test_scope_history(self):
        self.r.refresh_access_information(self.refresh_token["history"])
        self.assertTrue(list(self.r.get_redditor(self.un).get_upvoted()))

    @betamax()
    def test_scope_identity(self):
        self.r.refresh_access_information(self.refresh_token["identity"])
        self.assertEqual(self.un, self.r.get_me().name)

    @betamax()
    def test_scope_modconfig(self):
        self.r.refresh_access_information(self.refresh_token["modconfig"])
        self.r.get_subreddit(self.sr).set_settings("foobar")
        retval = self.r.get_subreddit(self.sr).get_stylesheet()
        self.assertTrue("images" in retval)

    @betamax()
    def test_scope_modflair(self):
        self.r.refresh_access_information(self.refresh_token["modflair"])
        self.r.get_subreddit(self.sr).set_flair(self.un, "foobar")

    @betamax()
    def test_scope_modlog(self):
        num = 50
        self.r.refresh_access_information(self.refresh_token["modlog"])
        result = self.r.get_subreddit(self.sr).get_mod_log(limit=num)
        self.assertEqual(num, len(list(result)))

    @betamax()
    def test_scope_modothers_modself(self):
        subreddit = self.r.get_subreddit(self.sr)
        self.r.refresh_access_information(self.refresh_token["modothers"])
        subreddit.add_moderator(self.other_user_name)

        # log in as other user
        self.r.refresh_access_information(self.other_refresh_token["modself"])
        self.r.accept_moderator_invite(self.sr)

        # now return to original user.
        self.r.refresh_access_information(self.refresh_token["modothers"])
        subreddit.remove_moderator(self.other_user_name)

    @betamax()
    def test_scope_modposts(self):
        self.r.refresh_access_information(self.refresh_token["modposts"])
        Submission.from_id(self.r, self.submission_edit_id).remove()

    @betamax()
    def test_scope_modself(self):
        subreddit = self.r.get_subreddit(self.sr)
        self.r.refresh_access_information(self.refresh_token["modothers"])
        subreddit.add_moderator(self.other_user_name)
        self.r.refresh_access_information(self.refresh_token["modcontributors"])
        subreddit.add_contributor(self.other_user_name)

        # log in as other user
        self.r.refresh_access_information(self.other_refresh_token["modself"])
        self.r.accept_moderator_invite(self.sr)

        self.r.leave_moderator(subreddit)
        subreddit.leave_contributor()

        subreddit.refresh()
        self.assertFalse(subreddit.user_is_moderator)
        self.assertFalse(subreddit.user_is_contributor)

    @betamax()
    def test_scope_mysubreddits(self):
        self.r.refresh_access_information(self.refresh_token["mysubreddits"])
        self.assertTrue(list(self.r.get_my_moderation()))

    @betamax()
    def test_scope_modwiki(self):
        self.r.refresh_access_information(self.refresh_token["modwiki"])
        subreddit = self.r.get_subreddit(self.sr)
        page = subreddit.get_wiki_page("index")
        page.add_editor(self.other_user_name)
        page.remove_editor(self.other_user_name)

    @betamax()
    def test_scope_modwiki_modcontributors(self):
        self.r.refresh_access_information(self.refresh_token["modwiki+contr"])
        subreddit = self.r.get_subreddit(self.sr)

        subreddit.add_wiki_ban(self.other_user_name)
        subreddit.remove_wiki_ban(self.other_user_name)

        subreddit.add_wiki_contributor(self.other_user_name)
        subreddit.remove_wiki_contributor(self.other_user_name)

    @betamax()
    def test_scope_creddits(self):
        # Assume there are insufficient creddits.
        self.r.refresh_access_information(self.refresh_token["creddits"])
        redditor = self.r.get_redditor("bboe")
        sub = self.r.get_submission(url=self.comment_url)

        # Test error conditions
        self.assertRaises(TypeError, sub.gild, months=1)
        for value in (False, 0, -1, "0", "-1"):
            self.assertRaises(TypeError, redditor.gild, value)

        # Test object gilding
        self.assertRaises(errors.InsufficientCreddits, redditor.gild)
        self.assertRaises(errors.InsufficientCreddits, sub.gild)
        self.assertRaises(errors.InsufficientCreddits, sub.comments[0].gild)

    @betamax()
    def test_scope_privatemessages(self):
        self.r.refresh_access_information(self.refresh_token["privatemessages"])
        self.assertTrue(list(self.r.get_inbox()))

    @betamax()
    def test_scope_read(self):
        self.r.refresh_access_information(self.refresh_token["read"])
        self.assertTrue(self.r.get_subreddit(self.priv_sr).subscribers > 0)
        fullname = "{0}_{1}".format(self.r.config.by_object[Submission], self.priv_submission_id)
        method1 = self.r.get_info(thing_id=fullname)
        method2 = self.r.get_submission(submission_id=self.priv_submission_id)
        self.assertEqual(method1, method2)

    @betamax()
    def test_scope_read_get_front_page(self):
        self.r.refresh_access_information(self.refresh_token["mysubreddits"])
        subscribed = list(self.r.get_my_subreddits(limit=None))
        self.r.refresh_access_information(self.refresh_token["read"])
        for post in self.r.get_front_page():
            self.assertTrue(post.subreddit in subscribed)

    @betamax()
    def test_scope_read_get_sub_listingr(self):
        self.r.refresh_access_information(self.refresh_token["read"])
        subreddit = self.r.get_subreddit(self.priv_sr)
        self.assertTrue(list(subreddit.get_top()))

    @betamax()
    def test_scope_read_get_submission_by_url(self):
        url = "https://www.reddit.com/r/reddit_api_test_priv/comments/16kbb7/" "google/"
        self.r.refresh_access_information(self.refresh_token["read"])
        submission = Submission.from_url(self.r, url)
        self.assertTrue(submission.num_comments != 0)

    @betamax()
    def test_scope_read_priv_sr_comments(self):
        self.r.refresh_access_information(self.refresh_token["read"])
        self.assertTrue(list(self.r.get_comments(self.priv_sr)))

    @betamax()
    def test_scope_wikiread_wiki_page(self):
        self.r.refresh_access_information(self.refresh_token["wikiread"])
        self.assertTrue(self.r.get_wiki_page(self.sr, "index"))

    @betamax()
    def test_scope_read_priv_sub_comments(self):
        self.r.refresh_access_information(self.refresh_token["read"])
        submission = Submission.from_id(self.r, self.priv_submission_id)
        self.assertTrue(submission.comments)

    @betamax()
    def test_scope_submit(self):
        self.r.refresh_access_information(self.refresh_token["submit"])
        result = self.r.submit(self.sr, "OAuth Submit", text="Foo")
        self.assertTrue(isinstance(result, Submission))

    @betamax()
    def test_scope_subscribe(self):
        self.r.refresh_access_information(self.refresh_token["subscribe"])
        self.r.get_subreddit(self.sr).subscribe()

    @betamax()
    def test_scope_vote(self):
        self.r.refresh_access_information(self.refresh_token["vote"])
        submission = Submission.from_id(self.r, self.submission_edit_id)
        submission.clear_vote()

    @betamax()
    def test_set_access_credentials(self):
        self.assertTrue(self.r.user is None)
        result = self.r.refresh_access_information(self.refresh_token["identity"], update_session=False)
        self.assertTrue(self.r.user is None)
        self.r.set_access_credentials(**result)
        self.assertFalse(self.r.user is None)

    @betamax()
    def test_oauth_without_identy_doesnt_set_user(self):
        self.assertTrue(self.r.user is None)
        self.r.refresh_access_information(self.refresh_token["edit"])
        self.assertTrue(self.r.user is None)
示例#8
0
class SubRedditStats(object):
    post_prefix = tt('Subreddit Stats:')
    post_header = tt('---\n###{0}\n')
    post_footer = tt('>Generated with [BBoe](/u/bboe)\'s [Subreddit Stats]'
                     '(https://github.com/praw-dev/prawtools)  \n{0}'
                     'SRS Marker: {1}')
    re_marker = re.compile('SRS Marker: (\d+)')

    @staticmethod
    def _previous_max(submission):
        try:
            val = SubRedditStats.re_marker.findall(submission.selftext)[-1]
            return float(val)
        except (IndexError, TypeError):
            print('End marker not found in previous submission. Aborting')
            sys.exit(1)

    @staticmethod
    def _permalink(permalink):
        tokens = permalink.split('/')
        if tokens[8] == '':  # submission
            return tt('/comments/{0}/_/').format(tokens[6])
        else:  # comment
            return tt('/comments/{0}/_/{1}?context=1').format(tokens[6],
                                                              tokens[8])

    @staticmethod
    def _user(user):
        if user is None:
            return '_deleted_'
        elif isinstance(user, Redditor):
            user = str(user)
        return tt('[{0}](/user/{1})').format(user.replace('_', '\_'), user)

    @staticmethod
    def _submit(func, *args, **kwargs):
        def sleep(sleep_time):
            print('\tSleeping for {0} seconds'.format(sleep_time))
            time.sleep(sleep_time)

        while True:
            try:
                return func(*args, **kwargs)
            except RateLimitExceeded as error:
                sleep(error.sleep_time)
            except ExceptionList as exception_list:
                for error in exception_list.errors:
                    if isinstance(error, RateLimitExceeded):
                        sleep(error.sleep_time)
                        break
                else:
                    raise

    def __init__(self, subreddit, site, verbosity):
        self.reddit = Reddit(str(self), site)
        self.subreddit = self.reddit.get_subreddit(subreddit)
        self.verbosity = verbosity
        self.submissions = []
        self.comments = []
        self.submitters = defaultdict(list)
        self.commenters = defaultdict(list)
        self.min_date = 0
        self.max_date = time.time() - DAYS_IN_SECONDS * 3
        self.prev_srs = None
        # Config
        self.reddit.config.comment_limit = -1  # Fetch max comments possible
        self.reddit.config.comment_sort = 'top'

    def login(self, user, pswd):
        if self.verbosity > 0:
            print('Logging in')
        self.reddit.login(user, pswd)

    def msg(self, msg, level, overwrite=False):
        if self.verbosity >= level:
            sys.stdout.write(msg)
            if overwrite:
                sys.stdout.write('\r')
                sys.stdout.flush()
            else:
                sys.stdout.write('\n')

    def prev_stat(self, prev_url):
        submission = self.reddit.get_submission(prev_url)
        self.min_date = self._previous_max(submission)
        self.prev_srs = prev_url

    def fetch_recent_submissions(self, max_duration, after, exclude_self,
                                 since_last=True):
        '''Fetches recent submissions in subreddit with boundaries.

        Does not include posts within the last three days as their scores may
        not be representative.

        Keyword arguments:
        max_duration -- When set, specifies the number of days to include
        after -- When set, fetch all submission after this submission id.
        exclude_self -- When true, don't include self posts.
        since_last -- When true use info from last submission to determine the
                      stop point
        '''
        if max_duration:
            self.min_date = self.max_date - DAYS_IN_SECONDS * max_duration
        params = {'after': after} if after else None
        self.msg('DEBUG: Fetching submissions', 1)
        for submission in self.subreddit.get_new_by_date(limit=None,
                                                         params=params):
            if submission.created_utc > self.max_date:
                continue
            if submission.created_utc <= self.min_date:
                break
            if (since_last and str(submission.author) == str(self.reddit.user)
                and submission.title.startswith(self.post_prefix)):
                # Use info in this post to update the min_date
                # And don't include this post
                self.msg(tt('Found previous: {0}')
                         .format(safe_title(submission)), 2)
                if self.prev_srs is None:  # Only use the most recent
                    self.min_date = max(self.min_date,
                                        self._previous_max(submission))
                    self.prev_srs = submission.permalink
                continue
            if exclude_self and submission.is_self:
                continue
            self.submissions.append(submission)
        num_submissions = len(self.submissions)
        self.msg('DEBUG: Found {0} submissions'.format(num_submissions), 1)
        if num_submissions == 0:
            return False

        # Update real min and max dates
        self.submissions.sort(key=lambda x: x.created_utc)
        self.min_date = self.submissions[0].created_utc
        self.max_date = self.submissions[-1].created_utc
        return True

    def fetch_top_submissions(self, top, exclude_self):
        '''Fetches top 1000 submissions by some top value.

        Keyword arguments:
        top -- One of week, month, year, all
        exclude_self -- When true, don't include self posts.
        '''
        if top not in ('day', 'week', 'month', 'year', 'all'):
            raise TypeError('{0!r} is not a valid top value'.format(top))
        self.msg('DEBUG: Fetching submissions', 1)
        params = {'t': top}
        for submission in self.subreddit.get_top(limit=None, params=params):
            if exclude_self and submission.is_self:
                continue
            self.submissions.append(submission)
        num_submissions = len(self.submissions)
        self.msg('DEBUG: Found {0} submissions'.format(num_submissions), 1)
        if num_submissions == 0:
            return False

        # Update real min and max dates
        self.submissions.sort(key=lambda x: x.created_utc)
        self.min_date = self.submissions[0].created_utc
        self.max_date = self.submissions[-1].created_utc
        return True

    def process_submitters(self):
        self.msg('DEBUG: Processing Submitters', 1)
        for submission in self.submissions:
            if submission.author:
                self.submitters[str(submission.author)].append(submission)

    def process_commenters(self):
        num = len(self.submissions)
        self.msg('DEBUG: Processing Commenters on {0} submissions'.format(num),
                 1)
        for i, submission in enumerate(self.submissions):
            self.msg('{0}/{1} submissions'.format(i + 1, num), 2,
                     overwrite=True)
            if submission.num_comments == 0:
                continue
            try:
                self.comments.extend(submission.all_comments_flat)
            except Exception as exception:
                print('Exception fetching comments on {0!r}: {1}'.format(
                        submission.content_id, str(exception)))
            for orphans in itervalues(submission._orphaned):
                self.comments.extend(orphans)
        for comment in self.comments:
            if comment.author:
                self.commenters[str(comment.author)].append(comment)

    def basic_stats(self):
        sub_ups = sum(x.ups for x in self.submissions)
        sub_downs = sum(x.downs for x in self.submissions)
        comm_ups = sum(x.ups for x in self.comments)
        comm_downs = sum(x.downs for x in self.comments)

        if sub_ups > 0 or sub_downs > 0:
            sub_up_perc = sub_ups * 100 / (sub_ups + sub_downs)
        else:
            sub_up_perc = 100
        if comm_ups > 0 or comm_downs > 0:
            comm_up_perc = comm_ups * 100 / (comm_ups + comm_downs)
        else:
            comm_up_perc = 100

        values = [('Total', len(self.submissions), '', len(self.comments), ''),
                  ('Unique Redditors', len(self.submitters), '',
                   len(self.commenters), ''),
                  ('Upvotes', sub_ups, '{0}%'.format(sub_up_perc),
                   comm_ups, '{0}%'.format(comm_up_perc)),
                  ('Downvotes', sub_downs, '{0}%'.format(100 - sub_up_perc),
                   comm_downs, '{0}%'.format(100 - comm_up_perc))]

        retval = '||Submissions|%|Comments|%|\n:-:|--:|--:|--:|--:\n'
        for quad in values:
            retval += '__{0}__|{1}|{2}|{3}|{4}\n'.format(*quad)
        return retval + '\n'

    def top_submitters(self, num, num_submissions):
        num = min(num, len(self.submitters))
        if num <= 0:
            return ''

        top_submitters = sorted(iteritems(self.submitters), reverse=True,
                                key=lambda x: (sum(y.score for y in x[1]),
                                               len(x[1])))[:num]

        retval = self.post_header.format('Top Submitters\' Top Submissions')
        for (author, submissions) in top_submitters:
            retval += '0. {0} pts, {1} submissions: {2}\n'.format(
                sum(x.score for x in submissions), len(submissions),
                self._user(author))
            for sub in sorted(submissions, reverse=True,
                              key=lambda x: x.score)[:num_submissions]:
                title = safe_title(sub)
                if sub.permalink != sub.url:
                    retval += tt('  0. [{0}]({1})').format(title, sub.url)
                else:
                    retval += tt('  0. {0}').format(title)
                retval += ' ({0} pts, [{1} comments]({2}))\n'.format(
                    sub.score, sub.num_comments,
                    self._permalink(sub.permalink))
            retval += '\n'
        return retval

    def top_commenters(self, num):
        score = lambda x: x.ups - x.downs

        num = min(num, len(self.commenters))
        if num <= 0:
            return ''

        top_commenters = sorted(iteritems(self.commenters), reverse=True,
                                key=lambda x: (sum(score(y) for y in x[1]),
                                               len(x[1])))[:num]

        retval = self.post_header.format('Top Commenters')
        for author, comments in top_commenters:
            retval += '0. {0} ({1} pts, {2} comments)\n'.format(
                self._user(author), sum(score(x) for x in comments),
                len(comments))
        return '{0}\n'.format(retval)

    def top_submissions(self, num):
        num = min(num, len(self.submissions))
        if num <= 0:
            return ''

        top_submissions = sorted(self.submissions, reverse=True,
                                 key=lambda x: x.score)[:num]

        retval = self.post_header.format('Top Submissions')
        for sub in top_submissions:
            title = safe_title(sub)
            if sub.permalink != sub.url:
                retval += tt('0. [{0}]({1})').format(title, sub.url)
            else:
                retval += tt('0. {0}').format(title)
            retval += ' by {0} ({1} pts, [{2} comments]({3}))\n'.format(
                self._user(sub.author), sub.score, sub.num_comments,
                self._permalink(sub.permalink))
        return tt('{0}\n').format(retval)

    def top_comments(self, num):
        score = lambda x: x.ups - x.downs

        num = min(num, len(self.comments))
        if num <= 0:
            return ''

        top_comments = sorted(self.comments, reverse=True,
                              key=score)[:num]
        retval = self.post_header.format('Top Comments')
        for comment in top_comments:
            title = safe_title(comment.submission)
            retval += tt('0. {0} pts: {1}\'s [comment]({2}) in {3}\n').format(
                score(comment), self._user(comment.author),
                self._permalink(comment.permalink), title)
        return tt('{0}\n').format(retval)

    def publish_results(self, subreddit, submitters, commenters, submissions,
                        comments, top, debug=False):
        def timef(timestamp, date_only=False):
            dtime = datetime.fromtimestamp(timestamp)
            if date_only:
                retval = dtime.strftime('%Y-%m-%d')
            else:
                retval = dtime.strftime('%Y-%m-%d %H:%M PDT')
            return retval

        if self.prev_srs:
            prev = '[Prev SRS]({0})  \n'.format(self._permalink(self.prev_srs))
        else:
            prev = ''

        basic = self.basic_stats()
        t_commenters = self.top_commenters(commenters)
        t_submissions = self.top_submissions(submissions)
        t_comments = self.top_comments(comments)
        footer = self.post_footer.format(prev, self.max_date)

        body = ''
        num_submissions = 10
        while body == '' or len(body) > MAX_BODY_SIZE and num_submissions > 2:
            t_submitters = self.top_submitters(submitters, num_submissions)
            body = (basic + t_submitters + t_commenters + t_submissions +
                    t_comments + footer)
            num_submissions -= 1

        if len(body) > MAX_BODY_SIZE:
            print('The resulting message is too big. Not submitting.')
            debug = True

        # Set the initial title
        base_title = '{0} {1} {2}posts from {3} to {4}'.format(
            self.post_prefix, str(self.subreddit),
            'top ' if top else '', timef(self.min_date, True),
            timef(self.max_date))

        submitted = False
        while not debug and not submitted:
            if subreddit:  # Verify the user wants to submit to the subreddit
                msg = ('You are about to submit to subreddit {0!r} as {1!r}.\n'
                       'Are you sure? yes/[no]: '.format(
                        subreddit, str(self.reddit.user)))
                sys.stdout.write(msg)
                sys.stdout.flush()
                if sys.stdin.readline().strip().lower() not in ['y', 'yes']:
                    subreddit = None
            elif not subreddit:  # Prompt for the subreddit to submit to
                msg = ('Please enter a subreddit to submit to (press return to'
                       ' abort): ')
                sys.stdout.write(msg)
                sys.stdout.flush()
                subreddit = sys.stdin.readline().strip()
                if not subreddit:
                    print('Submission aborted\n')
                    debug = True

            # Vary the title depending on where posting
            if str(self.subreddit) == subreddit:
                title = '{0} {1}posts from {2} to {3}'.format(
                    self.post_prefix, 'top ' if top else '',
                    timef(self.min_date, True), timef(self.max_date))
            else:
                title = base_title

            if subreddit:
                # Attempt to make the submission
                try:
                    res = self._submit(self.reddit.submit, subreddit, title,
                                       text=body)
                    print(res.permalink)
                    submitted = True
                except Exception as error:
                    raise
                    print('The submission failed:' + str(error))
                    subreddit = None

        if not submitted:
            print(base_title)
            print(body)
示例#9
0
class SubRedditStats(object):
    """Contain all the functionality of the subreddit_stats command."""

    post_prefix = tt('Subreddit Stats:')
    post_header = tt('---\n###{0}\n')
    post_footer = tt('>Generated with [BBoe](/u/bboe)\'s [Subreddit Stats]'
                     '(https://github.com/praw-dev/prawtools)  \n{0}'
                     'SRS Marker: {1}')
    re_marker = re.compile(r'SRS Marker: (\d+)')

    @staticmethod
    def _previous_max(submission):
        try:
            val = SubRedditStats.re_marker.findall(submission.selftext)[-1]
            return float(val)
        except (IndexError, TypeError):
            print('End marker not found in previous submission. Aborting')
            sys.exit(1)

    @staticmethod
    def _permalink(permalink):
        tokens = permalink.split('/')
        if tokens[8] == '':  # submission
            return tt('/comments/{0}/_/').format(tokens[6])
        else:  # comment
            return tt('/comments/{0}/_/{1}?context=1').format(
                tokens[6], tokens[8])

    @staticmethod
    def _pts(points):
        return '1 pt' if points == 1 else '{0} pts'.format(points)

    @staticmethod
    def _user(user):
        if user is None:
            return '_deleted_'
        elif isinstance(user, Redditor):
            user = str(user)
        return tt('[{0}](/user/{1})').format(user.replace('_', r'\_'), user)

    @staticmethod
    def _submit(func, *args, **kwargs):
        def sleep(sleep_time):
            print('\tSleeping for {0} seconds'.format(sleep_time))
            time.sleep(sleep_time)

        while True:
            try:
                return func(*args, **kwargs)
            except RateLimitExceeded as error:
                sleep(error.sleep_time)
            except ExceptionList as exception_list:
                for error in exception_list.errors:
                    if isinstance(error, RateLimitExceeded):
                        sleep(error.sleep_time)
                        break
                else:
                    raise

    def __init__(self, subreddit, site, verbosity, distinguished):
        """Initialize the SubRedditStats instance with config options."""
        self.reddit = Reddit(str(self), site, disable_update_check=True)
        self.subreddit = self.reddit.get_subreddit(subreddit)
        self.verbosity = verbosity
        self.distinguished = distinguished
        self.submissions = []
        self.comments = []
        self.submitters = defaultdict(list)
        self.commenters = defaultdict(list)
        self.min_date = 0
        self.max_date = time.time() - DAYS_IN_SECONDS * 3
        self.prev_srs = None

    def login(self, user, pswd):
        """Login and provide debugging output if so wanted."""
        if self.verbosity > 0:
            print('Logging in')
        self.reddit.login(user, pswd)

    def msg(self, msg, level, overwrite=False):
        """Output a messaage to the screen if the verbosity is sufficient."""
        if self.verbosity and self.verbosity >= level:
            sys.stdout.write(msg)
            if overwrite:
                sys.stdout.write('\r')
                sys.stdout.flush()
            else:
                sys.stdout.write('\n')

    def prev_stat(self, prev_url):
        """Load the previous subreddit stats page."""
        submission = self.reddit.get_submission(prev_url)
        self.min_date = self._previous_max(submission)
        self.prev_srs = prev_url

    def fetch_recent_submissions(self,
                                 max_duration,
                                 after,
                                 exclude_self,
                                 exclude_link,
                                 since_last=True):
        """Fetch recent submissions in subreddit with boundaries.

        Does not include posts within the last three days as their scores may
        not be representative.

        :param max_duration: When set, specifies the number of days to include
        :param after: When set, fetch all submission after this submission id.
        :param exclude_self: When true, don't include self posts.
        :param exclude_link:  When true, don't include links.
        :param since_last: When true use info from last submission to determine
            the stop point
        :returns: True if any submissions were found.

        """
        if exclude_self and exclude_link:
            raise TypeError('Cannot set both exclude_self and exclude_link.')
        if max_duration:
            self.min_date = self.max_date - DAYS_IN_SECONDS * max_duration
        params = {'after': after} if after else None
        self.msg('DEBUG: Fetching submissions', 1)
        for submission in self.subreddit.get_new(limit=None, params=params):
            if submission.created_utc > self.max_date:
                continue
            if submission.created_utc <= self.min_date:
                break
            if since_last and str(submission.author) == str(self.reddit.user) \
                    and submission.title.startswith(self.post_prefix):
                # Use info in this post to update the min_date
                # And don't include this post
                self.msg(
                    tt('Found previous: {0}').format(safe_title(submission)),
                    2)
                if self.prev_srs is None:  # Only use the most recent
                    self.min_date = max(self.min_date,
                                        self._previous_max(submission))
                    self.prev_srs = submission.permalink
                continue
            if exclude_self and submission.is_self:
                continue
            if exclude_link and not submission.is_self:
                continue
            self.submissions.append(submission)
        num_submissions = len(self.submissions)
        self.msg('DEBUG: Found {0} submissions'.format(num_submissions), 1)
        if num_submissions == 0:
            return False

        # Update real min and max dates
        self.submissions.sort(key=lambda x: x.created_utc)
        self.min_date = self.submissions[0].created_utc
        self.max_date = self.submissions[-1].created_utc
        return True

    def fetch_top_submissions(self, top, exclude_self, exclude_link):
        """Fetch top 1000 submissions by some top value.

        :param top: One of week, month, year, all
        :param exclude_self: When true, don't include self posts.
        :param exclude_link: When true, include only self posts
        :returns: True if any submissions were found.

        """
        if exclude_self and exclude_link:
            raise TypeError('Cannot set both exclude_self and exclude_link.')
        if top not in ('day', 'week', 'month', 'year', 'all'):
            raise TypeError('{0!r} is not a valid top value'.format(top))
        self.msg('DEBUG: Fetching submissions', 1)
        params = {'t': top}
        for submission in self.subreddit.get_top(limit=None, params=params):
            if exclude_self and submission.is_self:
                continue
            if exclude_link and not submission.is_self:
                continue
            self.submissions.append(submission)
        num_submissions = len(self.submissions)
        self.msg('DEBUG: Found {0} submissions'.format(num_submissions), 1)
        if num_submissions == 0:
            return False

        # Update real min and max dates
        self.submissions.sort(key=lambda x: x.created_utc)
        self.min_date = self.submissions[0].created_utc
        self.max_date = self.submissions[-1].created_utc
        return True

    def process_submitters(self):
        """Group submissions by author."""
        self.msg('DEBUG: Processing Submitters', 1)
        for submission in self.submissions:
            if submission.author and (self.distinguished
                                      or submission.distinguished is None):
                self.submitters[str(submission.author)].append(submission)

    def process_commenters(self):
        """Group comments by author."""
        num = len(self.submissions)
        self.msg('DEBUG: Processing Commenters on {0} submissions'.format(num),
                 1)
        for i, submission in enumerate(self.submissions):
            # Explicitly fetch as many comments as possible by top sort
            # Note that this is the first time the complete submission object
            # is obtained. Only a partial object was returned when getting the
            # subreddit listings.
            try:
                submission = self.reddit.get_submission(submission.permalink,
                                                        comment_limit=None,
                                                        comment_sort='top')
            except HTTPError as exc:
                print('Ignoring comments on {0} due to HTTP status {1}'.format(
                    submission.url, exc.response.status_code))
                continue
            self.msg('{0}/{1} submissions'.format(i + 1, num),
                     2,
                     overwrite=True)
            if submission.num_comments == 0:
                continue
            skipped = submission.replace_more_comments()
            if skipped:
                skip_num = sum(x.count for x in skipped)
                print('Ignored {0} comments ({1} MoreComment objects)'.format(
                    skip_num, len(skipped)))
            comments = [
                x for x in flatten_tree(submission.comments)
                if self.distinguished or x.distinguished is None
            ]
            self.comments.extend(comments)
            # pylint: disable=W0212
            for orphans in itervalues(submission._orphaned):
                self.comments.extend(orphans)
            # pylint: enable=W0212
        for comment in self.comments:
            if comment.author:
                self.commenters[str(comment.author)].append(comment)

    def basic_stats(self):
        """Return a markdown representation of simple statistics."""
        sub_score = sum(x.score for x in self.submissions)
        comm_score = sum(x.score for x in self.comments)
        sub_duration = self.max_date - self.min_date
        sub_rate = (86400. * len(self.submissions) /
                    sub_duration if sub_duration else len(self.submissions))

        # Compute comment rate
        if self.comments:
            self.comments.sort(key=lambda x: x.created_utc)
            duration = (self.comments[-1].created_utc -
                        self.comments[0].created_utc)
            comm_rate = (86400. * len(self.comments) /
                         duration if duration else len(self.comments))
        else:
            comm_rate = 0

        values = [('Total', len(self.submissions), len(self.comments)),
                  ('Rate (per day)', '{0:.2f}'.format(sub_rate),
                   '{0:.2f}'.format(comm_rate)),
                  ('Unique Redditors', len(self.submitters),
                   len(self.commenters)),
                  ('Combined Score', sub_score, comm_score)]

        retval = 'Period: {0:.2f} days\n\n'.format(sub_duration / 86400.)
        retval += '||Submissions|Comments|\n:-:|--:|--:\n'
        for quad in values:
            # pylint: disable=W0142
            retval += '__{0}__|{1}|{2}\n'.format(*quad)
            # pylint: enable=W0142
        return retval + '\n'

    def top_submitters(self, num, num_submissions):
        """Return a markdown representation of the top submitters."""
        num = min(num, len(self.submitters))
        if num <= 0:
            return ''

        top_submitters = sorted(iteritems(self.submitters),
                                reverse=True,
                                key=lambda x:
                                (sum(y.score for y in x[1]), len(x[1])))[:num]

        retval = self.post_header.format('Top Submitters\' Top Submissions')
        for (author, submissions) in top_submitters:
            retval += '0. {0}, {1} submission{2}: {3}\n'.format(
                self._pts(sum(x.score for x in submissions)), len(submissions),
                's' if len(submissions) > 1 else '', self._user(author))
            for sub in sorted(submissions, reverse=True,
                              key=lambda x: x.score)[:num_submissions]:
                title = safe_title(sub)
                if sub.permalink != sub.url:
                    retval += tt('  0. [{0}]({1})').format(title, sub.url)
                else:
                    retval += tt('  0. {0}').format(title)
                retval += ' ({0}, [{1} comment{2}]({3}))\n'.format(
                    self._pts(sub.score), sub.num_comments,
                    's' if sub.num_comments > 1 else '',
                    self._permalink(sub.permalink))
            retval += '\n'
        return retval

    def top_commenters(self, num):
        """Return a markdown representation of the top commenters."""
        score = lambda x: x.score

        num = min(num, len(self.commenters))
        if num <= 0:
            return ''

        top_commenters = sorted(iteritems(self.commenters),
                                reverse=True,
                                key=lambda x:
                                (sum(score(y) for y in x[1]), len(x[1])))[:num]

        retval = self.post_header.format('Top Commenters')
        for author, comments in top_commenters:
            retval += '0. {0} ({1}, {2} comment{3})\n'.format(
                self._user(author), self._pts(sum(score(x) for x in comments)),
                len(comments), 's' if len(comments) > 1 else '')
        return '{0}\n'.format(retval)

    def top_submissions(self, num):
        """Return a markdown representation of the top submissions."""
        num = min(num, len(self.submissions))
        if num <= 0:
            return ''

        top_submissions = sorted([
            x for x in self.submissions
            if self.distinguished or x.distinguished is None
        ],
                                 reverse=True,
                                 key=lambda x: x.score)[:num]

        if not top_submissions:
            return ''

        retval = self.post_header.format('Top Submissions')
        for sub in top_submissions:
            title = safe_title(sub)
            if sub.permalink != sub.url:
                retval += tt('0. [{0}]({1})').format(title, sub.url)
            else:
                retval += tt('0. {0}').format(title)
            retval += ' by {0} ({1}, [{2} comment{3}]({4}))\n'.format(
                self._user(sub.author), self._pts(sub.score), sub.num_comments,
                's' if sub.num_comments > 1 else '',
                self._permalink(sub.permalink))
        return tt('{0}\n').format(retval)

    def top_comments(self, num):
        """Return a markdown representation of the top comments."""
        score = lambda x: x.score

        num = min(num, len(self.comments))
        if num <= 0:
            return ''

        top_comments = sorted(self.comments, reverse=True, key=score)[:num]
        retval = self.post_header.format('Top Comments')
        for comment in top_comments:
            title = safe_title(comment.submission)
            retval += tt('0. {0}: {1}\'s [comment]({2}) in {3}\n').format(
                self._pts(score(comment)), self._user(comment.author),
                self._permalink(comment.permalink), title)
        return tt('{0}\n').format(retval)

    def publish_results(self,
                        subreddit,
                        submitters,
                        commenters,
                        submissions,
                        comments,
                        top,
                        debug=False):
        """Submit the results to the subreddit. Has no return value (None)."""
        def timef(timestamp, date_only=False):
            """Return a suitable string representaation of the timestamp."""
            dtime = datetime.fromtimestamp(timestamp)
            if date_only:
                retval = dtime.strftime('%Y-%m-%d')
            else:
                retval = dtime.strftime('%Y-%m-%d %H:%M PDT')
            return retval

        if self.prev_srs:
            prev = '[Prev SRS]({0})  \n'.format(self._permalink(self.prev_srs))
        else:
            prev = ''

        basic = self.basic_stats()
        t_commenters = self.top_commenters(commenters)
        t_submissions = self.top_submissions(submissions)
        t_comments = self.top_comments(comments)
        footer = self.post_footer.format(prev, self.max_date)

        body = ''
        num_submissions = 10
        while body == '' or len(body) > MAX_BODY_SIZE and num_submissions > 2:
            t_submitters = self.top_submitters(submitters, num_submissions)
            body = (basic + t_submitters + t_commenters + t_submissions +
                    t_comments + footer)
            num_submissions -= 1

        if len(body) > MAX_BODY_SIZE:
            print('The resulting message is too big. Not submitting.')
            debug = True

        # Set the initial title
        base_title = '{0} {1} {2}posts from {3} to {4}'.format(
            self.post_prefix, str(self.subreddit), 'top ' if top else '',
            timef(self.min_date, True), timef(self.max_date))

        submitted = False
        while not debug and not submitted:
            if subreddit:  # Verify the user wants to submit to the subreddit
                msg = ('You are about to submit to subreddit {0!r} as {1!r}.\n'
                       'Are you sure? yes/[no]: '.format(
                           subreddit, str(self.reddit.user)))
                sys.stdout.write(msg)
                sys.stdout.flush()
                if sys.stdin.readline().strip().lower() not in ['y', 'yes']:
                    subreddit = None
            elif not subreddit:  # Prompt for the subreddit to submit to
                msg = ('Please enter a subreddit to submit to (press return to'
                       ' abort): ')
                sys.stdout.write(msg)
                sys.stdout.flush()
                subreddit = sys.stdin.readline().strip()
                if not subreddit:
                    print('Submission aborted\n')
                    debug = True

            # Vary the title depending on where posting
            if str(self.subreddit) == subreddit:
                title = '{0} {1}posts from {2} to {3}'.format(
                    self.post_prefix, 'top ' if top else '',
                    timef(self.min_date, True), timef(self.max_date))
            else:
                title = base_title

            if subreddit:
                # Attempt to make the submission
                try:
                    res = self._submit(self.reddit.submit,
                                       subreddit,
                                       title,
                                       text=body)
                    print(res.permalink)
                    submitted = True
                except Exception as error:  # pylint: disable=W0703
                    print('The submission failed:' + str(error))
                    subreddit = None

        if not submitted:
            print(base_title)
            print(body)

    def save_csv(self, filename):
        """Create csv file containing comments and submissions by author."""
        redditors = set(self.submitters.keys()).union(self.commenters.keys())
        mapping = dict((x.lower(), x) for x in redditors)
        with codecs.open(filename, 'w', encoding='utf-8') as outfile:
            outfile.write('username, type, permalink, score\n')
            for _, redditor in sorted(mapping.items()):
                for submission in self.submitters.get(redditor, []):
                    outfile.write(u'{0}, submission, {1}, {2}\n'.format(
                        redditor, submission.permalink, submission.score))
                for comment in self.commenters.get(redditor, []):
                    outfile.write(u'{0}, comment, {1}, {2}\n'.format(
                        redditor, comment.permalink, comment.score))
示例#10
0
    error("Couldn't login to reddit: {0}".format(e))


# create DB session
engine = create_engine(DB_PATH)
Base.metadata.bind = engine
DBSession = sessionmaker(bind=engine)
session = DBSession()


if __name__ == '__main__':
    request_session = requests.session()

    for post in session.query(Post).all():
        # load reddit post
        submission = reddit.get_submission(submission_id=post.reddit_post_id)

        # load lepra post
        debug("Fetching leprosorium post #{0}...".format(post.lepra_post_id))
        lepra_url = "http://auto.leprosorium.ru/comments/{0}".format(post.lepra_post_id)
        response = request_session.get(lepra_url, cookies=LEPROSORIUM_COOKIES)
        bs_tree = BeautifulSoup(response.text)

        # parse lepra post

        debug("Parsing comments...")
        # for comment in comments
        for node in bs_tree.findAll('div', attrs={'class': 'post', 'class': 'indent_0'}):
            comment_id = int(node['id'])
            debug("Parsing comment #{0}:".format(comment_id))
            content = node.find('div', attrs={'class': 'dt'})