Пример #1
0
	def update_reddit(self):
		#Initialize PRAW and login
		r = Reddit(user_agent='/r/reddevils bot by /u/Hubwub')
		r.login(self.username,self.password)
		#Grab the current settings
		settings = r.get_subreddit(self.subreddit).get_settings()
		#Update the sidebar
		#print sidebar
		settings['description'] = sidebar
		settings = r.get_subreddit(self.subreddit).update_settings(description=settings['description'])
	def reset_image_pool(self, num_pictures):
		""" Retrieves the links to all the images that we want. """

		reddit = Reddit(user_agent=PROGRAM_NAME)
		images_downloaded = 0
		sr_dict = { 
			sr_name : reddit.get_subreddit(sr_name).get_top() 
			for sr_name in self.subreddit_pool 
		}

		# randomly pick a number of pictures from the subreddits.
		# skip the ones that aren't actually pictures.
		while images_downloaded < num_pictures:
			subreddit = random.choice([key for key in sr_dict.keys()])

			try:
				submission = next(sr_dict[subreddit])
			except StopIteration:

				# remove subreddits which run out of content
				del sr_dict[subreddit]

				# quit if we've run out of subreddits
				if len(sr_dict.keys()) > 0: continue;
				else: break;

			link = submission.url
			if utils.is_image_link(link):
				print('({num_pics} of {total_pics}) Image found at {url}. Downloading...'.format(num_pics=images_downloaded+1, total_pics=num_pictures, url=link))
				self.download_image(link, self.get_file_name(link))
				images_downloaded += 1
Пример #3
0
class GenericRedditLink:
	def __init__(self,parent):
		self.r=Reddit(user_agent="PollBotBestBot")
		self.parent=parent
		
	def __call__(self,msg):
		if msg["body"].lower().startswith("!r/"):
			m=msg["body"].lstrip("!r/")
			spli=m.split(":")
			subreddit=spli[0]
			print subreddit
			if subreddit in config.banned_subreddits:
				self.parent.scheduler.add("punishment:"+msg["mucnick"],0.1,self.parent.punishment,args=(msg["mucnick"],),repeat=True)
				self.parent.send_message(mto=self.parent.channel,mbody="Nope, not touching that.",mtype="groupchat")
				return
					
			body=self.get_hot(subreddit,msg)
			if body!=None:
				self.parent.send_message(mto=self.parent.channel,mbody=body,mtype="groupchat")
		
		if msg["body"].lower().startswith("!block ") and msg["mucnick"] in config.admins:
			m=m.spli(" ")
			subreddit=spli[1]
			config.banned_subreddits.append(subreddit)
			
	def get_hot(self,subreddit,msg):
		if msg["type"]=="groupchat":
			subreddit=self.r.get_subreddit(subreddit)
			try:
				if subreddit.over18:
					pass
			except (HTTPError, errors.InvalidSubreddit) as E:
				self.parent.send_message(mto=self.parent.channel,mbody="Learn to Reddit please, "+msg["mucnick"],mtype="groupchat")
				return None
				
			if subreddit.over18:
				#self.parent.send_message(mto=self.parent.channel,mbody="NSFW content is currently blocked. Direct complaints to mods and admins.",mtype="groupchat")
				extra=" :nws: "
			else:
				extra=""
				
			submissions=subreddit.get_hot(limit=10)
			a=None
			a1=None
			limit=random.randint(0,9)
			while limit>0:
				a1=a
				print a1
				try:
					a=next(submissions)
				except StopIteration:
					a=a1
					break
				print a
				limit-=1
			
			try:
				return "\n"+extra+str(a.title)+extra+"\n"+extra+str(a.url)+extra
			except AttributeError:
				return "Reddit API is currently not accepting connections. Please wait ~30 seconds before retrying."
Пример #4
0
def reddit_titles(sub):
    from praw import Reddit
    r = Reddit(user_agent="my_useragent")
    news = r.get_subreddit(sub).get_hot(limit=10)
    news = list(news)
    #num = 3
    for i in news:
        print(i.title)
Пример #5
0
def reddit_titles(sub):
    from praw import Reddit
    r = Reddit(user_agent="my_useragent")
    news = r.get_subreddit(sub).get_hot(limit=10)
    news = list(news)
    #num = 3
    for i in news:
        print(i.title)
Пример #6
0
def reddit_parser():
    args = build_reddit_parser().parse_args()
    db_connection = create_connection(args.db_host, args.db_password)
    reddit = Reddit(user_agent=args.user_agent)
    subreddit = reddit.get_subreddit(args.subreddit)
    if args.username:
        password = getpass("Enter reddit password: ")
        reddit.login(args.username, password)
    single_threaded_impl(subreddit, db_connection, args.sub_limit, args.method)
Пример #7
0
def ban_users(reddit: praw.Reddit, users):
    """
    Ban the users on reddit.
    """
    print("Updating DB objects...")
    # First pass, update database objects.
    for user, subs in users.items():
        if user in config.EXEMPT_USERS:
            # Ignore these guys. They're cool/bots.
            continue
        if not db.session.query(exists().where(db.BannedUser.username == user)).scalar():
            # Update.
            u = db.BannedUser(username=user, reason="AutoCensor automatic ban - Subreddits: {}".format(",").join(subs))
            db.session.add(u)
    # Commit session.
    db.session.commit()

    print("Building many-to-many entries...")
    # Second pass, check for users and subreddits, and update many-to-many entires.
    for user in users:
        if user in config.EXEMPT_USERS:
            # Ignore these guys. They're cool/bots.
            continue
        try:
            u_object = db.session.query(db.BannedUser).filter(db.BannedUser.username == user).one()
        except sqlalchemy.orm.exc.NoResultFound:
            continue
        for managing_sub in db.session.query(db.ManagingSubreddit).all():
            if u_object not in managing_sub.users:
                managing_sub.users.append(u_object)
            db.session.add(managing_sub)
    db.session.commit()

    print("Banning users...")

    banned = 0

    # Last pass, ban them on our subreddits.
    for subobject in db.session.query(db.ManagingSubreddit).all():
        # Get the subreddit.
        sub = reddit.get_subreddit(subobject.name)
        # Get the users.
        for user in subobject.users:
            # Begin the bans!
            try:
                sub.add_ban(
                    user.username,
                    ban_reason="Automatically banned for posting in these subreddits: {}".format(user.reason),
                    note="Automatically banned for posting in these subreddits: {}".format(user.reason),
                )
                banned += 1
            except:
                # whatever
                continue
    return banned
Пример #8
0
class Snitch:
    def __init__(self, username, passwd, url, subreddit):
        self.rh = Reddit('Release the Snitch v 0.1 by Kolpa')
        self.rh.login(username, passwd)

        self.css = self.__load_css()
        self.desc = self.__load_desc()

        self.url = url
        self.subreddit = subreddit

    def __load_css(self):
        with open('raw.css', 'r') as f:
            return f.read()

    def __load_desc(self):
        with open('descripiton.txt', 'r') as f:
            return f.read()

    def __get_last_id(self):
        with open('current', 'r') as f:
            return f.read()

    def __set_new_id(self):
        id = randrange(0, 999999)
        with open('current', 'w') as f:
            f.write(str(id))
        return id

    def _get_random_pos(self):
        return randrange(0, 100, 10), randrange(0, 100, 10)

    def __update_desc(self, desc):
        self.rh.update_settings(self.rh.get_subreddit(self.subreddit), description=desc)

    def can_move(self, id):
        if not os.path.exists('current'):
            return True
        return id == self.__get_last_id()

    def move(self, id):
        try:
            if self.can_move(id):
                new_id = self.__set_new_id()
                desc = self.desc.format(new_id)

                x, y = self._get_random_pos()
                css = self.css.format(x, y, self.url)

                self.rh.set_stylesheet(self.subreddit, css)
                self.__update_desc(desc)
                return True
        except Exception:
            return False
Пример #9
0
def main():
    parser = argparse.ArgumentParser(
            prog='modbot.py',
            description="Subreddit automoderating script")
    parser.add_argument('subreddit')
    parser.add_argument('-r', '--rulesdir', default='./rules')
    parser.add_argument('-u', '--user')
    parser.add_argument('-p', '--password')
    args = parser.parse_args()

    rh = RuleHandler(args.rulesdir, '*.rule')

    logging.config.fileConfig('logging.conf')

    reddit = Reddit('%s/%s' % (NAME, VERSION))
    try:
        reddit.login(args.user, args.password)
    except praw.errors.InvalidUserPass:
        logging.critical("Login failure")
        sys.exit(1)

    sub = reddit.get_subreddit(args.subreddit)
    read_thinglists()
    comments_ph = None
    submissions_ph = None

    while True:
        logging.debug("Loop start")
        loopstart = time.time()

        try:
            modqueue_items = sub.get_mod_queue(limit=100)
        except Exception, e:
            modqueue_items = []

        num = 0
        for modqueue_item in modqueue_items:
            num += 1
            matchrules(modqueue_item, rh.rules, is_modqueue=True)
        logging.info("Checked %d modqueue items" % num)

        try:
            if comments_ph == None:
                comments = sub.get_comments(limit=100)
            else:
                comments = sub.get_comments(place_holder=comments_ph,
                        limit=500)
        except Exception, e:
            comments = []
Пример #10
0
def main():
    parser = argparse.ArgumentParser(
        prog='modbot.py', description="Subreddit automoderating script")
    parser.add_argument('subreddit')
    parser.add_argument('-r', '--rulesdir', default='./rules')
    parser.add_argument('-u', '--user')
    parser.add_argument('-p', '--password')
    args = parser.parse_args()

    rh = RuleHandler(args.rulesdir, '*.rule')

    logging.config.fileConfig('logging.conf')

    reddit = Reddit('%s/%s' % (NAME, VERSION))
    try:
        reddit.login(args.user, args.password)
    except praw.errors.InvalidUserPass:
        logging.critical("Login failure")
        sys.exit(1)

    sub = reddit.get_subreddit(args.subreddit)
    read_thinglists()
    comments_ph = None
    submissions_ph = None

    while True:
        logging.debug("Loop start")
        loopstart = time.time()

        try:
            modqueue_items = sub.get_mod_queue(limit=100)
        except Exception, e:
            modqueue_items = []

        num = 0
        for modqueue_item in modqueue_items:
            num += 1
            matchrules(modqueue_item, rh.rules, is_modqueue=True)
        logging.info("Checked %d modqueue items" % num)

        try:
            if comments_ph == None:
                comments = sub.get_comments(limit=100)
            else:
                comments = sub.get_comments(place_holder=comments_ph,
                                            limit=500)
        except Exception, e:
            comments = []
Пример #11
0
def update_countdown(username, password, subreddit_name, target):
    user_agent = '/r/{0} countdown'.format(subreddit_name)
    reddit = Reddit(user_agent)
    reddit.login(username, password, disable_warning=True)

    # reddit access
    subreddit = reddit.get_subreddit(subreddit_name)
    settings = subreddit.get_settings()
    description = HTMLParser().unescape(settings['description'])

    # time matches
    countdown_delta = target - datetime.now()
    days_left = countdown_delta.days
    hours_left = countdown_delta.seconds // 3600

    # regex and strings
    # countdownSearch = re.compile("(\[\d?\d?\]\([#][a-z]*\)\s[a-z]*[!]?\s?)+", re.I)  # old regex
    countdownSearch = re.compile("((\s([a-z]{4})*)*\s?\[\d?\d?\]\([#][a-z]*\)\s[a-z]*[!]?\s?)+", re.I)
    origStr = " less than [](#days) days [](#hours) hours\n"
    noDaysStr = " less than [](#hours) hours\n"
    noHoursStr = " less than [](#minutes) minutes\n"
    raceLiveStr = " [](#days) Racing [](#hours) is [](#minutes) live\n"
    updatemeStr = " [](#days) Current [](#hours) event [](#minutes) ended\n"

    # make sure our event hasn't happened yet
    if target > datetime.now():

        if days_left > 0:
            description = re.sub(countdownSearch, origStr, description)
        elif hours_left > 0 and days_left == 0:
            description = re.sub(countdownSearch, noDaysStr, description)
        else:
            description = re.sub(countdownSearch, noHoursStr, description)

        for key, value in compute_time_ago_params(target).iteritems():
            pattern = "\\[[^\\]]*\\]\\(#{0}\\)".format(key)  # replace [<anything>](#<key>)
            repl = "[{0}](#{1})".format(value, key)  # with [<value>](#<key>)
            description = re.sub(pattern, repl, description)

    # if race is happening, show raceLiveStr, else race is over, show updatemeStr
    else:
        countdownSearch.search(description)
        if target.hour > datetime.now().hour - 3:
            description = re.sub(countdownSearch, raceLiveStr, description)
        else:
            description = re.sub(countdownSearch, updatemeStr, description)

    subreddit.update_settings(description=description)
Пример #12
0
def update_countdown(username, password, subreddit_name, target):
    user_agent = '/r/{0} countdown bot'.format(subreddit_name)
    reddit = Reddit(user_agent)
    reddit.login(username, password)
    
    subreddit = reddit.get_subreddit(subreddit_name)
    settings = subreddit.get_settings()
    description = HTMLParser().unescape(settings['description'])
    
    for key, value in compute_time_ago_params(target).iteritems():
        pattern = "\\[[^\\]]*\\]\\(#{0}\\)".format(key) # replace [<anything>](#<key>)
        repl = "[{0}](#{1})".format(value, key) # with [<value>](#<key>)
        description = re.sub(pattern, repl, description)
    
    print(description)
    subreddit.update_settings(description=description)
Пример #13
0
	def create_sidebar(self):
		h = HTMLParser.HTMLParser()
		#Initialize PRAW and login
		r = Reddit(user_agent='/r/reddevils bot by /u/Hubwub')
		r.login(self.username,self.password)
		#Grab the sidebar template from the wiki
		sidebar = r.get_subreddit(self.subreddit).get_wiki_page('edit_sidebar').content_md
		#Create list from sidebar by splitting at ####
		sidebar_list = sidebar.split('####')
		#Sidebar
		#print sidebar_list
		#sidebar = (sidebar_list[0]+league+sidebar_list[1])
		sidebar = (sidebar_list[0]+league+rfixtures+goals+sidebar_list[1])
		#Fix characters in sidebar
		sidebar = h.unescape(sidebar)
		return sidebar
Пример #14
0
def update_countdown(username, password, subreddit_name, target):
    user_agent = '/r/{0} countdown bot'.format(subreddit_name)
    reddit = Reddit(user_agent)
    reddit.login(username, password)

    subreddit = reddit.get_subreddit(subreddit_name)
    settings = subreddit.get_settings()
    description = HTMLParser().unescape(settings['description'])

    for key, value in compute_time_ago_params(target).iteritems():
        pattern = "\\[[^\\]]*\\]\\(#{0}\\)".format(
            key)  # replace [<anything>](#<key>)
        repl = "[{0}](#{1})".format(value, key)  # with [<value>](#<key>)
        description = re.sub(pattern, repl, description)

    print(description)
    subreddit.update_settings(description=description)
Пример #15
0
 def test_report(self):
     # login as new user to report submission
     oth = Reddit(USER_AGENT)
     oth.login('PyApiTestUser3', '1111')
     subreddit = oth.get_subreddit(self.sr)
     submission = None
     for submission in subreddit.get_new_by_date():
         if not submission.hidden:
             break
     else:
         self.fail('Could not find a non-reported submission.')
     submission.report()
     # check if submission was reported
     for report in self.r.get_subreddit(self.sr).get_reports():
         if report.id == submission.id:
             break
     else:
         self.fail('Could not find reported submission.')
Пример #16
0
def scrape(subreddit_name, backfill_to=None):
    """
    Scrape a subreddit.

    :type subreddit_name: str
    :type backfill_to: datetime.datetime

    """
    subreddit_name = subreddit_name.lower()
    imgur_client = ImgurClient(
        settings.IMGUR_CLIENT_ID, settings.IMGUR_CLIENT_SECRET)
    reddit = Reddit(user_agent=settings.REDDIT_USER_AGENT)
    subreddit = reddit.get_subreddit(subreddit_name)
    with session_manager() as session:
        if backfill_to is not None:
            _backfill(
                session, subreddit, subreddit_name, imgur_client, backfill_to)
        else:
            _scrape(session, subreddit, subreddit_name, imgur_client)
Пример #17
0
def scrape(reddit: praw.Reddit) -> dict:
    """
    Begin a scraping pass.

    Uses the subreddits defined in config.EDGELORD_SUBREDDITS.
    :param reddit: The reddit object to use.
    :return: A dict containing users and a list of subreddits.
    """
    banning = {}
    for sub in config.EDGELORD_SUBREDDITS:
        # Get sub.
        print("Scraping subreddit {}".format(sub))
        try:
            subreddit = reddit.get_subreddit(sub)
            hot_posts = subreddit.get_hot(limit=30)
            for i, post in enumerate(hot_posts):
                print("{} - [{}/30] - Scraping comments of".format(sub, i + 1), post)
                # Get submitter name.
                assert isinstance(post, praw.objects.Submission)
                # TODO: Figure out way to get submitters name.
                # Otherwise, loop over comments, plucking the submitter off.
                for comment in praw.helpers.flatten_tree(post.comments):
                    if isinstance(comment, praw.objects.MoreComments):
                        # TODO: Add MoreComments parsing.
                        continue
                    if should_ban(comment):
                        if comment.author is not None:
                            print("{} - [{}/30] - Adding to ban list: {}".format(sub, i + 1, comment.author))
                            if not comment.author.name in banning:
                                banning[comment.author.name] = []
                            if sub in banning[comment.author.name]:
                                continue
                            else:
                                banning[comment.author.name].append(sub)
        except Exception as e:
            print("Error happened - {}".format(e))
            print("Skipping to next sub.")

    return banning
Пример #18
0
 def random_reddit_image(self, caption=None, bot=None, update=None):
     """
     Find and send a random image from a random subreddit containing
     images.
     """
     if not (bot and update):
         return
     bot.send_chat_action(update.message.chat.id)
     # choose a random subreddit to pull image from
     subreddit = choice(self.subreddits)
     # grab submissions from the subreddit
     reddit = Reddit(user_agent=self.reddit_user_agent)
     submissions = reddit.get_subreddit(subreddit).get_hot(limit=50)
     # skip non-imgur links, animated images, and choose a random submission
     submission = choice([sub.url for sub in submissions
                          if 'imgur.com' in sub.url])
     # find all the image links in the submission, and choose a random one
     try:
         image_url = choice(get_image_links_from_imgur(submission))
     except (IndexError, ValueError):
         # no image found, so try again
         return self.random_reddit_image(caption=caption, bot=bot,
                                         update=update)
     # get the image content
     logger.info('"/{command}" from {user}: posting image at {url}'.format(
         command=' '.join([update.command] + update.command_args),
         user=update.message.user.username,
         url=image_url,
     ))
     response = requests.get(image_url)
     if response.status_code != 200:
         bot.send_message(update.message.chat.id, self.error_message)
         return
     # image_content = make_thumbnail(response.content)
     image_content = response.content
     bot.send_photo(update.message.chat.id, image_content,
                    reply_to_message_id=update.message.id,
                    caption=image_url)
Пример #19
0
def verify(moderator_username, moderator_password,
           username, subreddit,
           flair_text=None, flair_css=None):
    api = Reddit(user_agent=settings.USER_AGENT)
    try:
        api.login(moderator_username, moderator_password)
        subreddit = api.get_subreddit(subreddit)
        subreddit.add_contributor(username)
        api.set_flair(subreddit, username, flair_text, flair_css)
    except praw.errors.RateLimitExceeded:
        raise RateLimitExceededException
    except praw.errors.ModeratorRequired:
        raise ModeratorRequiredException
    except praw.errors.InvalidUserPass:
        raise InvalidLoginException
    except praw.errors.BadCSS:
        raise InvalidCSSException
    except praw.errors.InvalidUser:
        raise InvalidUserException
    except praw.errors.APIException as e:
        raise RedditAPIException(e)
    except praw.errors.ClientException as e:
        raise RedditAPIException(e)
Пример #20
0
class RedditBot(_RedditBotBase):
    """
    Basic extendable Reddit bot.

    Provides means to loop over a list of whitelisted subreddits.

    """
    VERSION = (0, 0, 0)  # override this
    USER_AGENT = '{name} v{version} (by /u/{admin})'

    # if loop() returns this the bot will refresh its settings
    BOT_SHOULD_REFRESH = 'BOT_SHOULD_REFRESH'

    def __init__(self, config):
        """
        Initialize the bot with a dict of configuration values.

        """
        self._setup(config)
        self._login(config)

        self.subreddits = self._get_subreddits()
        self.blocked_users = self._get_blocked_users()

    def _setup(self, config):
        try:
            admins = config['admins']
            if isinstance(admins, list):
                self.admins = admins
            else:
                self.admins = list(map(str.strip, admins.split(',')))

            self.settings = DEFAULT_SETTINGS.copy()
            self.settings.update(config.get('settings', {}))
        except KeyError as e:
            import sys
            sys.stderr.write('error: missing {} in configuration'.format(e))
            sys.exit(2)

    def _login(self, config):
        logger.info('Attempting to login using OAuth2')

        for attr in ['client_id', 'client_secret', 'redirect_uri']:
            assert attr in config[
                'oauth_info'], 'Missing `{}` in oauth_info'.format(attr)

        self.r = Reddit('OAuth Login v1.0')
        self.r.set_oauth_app_info(**config['oauth_info'])

        for attr in ['access_token', 'refresh_token']:
            assert attr in config[
                'access_info'], 'Missing `{}` in access_info'.format(attr)
        access_info = config['access_info']
        access_info['scope'] = self.__class__.get_scope()
        self.r.set_access_credentials(**access_info)
        self.bot_name = self.r.user.name
        self.admins.append(self.bot_name)
        user_agent = self.USER_AGENT.format(name=self.bot_name,
                                            admin=self.admins[0],
                                            version='.'.join(
                                                map(str, self.VERSION)))
        logger.debug('User-Agent: {!r}'.format(user_agent))
        self.r.http.headers['User-Agent'] = user_agent
        logger.info('Logged in as {}'.format(self.bot_name))

    @classmethod
    def get_scope(cls):
        """Basic permission scope for RedditReplyBot operations."""
        return super(RedditBot, cls).get_scope() | {
            'identity',
            'subscribe',
            'mysubreddits',
        }

    def run_forever(self):
        self.bot_start()
        try:
            while True:
                self.do_loop()
                self.refresh()
        except Exception as e:
            self.bot_error(e)
            raise
        finally:
            self.bot_stop()

    def refresh(self):
        logger.info('Refreshing settings')
        self.subreddits = self._get_subreddits()
        self.blocked_users = self._get_blocked_users()

    def do_loop(self):
        for subreddit in cycle(self.subreddits):
            try:
                if self.loop(subreddit) == self.BOT_SHOULD_REFRESH:
                    break
            except Forbidden as e:
                logger.error(
                    'Forbidden in {}! Removing from whitelist.'.format(
                        subreddit))
                self.remove_subreddits(subreddit)
                break
            except RateLimitExceeded as e:
                logger.warning(
                    'RateLimitExceeded! Sleeping {} seconds.'.format(
                        e.sleep_time))
                time.sleep(e.sleep_time)
            except (ConnectionError, HTTPException) as e:
                logger.warning(
                    'Error: Reddit down or no connection? {!r}'.format(e))
                time.sleep(self.settings['loop_sleep'] * 10)
            else:
                time.sleep(self.settings['loop_sleep'])
        else:
            logger.error(
                "No subreddits in file. Will read file again in 5 seconds.")
            time.sleep(5)

    def _get_subreddits(self):
        subreddits = list(
            map(lambda s: s.display_name, self.r.get_my_subreddits()))
        logger.info('Subreddits: {} entries'.format(len(subreddits)))
        logger.debug('List: {!r}'.format(subreddits))
        return subreddits

    def _get_blocked_users(self, filename=None):
        """Friends are blocked users, because Reddit only allows blocking
        users by private messages."""
        blocked_users = list(map(lambda u: u.name, self.r.get_friends()))
        logger.info('Blocked users: {} entries'.format(len(blocked_users)))
        logger.debug('List: {!r}'.format(blocked_users))
        return blocked_users

    def is_user_blocked(self, user_name):
        if user_name == self.bot_name:
            return True
        return user_name in self.blocked_users

    def is_subreddit_whitelisted(self, subreddit):
        return subreddit in self.subreddits

    def remove_subreddits(self, *subreddits):
        for sub_name in subreddits:
            if sub_name in self.subreddits:
                self.subreddits.remove(sub_name)
                sub = self.r.get_subreddit(sub_name)
                sub.unsubscribe()
                logger.info('Unsubscribed from /r/{}'.format(sub_name))

    def add_subreddits(self, *subreddits):
        for sub_name in subreddits:
            if sub_name not in self.subreddits:
                self.subreddits.add(sub_name)
                sub = self.r.get_subreddit(sub_name)
                sub.subscribe()
                logger.info('Subscribed to /r/{}'.format(sub_name))

    def block_users(self, *users):
        for user_name in users:
            if user_name not in self.blocked_users:
                self.blocked_users.add(user_name)
                user = self.r.get_redditor(user_name)
                user.friend()
                logger.info('Blocked /u/{}'.format(user_name))

    def unblock_users(self, *users):
        for user_name in users:
            if user_name in self.blocked_users:
                self.blocked_users.remove(user_name)
                user = self.r.get_redditor(user_name)
                user.unfriend()
                logger.info('Unblocked /u/{}'.format(user_name))
Пример #21
0
class Reddit_Cleverbot:

  def __init__(self, username, password, subreddit='all', useragent=USERAGENT):
    self.username = username
    self.password = password
    self.useragent = useragent
    self.subreddit = subreddit
    self.reddit = Reddit(useragent)
    self.reddit.login(username, password)
    self.stopped = True
    self.thread = None
    self.done = set()
    self.conversations = dict()

  def random_hot_comment(self):
    sub = self.reddit.get_subreddit(self.subreddit)
    hot = [post for post in sub.get_hot(limit=25)]
    post = random.choice(hot)
    comments = praw.helpers.flatten_tree(post.comments)
    # filter the comments to remove already-replied ones
    comments = [comment for comment in comments if comment not in self.done and isinstance(comment, praw.objects.Comment)]
    return random.choice(comments[0:100])

  def random_comment(self):
    comments = self.reddit.get_comments(self.subreddit)
    # filter the comments to remove already-replied ones
    comments = [comment for comment in comments if comment not in self.done]
    return random.choice(comments)

  def get_summoned_comments(self):
    comments = self.reddit.get_comments(self.subreddit)
    children = [comment for comment in comments 
      if comment not in self.done and SUMMON in comment.body]
    # print "--> " + str(len(children)) + " summons found!"
    return [self.reddit.get_info(thing_id=comment.parent_id) for comment in children]

  def reply(self, comment):
    if self.reddit.get_info(thing_id=comment.parent_id).author.name == self.username:
      # TODO: handle a threaded conversation over restarts. will need a DB. ugh
      pass
    if comment.parent_id in self.conversations:
      cleverbot = self.conversations[comment.parent_id]
    else:
      cleverbot = Cleverbot()
    response = cleverbot.ask(comment.body)
    post = comment.reply(response)
    self.done.add(comment.id)
    self.conversations[post.id] = copy(cleverbot)

  def reply_unread(self, interval):
    for item in self.reddit.get_unread():
      if item.parent_id not in self.conversations:
        print "Could not find conversation! Ignoring for now."
        pass
      self.reply(item)
      item.mark_as_read()
      time.sleep(interval)

  def reply_to_summons(self):
    summons = self.get_summoned_comments()
    for comment in summons:
      self.reply(comment)

  def _run_random(self, interval):
    while not self.stopped:
      self.reply_unread(interval)
      self.reply(self.random_hot_comment())
      time.sleep(interval)

  def run_random(self, interval):
    self.stopped = False
    self.thread = Thread(target=self._run_random, args=(interval,))
    self.thread.start()

  def stop(self):
    self.stopped = True
    #self.thread.join()
Пример #22
0
# /r/AFL Subreddit Flair Statistics

#!/usr/bin/env python
import sys
from datetime import datetime
from praw import Reddit

user = '******'
password = '******'
srname = 'AFL'
edit_reason = 'Updated by /u/rAFLgamethread bot'

r = Reddit('AFL-flairstats/0.1')
r.login(user, password)
sr = r.get_subreddit(srname)

flair_templates = {
    'adelaide': 'Adelaide',
    'adelaide2': 'Adelaide 2',
    'adelaide3': 'Adelaide 3',
    'adelaide5': 'Adelaide 5',
    'brisbane': 'Brisbane',
    'brisbane2': 'Brisbane 2',
    'brisbane3': 'Brisbane 3',
    'brisbane5': 'Brisbane 5',
    'carlton': 'Carlton',
    'carlton2': 'Carlton 2',
    'carlton3': 'Carlton 3',
    'carlton4': 'Carlton 4',
    'collingwood': 'Collingwood',
    'collingwood2': 'Collingwood 2',
Пример #23
0
def get_reddit_submissions(subreddit='earthporn'):
    reddit = Reddit(user_agent='earthporn_wallpapers_downloader')
    submissions = reddit.get_subreddit(subreddit).get_top_from_week()
    log.info('Fetching submissions from "{0}"'.format(subreddit))
    return submissions
Пример #24
0
class GenericRedditLink:
    def __init__(self, parent):
        self.r = Reddit(user_agent="PollBotBestBot")
        self.parent = parent

    def __call__(self, msg):
        if msg["body"].lower().startswith("!r/"):
            m = msg["body"].lstrip("!r/")
            spli = m.split(":")
            subreddit = spli[0]
            print subreddit
            if subreddit in config.banned_subreddits:
                self.parent.scheduler.add("punishment:" + msg["mucnick"],
                                          0.1,
                                          self.parent.punishment,
                                          args=(msg["mucnick"], ),
                                          repeat=True)
                self.parent.send_message(mto=self.parent.channel,
                                         mbody="Nope, not touching that.",
                                         mtype="groupchat")
                return

            body = self.get_hot(subreddit, msg)
            if body != None:
                self.parent.send_message(mto=self.parent.channel,
                                         mbody=body,
                                         mtype="groupchat")

        if msg["body"].lower().startswith(
                "!block ") and msg["mucnick"] in config.admins:
            m = m.spli(" ")
            subreddit = spli[1]
            config.banned_subreddits.append(subreddit)

    def get_hot(self, subreddit, msg):
        if msg["type"] == "groupchat":
            subreddit = self.r.get_subreddit(subreddit)
            try:
                if subreddit.over18:
                    pass
            except (HTTPError, errors.InvalidSubreddit) as E:
                self.parent.send_message(mto=self.parent.channel,
                                         mbody="Learn to Reddit please, " +
                                         msg["mucnick"],
                                         mtype="groupchat")
                return None

            if subreddit.over18:
                #self.parent.send_message(mto=self.parent.channel,mbody="NSFW content is currently blocked. Direct complaints to mods and admins.",mtype="groupchat")
                extra = " :nws: "
            else:
                extra = ""

            submissions = subreddit.get_hot(limit=10)
            a = None
            a1 = None
            limit = random.randint(0, 9)
            while limit > 0:
                a1 = a
                print a1
                try:
                    a = next(submissions)
                except StopIteration:
                    a = a1
                    break
                print a
                limit -= 1

            try:
                return "\n" + extra + str(
                    a.title) + extra + "\n" + extra + str(a.url) + extra
            except AttributeError:
                return "Reddit API is currently not accepting connections. Please wait ~30 seconds before retrying."
Пример #25
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)
Пример #26
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))
Пример #27
0
def get_reddit_submissions(subreddit='earthporn'):
    reddit = Reddit(user_agent='earthporn_wallpapers_downloader')
    submissions = reddit.get_subreddit(subreddit).get_top_from_week()
    log.info('Fetching submissions from "{0}"'.format(subreddit))
    return submissions
Пример #28
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))
Пример #29
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)
Пример #30
0
 def subreddit(self, value):
     reddit = Reddit('gtmanfred/1.0', cache_timeout=self.interval)
     self['subreddit'] = reddit.get_subreddit(value)
Пример #31
0
 def get_post(self):
     """ Get a post """
     r = Reddit(self.args.name)
     r.login(self.args.username, self.args.password)
     return r.get_subreddit(self.args.subreddit).get_new(limit=1).next()
Пример #32
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)
Пример #33
0
class ModUtils(object):
    """Class that provides all the modutils functionality."""
    def __init__(self,
                 subreddit,
                 site=None,
                 user=None,
                 pswd=None,
                 verbose=None):
        """Initialize the ModUtils class by passing in config options."""
        self.reddit = Reddit(str(self), site, disable_update_check=True)
        self.reddit.config.decode_html_entities = True
        self._logged_in = False
        self._user = user
        self._pswd = pswd
        self.sub = self.reddit.get_subreddit(subreddit)
        self.verbose = verbose
        self._current_flair = None

    def add_users(self, category):
        """Add users to 'banned', 'contributors', or 'moderators'."""
        mapping = {
            'banned': 'ban',
            'contributors': 'make_contributor',
            'moderators': 'make_moderator'
        }

        if category not in mapping:
            print('%r is not a valid option for --add' % category)
            return
        self.login()
        func = getattr(self.sub, mapping[category])
        print('Enter user names (any separation should suffice):')
        data = sys.stdin.read().strip()
        for name in re.split('[^A-Za-z0-9_]+', data):
            func(name)
            print('Added %r to %s' % (name, category))

    def clear_empty(self):
        """Remove flair that is not visible or has been set to empty."""
        for flair in self.current_flair():
            if not flair['flair_text'] and not flair['flair_css_class']:
                print(self.reddit.delete_flair(self.sub, flair['user']))
                print('Removed flair for {0}'.format(flair['user']))

    def current_flair(self):
        """Generate the flair, by user, for the subreddit."""
        if self._current_flair is None:
            self._current_flair = []
            self.login()
            if self.verbose:
                print('Fetching flair list for %s' % self.sub)
            for flair in self.sub.get_flair_list(limit=None):
                self._current_flair.append(flair)
                yield flair
        else:
            for item in self._current_flair:
                yield item

    def flair_template_sync(
            self,
            editable,
            limit,  # pylint: disable=R0912
            static,
            sort,
            use_css,
            use_text):
        """Synchronize templates with flair that already exists on the site.

        :param editable: Indicates that all the options should be editable.
        :param limit: The minimum number of users that must share the flair
            before it is added as a template.
        :param static: A list of flair templates that will always be added.
        :param sort: The order to sort the flair templates.
        :param use_css: Include css in the templates.
        :param use_text: Include text in the templates.

        """
        # Parameter verification
        if not use_text and not use_css:
            raise Exception('At least one of use_text or use_css must be True')
        sorts = ('alpha', 'size')
        if sort not in sorts:
            raise Exception('Sort must be one of: %s' % ', '.join(sorts))

        # Build current flair list along with static values
        counter = {}
        if static:
            for key in static:
                if use_css and use_text:
                    parts = tuple(x.strip() for x in key.split(','))
                    if len(parts) != 2:
                        raise Exception('--static argument %r must have two '
                                        'parts (comma separated) when using '
                                        'both text and css.' % parts)
                    key = parts
                counter[key] = limit
        self.login()
        if self.verbose:
            sys.stdout.write('Retrieving current flair\n')
            sys.stdout.flush()
        for flair in self.current_flair():
            if self.verbose:
                sys.stdout.write('.')
                sys.stdout.flush()
            if use_text and use_css:
                key = (flair['flair_text'], flair['flair_css_class'])
            elif use_text:
                key = flair['flair_text']
            else:
                key = flair['flair_css_class']
            if key in counter:
                counter[key] += 1
            else:
                counter[key] = 1
        if self.verbose:
            print()

        # Sort flair list items according to the specified sort
        if sort == 'alpha':
            items = sorted(counter.items())
        else:
            items = sorted(counter.items(), key=lambda x: x[1], reverse=True)

        # Clear current templates and store flair according to the sort
        if self.verbose:
            print('Clearing current flair templates')
        self.sub.clear_flair_templates()
        for key, count in items:
            if not key or count < limit:
                continue
            if use_text and use_css:
                text, css = key
            elif use_text:
                text, css = key, ''
            else:
                text, css = '', key
            if self.verbose:
                print('Adding template: text: "%s" css: "%s"' % (text, css))
            self.sub.add_flair_template(text, css, editable)

    def login(self):
        """Login and provide debugging output if so wanted."""
        if not self._logged_in:
            if self.verbose:
                print('Logging in')
            self.reddit.login(self._user, self._pswd)
            self._logged_in = True

    def message(self, category, subject, msg_file):
        """Send message to all users in `category`."""
        self.login()
        users = getattr(self.sub, 'get_%s' % category)()
        if not users:
            print('There are no %s on %s.' % (category, str(self.sub)))
            return

        if msg_file:
            try:
                msg = open(msg_file).read()
            except IOError as error:
                print(str(error))
                return
        else:
            print('Enter message:')
            msg = sys.stdin.read()

        print('You are about to send the following '
              'message to the users %s:' % ', '.join([str(x) for x in users]))
        print('---BEGIN MESSAGE---\n%s\n---END MESSAGE---' % msg)
        if raw_input('Are you sure? yes/[no]: ').lower() not in ['y', 'yes']:
            print('Message sending aborted.')
            return
        for user in users:
            user.send_message(subject, msg)
            print('Sent to: %s' % str(user))

    def output_current_flair(self, as_json=False):
        """Display the current flair for all users in the subreddit."""
        flair_list = sorted(self.current_flair(), key=lambda x: x['user'])
        if as_json:
            print(json.dumps(flair_list, sort_keys=True, indent=4))
            return

        for flair in flair_list:
            print(flair['user'])
            print('  Text: %s\n   CSS: %s' %
                  (flair['flair_text'], flair['flair_css_class']))

    def output_flair_stats(self):
        """Display statistics (number of users) for each unique flair item."""
        css_counter = Counter()
        text_counter = Counter()
        for flair in self.current_flair():
            if flair['flair_css_class']:
                css_counter[flair['flair_css_class']] += 1
            if flair['flair_text']:
                text_counter[flair['flair_text']] += 1

        print('Flair CSS Statistics')
        for flair, count in sorted(css_counter.items(),
                                   key=lambda x: (x[1], x[0])):
            print('{0:3} {1}'.format(count, flair))

        print('Flair Text Statistics')
        for flair, count in sorted(text_counter.items(),
                                   key=lambda x: (x[1], x[0]),
                                   reverse=True):
            print('{0:3} {1}'.format(count, flair))

    def output_list(self, category):
        """Display the list of users in `category`."""
        self.login()
        print('%s users:' % category)
        for user in getattr(self.sub, 'get_%s' % category)():
            print('  %s' % user)
Пример #34
0
    tag_check(post, ptitle)
    flair_assign(post, purl, ptitle, flair)
    approve_host(post, purl, ptitle)
    if details_scan(post, pauthor, ptime) is False:
        pass
    else:
        with open("oldposts", "a+") as file:
            file.write(pid + "\n")


"""RUNNING BOT"""

print("Running on /r/" + SUBREDDIT)
while True:
    print("\nRunning at " + str(datetime.now(timezone.utc)))
    subreddit = r.get_subreddit(SUBREDDIT)
    posts = subreddit.get_new(limit=MAXPOSTS)
    try:
        with open("oldposts", "r") as file:
            oldposts = [line.strip() for line in file]
    except:
        oldposts = []
    try:
        for post in posts:
            if post.id in oldposts:
                pass
            else:
                actions(post)
    except Exception as e:
        print("An error has occurred\n", e)
    for var in range(WAIT, 0, -1):
Пример #35
0
    tag_check(post, ptitle)
    flair_assign(post, purl, ptitle, flair)
    approve_host(post, purl, ptitle)
    if details_scan(post, pauthor, ptime) is False:
        pass
    else:
        with open("oldposts", "a+") as file:
            file.write(pid + "\n")


"""RUNNING BOT"""

print("Running on /r/" + SUBREDDIT)
while True:
    print("\nRunning at " + str(datetime.now(timezone.utc)))
    subreddit = r.get_subreddit(SUBREDDIT)
    posts = subreddit.get_new(limit=MAXPOSTS)
    try:
        with open("oldposts", "r") as file:
            oldposts = [line.strip() for line in file]
    except:
        oldposts = []
    try:
        for post in posts:
            if post.id in oldposts:
                pass
            else:
                actions(post)
    except Exception as e:
        print("An error has occurred\n", e)
    for var in range(WAIT, 0, -1):
Пример #36
0
class RedditBot(_RedditBotBase):
    """
    Basic extendable Reddit bot.

    Provides means to loop over a list of whitelisted subreddits.

    """
    VERSION = (0, 0, 0)  # override this
    USER_AGENT = '{name} v{version} (by /u/{admin})'

    # if loop() returns this the bot will refresh its settings
    BOT_SHOULD_REFRESH = 'BOT_SHOULD_REFRESH'

    def __init__(self, config):
        """
        Initialize the bot with a dict of configuration values.

        """
        self._setup(config)
        self._login(config)

        self.subreddits = self._get_subreddits()
        self.blocked_users = self._get_blocked_users()

    def _setup(self, config):
        try:
            admins = config['admins']
            if isinstance(admins, list):
                self.admins = admins
            else:
                self.admins = list(map(str.strip, admins.split(',')))

            self.settings = DEFAULT_SETTINGS.copy()
            self.settings.update(config.get('settings', {}))
        except KeyError as e:
            import sys
            sys.stderr.write('error: missing {} in configuration'.format(e))
            sys.exit(2)

    def _login(self, config):
        logger.info('Attempting to login using OAuth2')

        for attr in ['client_id', 'client_secret', 'redirect_uri']:
            assert attr in config['oauth_info'], 'Missing `{}` in oauth_info'.format(attr)

        self.r = Reddit('OAuth Login v1.0')
        self.r.set_oauth_app_info(**config['oauth_info'])

        for attr in ['access_token', 'refresh_token']:
            assert attr in config['access_info'], 'Missing `{}` in access_info'.format(attr)
        access_info = config['access_info']
        access_info['scope'] = self.__class__.get_scope()
        self.r.set_access_credentials(**access_info)
        self.bot_name = self.r.user.name
        self.admins.append(self.bot_name)
        user_agent = self.USER_AGENT.format(
            name=self.bot_name,
            admin=self.admins[0],
            version='.'.join(map(str, self.VERSION))
        )
        logger.debug('User-Agent: {!r}'.format(user_agent))
        self.r.http.headers['User-Agent'] = user_agent
        logger.info('Logged in as {}'.format(self.bot_name))

    @classmethod
    def get_scope(cls):
        """Basic permission scope for RedditReplyBot operations."""
        return super(RedditBot, cls).get_scope() | {
            'identity',
            'subscribe',
            'mysubreddits',
        }

    def run_forever(self):
        self.bot_start()
        try:
            while True:
                self.do_loop()
                self.refresh()
        except Exception as e:
            self.bot_error(e)
            raise
        finally:
            self.bot_stop()

    def refresh(self):
        logger.info('Refreshing settings')
        self.subreddits = self._get_subreddits()
        self.blocked_users = self._get_blocked_users()

    def do_loop(self):
        for subreddit in cycle(self.subreddits):
            try:
                if self.loop(subreddit) == self.BOT_SHOULD_REFRESH:
                    break
            except Forbidden as e:
                logger.error('Forbidden in {}! Removing from whitelist.'.format(subreddit))
                self.remove_subreddits(subreddit)
                break
            except RateLimitExceeded as e:
                logger.warning('RateLimitExceeded! Sleeping {} seconds.'.format(e.sleep_time))
                time.sleep(e.sleep_time)
            except (ConnectionError, HTTPException) as e:
                logger.warning('Error: Reddit down or no connection? {!r}'.format(e))
                time.sleep(self.settings['loop_sleep'] * 10)
            else:
                time.sleep(self.settings['loop_sleep'])
        else:
            logger.error("No subreddits in file. Will read file again in 5 seconds.")
            time.sleep(5)

    def _get_subreddits(self):
        subreddits = list(map(lambda s: s.display_name, self.r.get_my_subreddits()))
        logger.info('Subreddits: {} entries'.format(len(subreddits)))
        logger.debug('List: {!r}'.format(subreddits))
        return subreddits

    def _get_blocked_users(self, filename=None):
        """Friends are blocked users, because Reddit only allows blocking
        users by private messages."""
        blocked_users = list(map(lambda u: u.name, self.r.get_friends()))
        logger.info('Blocked users: {} entries'.format(len(blocked_users)))
        logger.debug('List: {!r}'.format(blocked_users))
        return blocked_users

    def is_user_blocked(self, user_name):
        if user_name == self.bot_name:
            return True
        return user_name in self.blocked_users

    def is_subreddit_whitelisted(self, subreddit):
        return subreddit in self.subreddits

    def remove_subreddits(self, *subreddits):
        for sub_name in subreddits:
            if sub_name in self.subreddits:
                self.subreddits.remove(sub_name)
                sub = self.r.get_subreddit(sub_name)
                sub.unsubscribe()
                logger.info('Unsubscribed from /r/{}'.format(sub_name))

    def add_subreddits(self, *subreddits):
        for sub_name in subreddits:
            if sub_name not in self.subreddits:
                self.subreddits.add(sub_name)
                sub = self.r.get_subreddit(sub_name)
                sub.subscribe()
                logger.info('Subscribed to /r/{}'.format(sub_name))

    def block_users(self, *users):
        for user_name in users:
            if user_name not in self.blocked_users:
                self.blocked_users.add(user_name)
                user = self.r.get_redditor(user_name)
                user.friend()
                logger.info('Blocked /u/{}'.format(user_name))

    def unblock_users(self, *users):
        for user_name in users:
            if user_name in self.blocked_users:
                self.blocked_users.remove(user_name)
                user = self.r.get_redditor(user_name)
                user.unfriend()
                logger.info('Unblocked /u/{}'.format(user_name))
Пример #37
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)
Пример #38
0
class ModUtils(object):
    @staticmethod
    def remove_entities(item):
        if not item:
            return item
        return item.replace("&amp;", "&").replace("&lt;", "<").replace("&gt;", ">")

    def __init__(self, subreddit, site=None, user=None, pswd=None, verbose=None):
        self.reddit = Reddit(str(self), site)
        self._logged_in = False
        self._user = user
        self._pswd = pswd
        self.sub = self.reddit.get_subreddit(subreddit)
        self.verbose = verbose
        self._current_flair = None

    def add_users(self, category):
        mapping = {"banned": "ban", "contributors": "make_contributor", "moderators": "make_moderator"}

        if category not in mapping:
            print "%r is not a valid option for --add" % category
            return
        self.login()
        func = getattr(self.sub, mapping[category])
        print "Enter user names (any separation should suffice):"
        data = sys.stdin.read().strip()
        for name in re.split("[^A-Za-z0-9_]+", data):
            func(name)
            print "Added %r to %s" % (name, category)

    def current_flair(self):
        if self._current_flair is None:
            self._current_flair = []
            self.login()
            if self.verbose:
                print "Fetching flair list for %s" % self.sub
            for flair in self.sub.flair_list():
                for item in ("flair_text", "flair_css_class"):
                    flair[item] = self.remove_entities(flair[item])
                self._current_flair.append(flair)
                yield flair
        else:
            for item in self._current_flair:
                yield item

    def flair_template_sync(self, editable, limit, static, sort, use_css, use_text):  # pylint: disable-msg=R0912
        # Parameter verification
        if not use_text and not use_css:
            raise Exception("At least one of use_text or use_css must be True")
        sorts = ("alpha", "size")
        if sort not in sorts:
            raise Exception("Sort must be one of: %s" % ", ".join(sorts))

        # Build current flair list along with static values
        counter = {}
        if static:
            for key in static:
                if use_css and use_text:
                    parts = tuple(x.strip() for x in key.split(","))
                    if len(parts) != 2:
                        raise Exception(
                            "--static argument %r must have two "
                            "parts (comma separated) when using "
                            "both text and css." % parts
                        )
                    key = parts
                counter[key] = limit
        self.login()
        if self.verbose:
            sys.stdout.write("Retrieving current flair\n")
            sys.stdout.flush()
        for flair in self.current_flair():
            if self.verbose:
                sys.stdout.write(".")
                sys.stdout.flush()
            if use_text and use_css:
                key = (flair["flair_text"], flair["flair_css_class"])
            elif use_text:
                key = flair["flair_text"]
            else:
                key = flair["flair_css_class"]
            if key in counter:
                counter[key] += 1
            else:
                counter[key] = 1
        if self.verbose:
            print

        # Sort flair list items according to the specified sort
        if sort == "alpha":
            items = sorted(counter.items())
        else:
            items = sorted(counter.items(), key=lambda x: x[1], reverse=True)

        # Clear current templates and store flair according to the sort
        if self.verbose:
            print "Clearing current flair templates"
        self.sub.clear_flair_templates()
        for key, count in items:
            if not key or count < limit:
                continue
            if use_text and use_css:
                text, css = key
            elif use_text:
                text, css = key, ""
            else:
                text, css = "", key
            if self.verbose:
                print 'Adding template: text: "%s" css: "%s"' % (text, css)
            self.sub.add_flair_template(text, css, editable)

    def login(self):
        if not self._logged_in:
            if self.verbose:
                print "Logging in"
            self.reddit.login(self._user, self._pswd)
            self.logged_in = True

    def message(self, category, subject, msg_file):
        self.login()
        users = getattr(self.sub, "get_%s" % category)()
        if not users:
            print "There are no %s on %s." % (category, str(self.sub))
            return

        if msg_file:
            try:
                msg = open(msg_file).read()
            except IOError, error:
                print str(error)
                return
        else:
Пример #39
0
 def add_callback():
     tmp = Reddit(USER_AGENT, disable_update_check=True)
     tmp.login(self.other_user_name, self.other_user_pswd,
               disable_warning=True)
     tmp.get_subreddit(self.sr).accept_moderator_invite()
Пример #40
0
class ModUtils(object):

    """Class that provides all the modutils functionality."""

    def __init__(self, subreddit, site=None, user=None, pswd=None, verbose=None):
        self.reddit = Reddit(str(self), site, disable_update_check=True)
        self.reddit.config.decode_html_entities = True
        self._logged_in = False
        self._user = user
        self._pswd = pswd
        self.sub = self.reddit.get_subreddit(subreddit)
        self.verbose = verbose
        self._current_flair = None

    def add_users(self, category):
        """Add users to 'banned', 'contributors', or 'moderators'."""
        mapping = {"banned": "ban", "contributors": "make_contributor", "moderators": "make_moderator"}

        if category not in mapping:
            print("%r is not a valid option for --add" % category)
            return
        self.login()
        func = getattr(self.sub, mapping[category])
        print("Enter user names (any separation should suffice):")
        data = sys.stdin.read().strip()
        for name in re.split("[^A-Za-z0-9_]+", data):
            func(name)
            print("Added %r to %s" % (name, category))

    def clear_empty(self):
        """Remove flair that is not visible or has been set to empty."""
        for flair in self.current_flair():
            if not flair["flair_text"] and not flair["flair_css_class"]:
                print(self.reddit.delete_flair(self.sub, flair["user"]))
                print("Removed flair for {0}".format(flair["user"]))

    def current_flair(self):
        """Generate the flair, by user, for the subreddit."""
        if self._current_flair is None:
            self._current_flair = []
            self.login()
            if self.verbose:
                print("Fetching flair list for %s" % self.sub)
            for flair in self.sub.get_flair_list(limit=None):
                self._current_flair.append(flair)
                yield flair
        else:
            for item in self._current_flair:
                yield item

    def flair_template_sync(self, editable, limit, static, sort, use_css, use_text):  # pylint: disable-msg=R0912
        """Synchronize templates with flair that already exists on the site.

        :param editable: Indicates that all the options should be editable.
        :param limit: The minimum number of users that must share the flair
            before it is added as a template.
        :param static: A list of flair templates that will always be added.
        :param sort: The order to sort the flair templates.
        :param use_css: Include css in the templates.
        :param use_text: Include text in the templates.

        """
        # Parameter verification
        if not use_text and not use_css:
            raise Exception("At least one of use_text or use_css must be True")
        sorts = ("alpha", "size")
        if sort not in sorts:
            raise Exception("Sort must be one of: %s" % ", ".join(sorts))

        # Build current flair list along with static values
        counter = {}
        if static:
            for key in static:
                if use_css and use_text:
                    parts = tuple(x.strip() for x in key.split(","))
                    if len(parts) != 2:
                        raise Exception(
                            "--static argument %r must have two "
                            "parts (comma separated) when using "
                            "both text and css." % parts
                        )
                    key = parts
                counter[key] = limit
        self.login()
        if self.verbose:
            sys.stdout.write("Retrieving current flair\n")
            sys.stdout.flush()
        for flair in self.current_flair():
            if self.verbose:
                sys.stdout.write(".")
                sys.stdout.flush()
            if use_text and use_css:
                key = (flair["flair_text"], flair["flair_css_class"])
            elif use_text:
                key = flair["flair_text"]
            else:
                key = flair["flair_css_class"]
            if key in counter:
                counter[key] += 1
            else:
                counter[key] = 1
        if self.verbose:
            print()

        # Sort flair list items according to the specified sort
        if sort == "alpha":
            items = sorted(counter.items())
        else:
            items = sorted(counter.items(), key=lambda x: x[1], reverse=True)

        # Clear current templates and store flair according to the sort
        if self.verbose:
            print("Clearing current flair templates")
        self.sub.clear_flair_templates()
        for key, count in items:
            if not key or count < limit:
                continue
            if use_text and use_css:
                text, css = key
            elif use_text:
                text, css = key, ""
            else:
                text, css = "", key
            if self.verbose:
                print('Adding template: text: "%s" css: "%s"' % (text, css))
            self.sub.add_flair_template(text, css, editable)

    def login(self):
        """Login and provide debugging output if so wanted."""
        if not self._logged_in:
            if self.verbose:
                print("Logging in")
            self.reddit.login(self._user, self._pswd)
            self._logged_in = True

    def message(self, category, subject, msg_file):
        """Send message to all users in `category`."""
        self.login()
        users = getattr(self.sub, "get_%s" % category)()
        if not users:
            print("There are no %s on %s." % (category, str(self.sub)))
            return

        if msg_file:
            try:
                msg = open(msg_file).read()
            except IOError as error:
                print(str(error))
                return
        else:
            print("Enter message:")
            msg = sys.stdin.read()

        print("You are about to send the following " "message to the users %s:" % ", ".join([str(x) for x in users]))
        print("---BEGIN MESSAGE---\n%s\n---END MESSAGE---" % msg)
        if raw_input("Are you sure? yes/[no]: ").lower() not in ["y", "yes"]:
            print("Message sending aborted.")
            return
        for user in users:
            user.send_message(subject, msg)
            print("Sent to: %s" % str(user))

    def output_current_flair(self, as_json=False):
        """Display the current flair for all users in the subreddit."""
        flair_list = sorted(self.current_flair(), key=lambda x: x["user"])
        if as_json:
            print(json.dumps(flair_list, sort_keys=True, indent=4))
            return

        for flair in flair_list:
            print(flair["user"])
            print("  Text: %s\n   CSS: %s" % (flair["flair_text"], flair["flair_css_class"]))

    def output_flair_stats(self):
        """Display statistics (number of users) for each unique flair item."""
        css_counter = Counter()
        text_counter = Counter()
        for flair in self.current_flair():
            if flair["flair_css_class"]:
                css_counter[flair["flair_css_class"]] += 1
            if flair["flair_text"]:
                text_counter[flair["flair_text"]] += 1

        print("Flair CSS Statistics")
        for flair, count in sorted(css_counter.items(), key=lambda x: (x[1], x[0])):
            print("{0:3} {1}".format(count, flair))

        print("Flair Text Statistics")
        for flair, count in sorted(text_counter.items(), key=lambda x: (x[1], x[0]), reverse=True):
            print("{0:3} {1}".format(count, flair))

    def output_list(self, category):
        """Display the list of users in `category`."""
        self.login()
        print("%s users:" % category)
        for user in getattr(self.sub, "get_%s" % category)():
            print("  %s" % user)
Пример #41
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)
Пример #42
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()
    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)
Пример #43
0
 def add_callback():
     tmp = Reddit(USER_AGENT, disable_update_check=True)
     tmp.login(self.other_user_name,
               self.other_user_pswd,
               disable_warning=True)
     tmp.get_subreddit(self.sr).accept_moderator_invite()