Пример #1
0
def remove_users(reddit: Reddit, users, subreddit, c, conn):
    moderator_relationship = subreddit.moderator()
    to_delete = list()
    moderators = list()
    for moderator in moderator_relationship:
        moderators.append(moderator)
    for user in users:
        try:
            in_subreddit = False
            redditor: Redditor = reddit.redditor(user)
            today = datetime.date.today()
            join_date = get_user_join_date(user, c, conn)
            delta_time = today - join_date
            if delta_time.days <= 7:
                continue
            in_subreddit = iterate_over_days(redditor.comments.new(
                limit=1000), subreddit, config.removal_time)
            if not in_subreddit:
                in_subreddit = iterate_over_days(redditor.submissions.new(
                    limit=1000), subreddit, config.removal_time)
            if not in_subreddit:
                to_delete.append(user)
                continue
        except Exception:
            continue
    for user in to_delete:
        redditor = reddit.redditor(user)
        subreddit.contributor.remove(redditor)
        c.execute('DELETE FROM Users WHERE Username=?', (user,))
        conn.commit()
        print(user)
    print('Amount of users deleted: ', len(to_delete))
    print('')
    return to_delete
Пример #2
0
class Reddit:
    def __init__(self, client_id=None, client_secret=None, user_agent=None):
        self.client_id = client_id or os.getenv('REDDIT_CLIENT_ID')
        self.client_secret = client_secret or os.getenv('REDDIT_CLIENT_SECRET')
        self.user_agent = user_agent or os.getenv('REDDIT_USER_AGENT')

        self.api = PrawReddit(
            client_id=self.client_id,
            client_secret=self.client_secret,
            user_agent=self.user_agent,
            read_only=True,
        )

    def get_comments_submissions(self, username, num=5):
        """Return max `num` of comments and submissions by `username`."""
        coms = [
            dict(
                title=comment.link_title,
                text=comment.body_html,
                subreddit=comment.subreddit_name_prefixed,
                url=comment.link_url,
                created=datetime.fromtimestamp(comment.created_utc, pytz.utc),
            )
            for comment in self.api.redditor(username).comments.new(limit=num)
        ]
        subs = [
            dict(
                title=submission.title,
                text=submission.selftext_html,
                subreddit=submission.subreddit_name_prefixed,
                url=submission.url,
                created=datetime.fromtimestamp(submission.created_utc,
                                               pytz.utc),
            ) for submission in self.api.redditor(username).submissions.new(
                limit=num)
        ]
        return coms + subs if len(coms + subs) < num else (coms + subs)[:num]

    def profile_image_url(self, username):
        """Return URL of user's avatar image."""
        try:
            return self.api.redditor(username).icon_img
        except NotFound:
            logger.exception('Failed to fetch Reddit profile image of %s',
                             username)
            return None

    @staticmethod
    def profile_url(username):
        """Return URL of user's profile."""
        return 'https://www.reddit.com/user/%s' % username
Пример #3
0
def run_user_search(r: Reddit, comments_replied_to):
    """
    Runs a stream of new comments from a designated Redditor. Replies to new
    comments with a message.
    todo    link this with some api to make funny comment responses. Update
        time to be based off time at start of program.
    :param r: an instance of Reddit
    :param comments_replied_to: the archive of comment IDs for comments this
    bot has replied to already
    """
    # Define a search user, create a list to track all comments
    search_user = config.HiddenUser

    # Track comments replied to
    comments_replied_to = []

    # Track time of newest submission and submission in stream
    new_message_time_utc = 0
    current_comment_time_utc = 0

    # Finds the most recently posted comment
    newest_comment = r.redditor(search_user).comments.new(limit=1).next()
    new_message_time_utc = newest_comment.created_utc
    print(f'The most recent comment was {newest_comment.body!r}')

    # Streams new comments in
    for comment in r.redditor(search_user).stream.comments():
        current_comment_time_utc = comment.created_utc
        # If new comment is found, reply and update
        if (current_comment_time_utc > new_message_time_utc):
            print(f'I found a new comment {comment.body!r}')
            comment.reply(commands.HELSER)

            comments_replied_to.append(comment.id)

            # Opens comment tracking archive and trims it to maximum 25
            # comment IDs
            with open("comments_replied_to.txt", "r") as fread:
                comment_id_archive = fread.read().splitlines(True)
                comment_id_archive = comment_id_archive[-24:]

            # Adds the newest comment we've found and replied to as the 25
            # comment ID, at the end of the
            # list
            with open("comments_replied_to.txt", "w") as fout:
                for comment_id in comment_id_archive:
                    fout.write(comment_id)
                fout.write(comment.id)

            # Track time of most recent message.
            new_message_time_utc = current_comment_time_utc
Пример #4
0
def _set_redditor_karma(api: praw.Reddit, name):
    red = api.redditor(name)
    return """
MATCH (n {id: "%s"})
WITH n
SET n.comment_karma = %s, n.link_karma = %s
""" % (red.id, red.comment_karma, red.link_karma)
def respond(config, debug=False):
    setup_logging(debug)
    logger = logging.getLogger('Responder')
    logger.info('Initialized')
    reddit = Reddit(config['Bot'])
    with open(config['Track file']) as f:
        last_response = float(f.read())
    logger.debug('Last comment read in')
    comments = list(
        filter(
            lambda c: c.subreddit.display_name.lower() in config['Subreddits'].
            lower(),
            reddit.redditor(config['Target']).comments.new(limit=50)))
    logger.debug('Fetched latest comments. Count: %i', len(comments))
    for comment in comments:
        if last_response < comment.created_utc:
            logger.debug('Target comment found')
            response = comment.reply(config['Message'])
            if response:
                logger.info(
                    'Responded to comment. Response URL: https://reddit.com%s',
                    response.permalink)
                with open(config['Track file'], 'w') as f:
                    f.write(str(comment.created_utc))
                break
            else:
                logger.warning('Unable to respond to comment: %s',
                               comment.link_url)
    logger.debug('Exiting')
Пример #6
0
def migrate_friends(origin_account: praw.Reddit, destination_account: praw.Reddit, friends: list, verbose: bool = True):
    """
    Migrates a friend list from one reddit account to another
    """
    if utils.is_null_or_empty(friends):
        print("Friends list is empty or was not found.")
        return

    for friend in friends:
        redditor_name = friend.name

        try:
            # Add to destination account
            destination_account.redditor(redditor_name).friend()

            # Remove from origin account
            origin_account.redditor(redditor_name).unfriend()
        except Exception as ex:
            log.error(ex, f"An error occurred while migrating the redditor '{redditor_name}'.")
def send_message_to_staff(
    reddit: Reddit,
    winners_list_path: str,
    staff_recipients: List[str],
    version_string: str,
    gold_coin_reward: int,
):
    """
    Sends the winners list results to a list of recipients via Private Message (PM)

    This function must be called only after the winners_list_path file exists!

    Attributes:
        reddit: the PRAW Reddit instance
        winners_list_path: the file path to read from for the winners list + potential winners list
        staff_recipients: a list of staff member recipients for the PM
        version_string: the version of the patch notes
        gold_coin_reward: the number of Gold Coins intended for the reward
    """
    with open(winners_list_path, "r") as winners_list_file:
        winners_list_text = (
            f"The following Reddit users have won {str(gold_coin_reward)} Gold Coins from the Reddit Patch Notes game:\n\n"
            + winners_list_file.read())
        subject_line = (
            f"{version_string} - Winners for the HoN Patch Notes Guessing Game"
        )

        for recipient in staff_recipients:
            try:
                reddit.redditor(recipient).message(subject=subject_line,
                                                   message=winners_list_text)
            except RedditAPIException as redditError:
                tprint(f"RedditAPIException encountered: {redditError}")
                tprint(
                    f"{recipient} was not sent a message, continuing to next recipient"
                )
                continue
            except Exception as error:
                tprint(f"General Exception encountered: {error}")
                tprint(
                    f"{recipient} was not sent a message, continuing to next recipient"
                )
                continue
Пример #8
0
def decay(r: praw.Reddit, user: str, db: Database) -> None:
    """
    Trigger point decay on a user
    """
    for submission in r.redditor(user).new(limit=1):
        if (time.time() - submission.created_utc < 259200):
            return
        usero = db.lookup(user)
        usero.score *= .96
        journal("User " + user + "'s score decayed to " + str(user.score))
        db.add(usero)
Пример #9
0
def main():
    config = configparser.ConfigParser()
    config.read(f'{HOME_DIR}/.config/praw.ini')
    bots = config['DEFAULT']['bots'].split(',')

    for bot in bots:
        reddit = Reddit(bot, config_interpolation='basic')
        bot_name = reddit.config.custom['bot_name']
        redditor = reddit.redditor(bot_name)
        get_bot_stat(redditor).to_csv(
            f'{CURRENT_DIR}/{bot_name}.tsv',
            sep='\t',
            index=False,
        )
        print(f'{bot} done')
Пример #10
0
def is_sub_mod_praw(sub_name: Text, useranme: Text, reddit: Reddit) -> bool:
    """
    Check if a given username is a moderator on a given sub
    :rtype: bool
    :param subreddit: Praw SubReddit obj
    :param user: username
    :return: bool
    """
    user = reddit.redditor(useranme)
    if not user:
        log.error('Failed to locate redditor %s', useranme)
        return False
    for sub in user.moderated():
        if sub.display_name.lower() == sub_name.lower():
            return True
    return False
def test_get_user_submissions(test_user: str, limit: int, downloader_mock: MagicMock, reddit_instance: praw.Reddit):
    downloader_mock.args.limit = limit
    downloader_mock._determine_sort_function.return_value = praw.models.Subreddit.hot
    downloader_mock.sort_filter = RedditTypes.SortType.HOT
    downloader_mock.args.submitted = True
    downloader_mock.args.user = test_user
    downloader_mock.authenticated = False
    downloader_mock.reddit_instance = reddit_instance
    downloader_mock._create_filtered_listing_generator.return_value = \
        RedditDownloader._create_filtered_listing_generator(
            downloader_mock,
            reddit_instance.redditor(test_user).submissions,
        )
    results = RedditDownloader._get_user_data(downloader_mock)
    results = assert_all_results_are_submissions(limit, results)
    assert all([res.author.name == test_user for res in results])
def get_fruit_vectors(fruit_vector_client: praw.Reddit, recent: bool, start_limit=0, end_limit=-1):
    posts = fruit_vector_client.redditor("ErmineDev").submissions.new(limit=300)
    fruit_vectors = []
    for current_post in posts:
        if current_post.subreddit_id in whitelisted_subreddit_ids:
            # The ID of the first non-fruit vector post, stop here if this is also the current post ID
            if current_post.id != "eos9jv":
                fruit_vectors.append(current_post)
            else:
                break
    if start_limit > len(fruit_vectors):
        start_limit = 0
    if end_limit < start_limit:
        end_limit = len(fruit_vectors)
    if recent:
        return fruit_vectors[start_limit:end_limit][::-1]
    elif not recent:
        return fruit_vectors[::-1][start_limit:end_limit]
Пример #13
0
class RedditInstance():
    '''
    This class will take care of the Reddit instance
    especially with regards to posting.
    '''
    def __init__(self, site_name='', user_agent=''):
        self.site_name = site_name
        self.user_agent = user_agent
        self.Reddit = Reddit(site_name=self.site_name, user_agent=self.user_agent)
        
        self.post_title = ''
        self.post_body = []

    def append_body(self, message, end='\n'):
        if isinstance(message, str):
            self.post_body.append(message + end)
        elif isinstance(message, list):
            self.post_body += message


    def extend_body_last(self, message, end='\n'):
        self.post_body[-1] += message + end

    @property
    def body(self):
        return ''.join(self.post_body)

    def post(self, subreddit):
        self.Reddit.subreddit(subreddit).submit(self.post_title, self.body)

    @property
    def latest_post(self):
        return self.Reddit.redditor(self.site_name).submissions.new(limit=1)
    
    def comment_on_post(self, comment):
        self.latest_post.reply(comment)
Пример #14
0
def save_user(
    db,
    reddit: praw.Reddit,
    username: str,
    post_reload_sec: int,
    comment_reload_sec: int,
) -> None:

    user = reddit.redditor(username)
    latest_post_utc = latest_from_user_utc(db=db,
                                           table_name="posts",
                                           username=username)
    get_since = latest_post_utc and (latest_post_utc - post_reload_sec)
    LOGGER.info(f"Getting posts by {username} since timestamp {get_since}")
    _takewhile = partial(created_since, target_sec_utc=get_since)

    db["posts"].upsert_all(
        (saveable(s)
         for s in takewhile(_takewhile, user.submissions.new(limit=LIMIT))),
        pk="id",
        alter=True,
    )

    latest_comment_utc = latest_from_user_utc(db=db,
                                              table_name="comments",
                                              username=username)
    get_since = latest_post_utc and (latest_post_utc - comment_reload_sec)
    LOGGER.info(f"Getting comments by {username} since timestamp {get_since}")
    _takewhile = partial(created_since, target_sec_utc=get_since)

    db["comments"].upsert_all(
        (saveable(s)
         for s in takewhile(_takewhile, user.comments.new(limit=LIMIT))),
        pk="id",
        alter=True,
    )
Пример #15
0
class reddit_bot(configuration_manager,Reddit):

    _COMMENT_MAX_LEN = 10000

    def __init__(self,configuration_json_file, configuation_json_item):
        self.bot = None
        self.bot_call = None
        self.bot_subreddit = None
        self.bot_database = None
        self.bot_name = None
        self.bot_objects = []
        self.configuation_json_item = configuation_json_item
        self.initialize_bot(configuration_json_file)
        self.subscribed_subreddits = self.get_subscribed_subreddits()

    def initialize_bot(self,PRAW_bot_json_file):
        PRAW_json_item = ','.join([self._firstItem,self.configuation_json_item])
        connection_string_list = self.config_arguments(PRAW_bot_json_file,PRAW_json_item)
        self.bot = Reddit(
        user_agent=connection_string_list['user_agent'],
        client_id=connection_string_list['client_id'],
        client_secret=connection_string_list['client_secret'],
        username=connection_string_list['username'],
        password=connection_string_list['password']
        )
        self.bot_name=connection_string_list['username']
        self.bot_call=connection_string_list['bot_call']
        self.bot_subreddit=self.bot.subreddit(connection_string_list['bot_subreddit'])
        self.bot_database=connection_string_list['source_database']
    
    def get_subscribed_subreddits(self):
        subreddit_list = []
        for subreddit in list(self.bot.user.subreddits(limit=None)):
            subreddit_list.append(subreddit.display_name)
        return '+'.join(subreddit_list)
    
    def get_reddit_obj_type(self,object_id):
        try:
            return str(self.bot.redditor(object_id[0:3]))
        except:
            print('didn\'t work')
    
    def get_reddit_obj(self,object_id):
        try:
            return self.bot.redditor(object_id)
        except:
            print('didn\'t work')

    def reddit_object_redditor_reply(self,object_id,source_redditor=None,return_comment_id=False):
        if source_redditor is None:
            source_redditor = self.bot_name
        if self.get_reddit_obj_type(object_id)==REDDIT_COMMENT:
            object_id=object_id.replace(REDDIT_COMMENT,'')
            calling_object = self.bot.comment(object_id)
            calling_object.comment_sort = 'new'
        elif self.get_reddit_obj_type(object_id)==REDDIT_LINK:
            object_id=object_id.replace(REDDIT_LINK,'')
            calling_object = self.bot.submission(object_id)
            calling_object.comment_sort = 'new'
            calling_object.reply_sort = 'new'
        try:
            replies = calling_object.comments
            for r in replies:
                if r.author == source_redditor:
                    if return_comment_id:
                        return r
                    else:
                        return True
            return False
        except:
            return False
Пример #16
0
class RedditBot():
    def __init__(self,
                 praw_bot_name: str,
                 is_silent: bool = True,
                 test_submission: bool = True,
                 manual: bool = False) -> None:
        self.reddit = Reddit(praw_bot_name, config_interpolation='basic')
        self.model = Model(self.reddit.config.custom['model_name'])
        self.is_silent = is_silent
        self.test_submission = test_submission
        self.manual = manual
        logger.debug(f'model {self.reddit.config.custom["model_name"]}')

    def _get_subreddits(self) -> List[Subreddit]:
        subreddits = []
        for subreddit_name in self.reddit.config.custom['subreddits'].split(
                ','):
            subreddit = self.reddit.subreddit(subreddit_name)
            subreddits.append(subreddit)
        return subreddits

    def _get_replied_submissions(self) -> Set[str]:
        redditor = self.reddit.redditor(self.reddit.config.custom['bot_name'])
        summissions_id = set()
        for comment in redditor.comments.new(limit=None):
            summissions_id.add(comment.submission.id)
        return summissions_id

    def _get_submissions(self,
                         subreddits: List[Subreddit],
                         check_hot_count: int = 150,
                         max_submission_count: int = 5) -> List[Submission]:
        replied_submissions_id = self._get_replied_submissions()

        submissions = []
        for subreddit in subreddits:
            for submission in subreddit.hot(limit=check_hot_count):
                if submission.id in replied_submissions_id:
                    continue
                if contains_stop_words(submission.title):
                    continue
                submissions.append(submission)

        if len(submissions) == 0:
            logger.debug(f'no submissions found')

        submissions = sorted(submissions,
                             key=lambda x: x.created_utc,
                             reverse=True)

        submissions = submissions[:max_submission_count * 4]
        submissions = random.sample(
            submissions, min(len(submissions), max_submission_count))
        logger.debug(f'found submissions: {submissions}')
        return submissions

    def _prepare_reply(self,
                       title: str,
                       text: str,
                       num_return_sequences: int = 1) -> [str]:
        replies = self.model.generate_text(title, text, num_return_sequences)
        processed_replies = [process_output(reply) for reply in replies]
        return processed_replies, replies

    def _make_reply(self,
                    submission: Submission,
                    reply_text: str = None) -> None:
        url = submission.url
        title = submission.title
        selftext = submission.selftext

        if reply_text is None:
            reply_text, generated_text = self._prepare_reply(title, selftext)
            reply_text, generated_text = reply_text[0], generated_text[0]

        if not reply_text or reply_text == ' ':
            logger.debug(
                f'FAIL: generated reply to submission={title} was very bad ({generated_text})'
            )
            return

        if self.test_submission:
            submission = self.reddit.submission(
                self.reddit.config.custom['test_submission'])
            if not self.is_silent:
                submission.reply(
                    f'{url}\n\n{title}\n\n{selftext}\n\n\nREPLY:\n\n' +
                    reply_text)
        else:
            if not self.is_silent:
                submission.reply(reply_text)

        logger.debug(f'made reply: submission={title} reply={reply_text}')

    def _choose_reply_manual(self, submission: Submission,
                             reply_texts: str) -> str:
        url = submission.url
        title = submission.title
        selftext = submission.selftext
        print('SUBMISSION DATA:')
        print(f'{url}\n{title}\n{selftext}')
        print('Possible options:')
        for i, reply_text in enumerate(reply_texts):
            print(f'{i}) {reply_text}')
            print('-----')
        reply_index = input('Your choice:')
        try:
            reply_index = int(reply_index)
            if reply_index < 0 or reply_index >= len(reply_texts):
                return None
            print(f'selected option: {reply_index}')
            return reply_texts[reply_index]
        except ValueError:
            return None

    def _make_replies(self, submissions: List[Submission]) -> None:
        if not self.manual:
            for submission in submissions:
                self._make_reply(submission)
                time.sleep(SLEEP_BETWEEN_REPLIES)
        else:
            reply_texts_list = []
            for submission in submissions:
                reply_text, _ = self._prepare_reply(submission.title,
                                                    submission.selftext, 10)
                reply_texts_list.append(reply_text)
            replies = []
            for submission, reply_texts in zip(submissions, reply_texts_list):
                replies.append(
                    self._choose_reply_manual(submission, reply_texts))
            for submission, reply_text in zip(submissions, replies):
                if reply_text is None:
                    continue
                self._make_reply(submission, reply_text)
                time.sleep(SLEEP_BETWEEN_REPLIES)

    def run(self) -> None:
        subreddits = self._get_subreddits()
        if self.manual:
            submissions = self._get_submissions(subreddits,
                                                max_submission_count=10)
        else:
            submissions = self._get_submissions(subreddits)
        self._make_replies(submissions)
Пример #17
0
def get_comments(reddit: praw.Reddit, user_name: str):
    user = reddit.redditor(user_name)
    comments = user.comments.new()
    yield from comments
Пример #18
0
def processinbox(r: praw.Reddit, accountsdb) :
    for message in r.inbox.unread() :
        r.inbox.mark_read([message])
        body = message.body.split()
        body[0] = body[0].lower()
        user = ''
        if "!bal" in body[0] :
            if len(body) == 2 and (not config.privatebalances or message.author.name in config.mods):
                user = body[1]
            else :
                user = message.author.name
            balance = database.balance(user, accountsdb)
            message.reply("User " + user + " has " + str(balance) + " " + config.currencyname + config.signature)
            log(message.author.name + " queried " + user +"'s balance, which is " + str(balance))
            continue
        if "!newa" in body[0] or "!createa" in body[0]:
            if database.balance(message.author.name) == None :
                database.createaccount(message.author.name, accountsdb, config.startingbalance)
                message.reply("Account created. Your starting balance is " + str(config.startingbalance) + " " + config.currencyname + config.signature)
                log(message.author.name + " created a new account")
            else :
                message.reply("You already have an account. Your balance is " + str(database.balance(message.author.name)) + " " + config.currencyname + config.signature)
            continue
        if ("!delete" in body[0] or "!close" in body[0]) and len(body) == 1 :
            if database.balance(message.author.name) == None :
                message.reply("You don't have an account." + config.signature)
            else :
                database.deleteaccount(message.author.name, accountsdb)
                message.reply("Your account has been deleted." + config.signature)
                log(message.author.name + " deleted their account")
            continue
        if "!trans" in body[0] or "!send" in body[0]:
            if len(body) < 3 :
                message.reply("Error: command `!transfer` takes 2 arguments, not " + str(len(body) - 1) + config.signature)
                continue
            if database.balance(message.author.name, accountsdb) == None :
                message.reply("You currently don't have an account. Run !newacc to create an account." + config.signature)
                continue
            if database.balance(body[1], accountsdb) == None :
                message.reply("The target user, " + body[1] + " does not have an account." + config.signature)
                continue
            try :
                amt = int(body[2])
                if amt <= 0 :
                    message.reply("Error: amount cannot be negative or zero" + config.signature)
                    continue
                if amt > database.balance(message.author.name, accountsdb) :
                    message.reply("Error: amount is greater than your available balance, which is " + str(database.balance(message.author.name, accountsdb)))
                    continue
                database.change(message.author.name, amt * -1, accountsdb)
                database.change(body[1], amt, accountsdb)
                message.reply("Transfer successful! Your available balance is now " + str(database.balance(message.author.name, accountsdb)) + " " + config.currencyname + config.signature)
                r.redditor(body[1]).message("You received a transfer", "You were sent " + str(amt) + " " + config.currencyname + " from u/" + message.author.name + config.signature)
                log(message.author.name + " transferred " + str(amt) + " to " + body[1] + "; new balance = " + str(database.balance(message.author.name, accountsdb)))
                continue
            except ValueError :
                message.reply("Error: amount must be an integer" + config.signature)
                continue
        mc = modcommand(message, accountsdb)
        if "!" in body[0] and not mc:
            message.reply("Command \"" + body[0] + "\" not found." + config.signature)
            if config.markread :
                continue
        if not mc :
            r.inbox.mark_unread([message])
    time.sleep(config.sleeptime)
if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("--db_path", type=str, required=True, help="Path to the database file")
    parser.add_argument(
        "--n_hot", type=int, required=True, help="Number of hot submissions and comments to fetch for each user"
    )
    parser.add_argument("--reddit_client_id", type=str, required=True, help="Client ID to use with Reddit's API")
    parser.add_argument(
        "--reddit_client_secret", type=str, required=True, help="Client secret to use with Reddit's API"
    )
    args = parser.parse_args()

    logger.info("Create connection to '{}'".format(args.db_path))
    db_connection = create_connection(db_path=args.db_path)

    logger.info("Connect to Reddit API")
    reddit = Reddit(
        user_agent="mbti-type-from-text:v0.0.1 (by /u/gcoter)",
        client_id=args.reddit_client_id,
        client_secret=args.reddit_client_secret,
    )

    logger.info("Start iterating over users")
    all_user_names = get_all_user_names(db_connection=db_connection)
    for n, user_name in enumerate(all_user_names):
        logger.info("Handle user '{}' ({}/{})".format(user_name, n + 1, len(all_user_names)))
        user = reddit.redditor(name=user_name)
        insert_or_update_user_submissions(user=user, n_hot=args.n_hot, db_connection=db_connection)
        insert_or_update_user_comments(user=user, n_hot=args.n_hot, db_connection=db_connection)
Пример #20
0
class SearchEngine:

    __rd_socket = None
    __ps_api = None

    def __init__(self, user_agent=False, use_praw=False):
        rd_credential = (RedditCredential.objects.get(user_agent=user_agent)
                         if user_agent else RedditCredential.objects.first())

        self.__rd_socket = Reddit(client_id=rd_credential.client_id,
                                  client_secret=rd_credential.client_secret,
                                  user_agent=rd_credential.user_agent)

        self.__ps_api = PushshiftAPI(self.__rd_socket if use_praw else None)

    def new_credential(client_id, client_secret, user_agent, domain):
        rd_credential = RedditCredential(
            client_id=client_id,
            client_secret=client_secret,
            user_agent=user_agent,
            domain=domain,
        )
        rd_credential.save()

    def most_commented_submissions(self,
                                   subreddit=None,
                                   before=None,
                                   after=None,
                                   limit=10):
        return [
            {
                'id': submission.id,
                'title': submission.title,
                'body': submission.selftext,
                'url': submission.url,
                'subreddit': submission.subreddit,
                'author': submission.author,
                'num_comments': submission.num_comments,
                'created_at': datetime.utcfromtimestamp(
                    submission.created_utc),
                'retrieved_on': datetime.utcfromtimestamp(
                    submission.retrieved_on)
            } for submission in self.__ps_api.search_submissions(
                before=datetime.strptime(before, "%d-%m-%Y"
                                         ) if before else None,
                after=datetime.strptime(after, "%d-%m-%Y") if after else None,
                subreddit=subreddit,
                sort_type='num_comments',
                limit=limit)
        ] or [None]

    def retrive_submission_by_id(self, submission_id):
        submission = self.__rd_socket.submission(id=submission_id)

        submission = {
            'id': submission.id,
            'title': submission.title,
            'body': submission.selftext,
            'url': submission.url,
            'subreddit': submission.subreddit.display_name,
            'author': submission.author.name,
            'score': submission.score,
            'num_comments': submission.num_comments,
            'created_at': datetime.utcfromtimestamp(submission.created_utc),
            'retrieved_on': datetime.now()
        }

        return submission

    def retrive_submission_comments(self,
                                    submission_id,
                                    before=None,
                                    after=None,
                                    method='psaw'):
        comments = None

        if method == 'psaw':
            comments = [{
                'id':
                comment.id,
                'author_id':
                comment.author,
                'body':
                comment.body,
                'created_at':
                datetime.utcfromtimestamp(comment.created_utc),
                'submission_id':
                comment.link_id.split('_')[1],
                'parent':
                comment.parent_id.split('_')[1],
                'retrieved_on':
                datetime.utcfromtimestamp(comment.retrieved_on)
            } for comment in self.__ps_api.search_comments(
                link_id=submission_id,
                after=datetime.strptime(after, "%d-%m-%Y") if after else None,
                before=datetime.strptime(before, "%d-%m-%Y"
                                         ) if before else None,
            )]

        if method == 'praw' or not comments:
            comments = [{
                'id':
                comment.id,
                'author_id':
                comment.author.name if comment.author else None,
                'body':
                comment.body,
                'score':
                comment.score,
                'created_at':
                datetime.utcfromtimestamp(comment.created_utc),
                'submission_id':
                comment._submission.id,
                'parent':
                comment.parent_id.split('_')[1],
                'retrieved_on':
                datetime.now()
            } for comment in self.__rd_socket.submission(
                id=submission_id).comments.replace_more(limit=0)]

        return comments

    def retrive_redditor_submissions(self,
                                     redditor,
                                     domain=None,
                                     before=None,
                                     after=None,
                                     method='psaw'):
        submission = None

        if method == 'psaw':
            submissions = [{
                'id':
                submission.id,
                'title':
                submission.title,
                'body':
                submission.selftext,
                'url':
                submission.url,
                'subreddit':
                submission.subreddit,
                'author':
                submission.author,
                'num_comments':
                submission.num_comments,
                'created_at':
                datetime.utcfromtimestamp(submission.created_utc),
                'retrieved_on':
                datetime.utcfromtimestamp(submission.retrieved_on)
            } for submission in self.__ps_api.search_submissions(
                author=redditor,
                domain=domain,
                before=datetime.strptime(before, "%d-%m-%Y"
                                         ) if before else None,
                after=datetime.strptime(after, "%d-%m-%Y") if after else None)]

        if method == 'praw' or not submissions:
            submissions = [
                {
                    'id': submission.id,
                    'title': submission.title,
                    'body': submission.selftext,
                    'url': submission.url,
                    'subreddit': submission.subreddit.display_name,
                    'author': submission.author.name,
                    'score': submission.score,
                    'num_comments': submission.num_comments,
                    'created_at':
                    datetime.utcfromtimestamp(submission.created_utc),
                    'retrieved_on': datetime.now()
                } for submission in self.__rd_socket.redditor(
                    redditor).submissions.new()
                if not domain or submission.subreddit.display_name in domain
            ]

        return submissions

    def redditor_info(self, redditor):
        redditor = self.__rd_socket.redditor(redditor)

        return {
            'name':
            getattr(redditor, 'name'),
            'submissions_karma':
            getattr(redditor, 'link_karma'),
            'comments_karma':
            getattr(redditor, 'comment_karma'),
            'created_at':
            datetime.utcfromtimestamp(getattr(redditor, 'created_utc'))
        }

    def subreddit_info(self, subreddit):
        subreddit = self.__rd_socket.subreddit(subreddit)

        return {
            'name': subreddit.display_name,
            'description': subreddit.description[:5000],
            'short_description': subreddit.public_description,
            'num_subscribers': subreddit.subscribers,
            'created_at': datetime.utcfromtimestamp(subreddit.created_utc),
            'last_update': datetime.now()
        }

    def update_comments_score(self, comments_id):

        num_comments = len(comments_id)
        for i, comment_id in enumerate(comments_id):
            try:
                comment = Comment.objects.get(id=comment_id)
                score = self.__rd_socket.comment(id=comment_id).score
                comment.score = int(score)
                comment.save()
            except Exception as ex:
                print(ex)
                pass

            if i % 500 == 0:
                print(f'{i+1}/{num_comments}')

        print(f'{i+1}/{num_comments}')

    def update_submissions_score(self, submissions_id):

        num_submissions = len(submissions_id)
        for i, submission_id in enumerate(submissions_id):
            submission = Submission.objects.get(id=submission_id)
            score = self.__rd_socket.submission(id=submission_id).score
            submission.score = score
            submission.save()

            if i % 500 == 0:
                print(f'{i+1}/{num_submissions}')

        print(f'{i+1}/{num_submissions}')

    def update_submissions_comments(self, submissions_ids=None):
        comments_ids = {comment.id for comment in Comment.objects.all()}
        authors_names = {author.name for author in RedditUser.objects.all()}
        submissions = Submission.objects.filter(
            id__in=submissions_ids
        ) if submissions_ids else Submission.objects.all()

        for i, submission in enumerate(submissions):
            print(f'{i}/{len(submissions)} - {submission.id}')
            comments = [
                comment
                for comment in self.retrive_submission_comments(submission.id)
                if comment['id'] not in comments_ids
            ]

            authors = [
                comment['author_id'] for comment in comments
                if comment['author_id'] not in authors_names
            ]

            authors_bulk = []
            for author in authors:
                try:
                    authors_bulk.append(
                        RedditUser(**self.redditor_info(author)))
                except Exception:
                    authors_bulk.append(RedditUser(name=author))

            RedditUser.objects.bulk_create(authors_bulk, ignore_conflicts=True)

            comments = [Comment(**comment) for comment in comments]
            Comment.objects.bulk_create(comments, ignore_conflicts=True)
Пример #21
0
class Reddit(metaclass=Singleton):
    def __init__(self):
        self.api = RedditApi(
            # username=REDDIT_USERNAME,
            # password=REDDIT_PASSWORD,
            client_id=REDDIT_CLIENTID,
            client_secret=REDDIT_CLIENTSECRET,
            user_agent=REDDIT_USERAGENT,
            refresh_token=REDDIT_REFRESH_TOKEN,
            redirect_uri=REDDIT_REDIRECT_URL,
        )

        if REDDIT_REFRESH_TOKEN:
            print(f'Logged in with: {self.api.user.me()}')
            return

        if not REDDIT_REFRESH_CODE:
            url = self.api.auth.url(['*'], 2205, 'permanent')
            print(
                f'Missing refresh token, go with {REDDIT_USERNAME} to the url:\n{url}'
            )
        else:
            refresh_token = self.api.auth.authorize(REDDIT_REFRESH_CODE)
            print(f'Code validated. Refresh token: {refresh_token}')

        sleep(15)
        exit()

    def inbox(self):
        return [
            message for message in self.api.inbox.unread()
            if not message.was_comment
        ][:5]

    def send(self, username, title, content):
        try:
            LOG.info(f'Sending to {username}: {title}')

            self.api.redditor(username).message(title, content)
            sleep(5)
        except:
            LOG.error(f'Error sending to {username}: {title}')

    def reply(self, message, content):
        try:
            LOG.info(f'Replying to {message.author.name}: {message.subject}')

            message.reply(content)
            message.mark_read()
            sleep(5)
        except Exception as e:
            LOG.error(
                f'Error replying to {message.author.name}: {message.subject} > {e}'
            )

    def usable(self, sub, country=None):
        if not sub:
            return False

        now = datetime.utcnow()

        try:
            submission = self.api.submission(id=sub.submission_id)

            if not submission.author or submission.author.name != REDDIT_USERNAME:
                LOG.info(f'Submission was deleted: {sub}')
                return False
            else:
                LOG.info(f'Submission wasnt deleted: {sub}')

            if submission.stickied and (now - sub.created_at).days < 170:
                LOG.info(f'Submission is stickied and not expired: {sub}')
                return True
            else:
                LOG.info(f'Submission isnt stickied or expired: {sub}')

            if now > sub.expires_at:
                LOG.info(f'Submission expired: {sub}')
                return False
            else:
                LOG.info(f'Submission is active: {sub}')

                if country:
                    LOG.info(
                        f'Submission is for a country, it will be reused: {sub}'
                    )
                    return True

            if now.today().weekday() not in [0,
                                             3]:  # now is not monday/thursday
                LOG.info(
                    f'Submission will be reused (not monday/thursday yet): {sub}'
                )
                return True
            else:
                LOG.info(
                    f'Submission might be replaced (monday/thursday): {sub}')

            if now.hour < 17:
                LOG.info(
                    f'Submission will be reused (not 17:00 UTC yet): {sub}')
                return True
            else:
                LOG.info(f'Submission might be replaced (17:00 UTC): {sub}')

            if sub.created_at.day != now.day:
                LOG.info(
                    f'Submission will be replaced (it\'s monday/thursday, my dudes): {sub}'
                )
                return False
            else:
                LOG.info(
                    f'Submission wont be replaced (already created one today): {sub}'
                )

        except Exception as e:
            LOG.error(
                f'Submission shows error {str(e)}, will be replaced: {sub}')
            return False

        LOG.info(f'Submission will be reused: {sub}')
        return True

    def create(self, subreddit, title, content):
        submission = self.api\
            .subreddit(subreddit)\
            .submit(title, selftext=content)

        # submission.disable_inbox_replies()

        self.update_flair(submission, subreddit)

        return submission.id

    def edit(self, sub, content):
        submission = self.api.submission(id=sub.submission_id)

        if not submission:
            return

        submission.edit(content)
        LOG.info(f'Submission updated: {sub}')

    def nsfw(self, sub):
        if not sub:
            return

        submission = self.api.submission(id=sub.submission_id)

        if not submission:
            return

        try:
            submission.mod.nsfw()
            LOG.info(f'Submission marked as NSFW: {sub}')
        except:
            LOG.error(f'Submission can\'t be marked as NSFW: {sub}')

    def update_flair(self, submission, subreddit):
        try:
            flair_text = FLAIRS.get(subreddit)

            if not flair_text:
                return
            else:
                flair_text = flair_text.lower()

            for flair in submission.flair.choices():
                if flair['flair_text'].lower() == flair_text:
                    submission.flair.select(flair['flair_template_id'])

                    LOG.info(f'Flair "{flair_text}" applied')
                    break
            else:
                LOG.warning(f'Flair "{flair_text}" not found for {subreddit}')
        except Exception as e:
            LOG.error(f'Flair for "{subreddit}" can\'t applied: {str(e)}')

    def submit(self, system, subreddit, title, content, country=None):
        subreddit = subreddit.lower()

        reddit_db = RedditDatabase()

        key = f'{system}/{country if country else subreddit}'
        sub = reddit_db.load(key)

        now = datetime.utcnow()

        if not self.usable(sub, country):
            self.nsfw(sub)

            sub = Submission(_id=key,
                             submission_id=self.create(subreddit, title,
                                                       content),
                             subreddit=subreddit,
                             system=system,
                             title=title,
                             days_to_expire=60 if country else 165)

            LOG.info(f'Submission created: {sub}')
        else:
            self.edit(sub, content)
            sub.updated_at = now

        sub.length = len(content)
        reddit_db.save(sub)

        sleep(5)

        return sub
def send_message_to_winners(  # noqa: C901
    reddit: Reddit,
    winners_list: List[str],
    reward_codes_list: List[str],
    version_string: str,
    gold_coin_reward: int,
):
    """
    Sends the winners list results to a list of recipients via Private Message (PM).

    This function uses recursion to send messages to failed recipients.

    This function also frequently encounters Reddit API Exceptions due to rate limits.
        To sleep for the appropriate duration without wasting time, the rate limit error is parsed:

    Test strings for regex capture:
    RATELIMIT: "Looks like you've been doing that a lot. Take a break for 4 minutes before trying again." on field 'ratelimit'
    RATELIMIT: "Looks like you've been doing that a lot. Take a break for 47 seconds before trying again." on field 'ratelimit'
    RATELIMIT: "Looks like you've been doing that a lot. Take a break for 4 minutes 47 seconds before trying again."
        on field 'ratelimit'
    RATELIMIT: "Looks like you've been doing that a lot. Take a break for 1 minute before trying again." on field 'ratelimit'

    Attributes:
        reddit: the PRAW Reddit instance
        winners_list: a list of winning recipients for the PM
        reward_codes_list: a list of reward codes for each winner
        version_string: the version of the patch notes
        gold_coin_reward: the number of Gold Coins intended for the reward
    """

    subject_line = f"Winner for the {version_string} Patch Notes Guessing Game"

    failed_recipients_list = []

    for recipient in winners_list:
        reward_code = (
            "N/A - all possible reward codes have been used up.\n\n"
            f"Please contact {STAFF_MEMBER_THAT_HANDS_OUT_REWARDS} for a code to be issued manually."
        )
        if len(reward_codes_list) > 0:
            reward_code = reward_codes_list[0]

        # TODO: Add this back if reward codes generator works again
        # message = (
        #     f"Congratulations {recipient}!\n\n"
        #     f"You have been chosen by the bot as a winner for the {version_string} Patch Notes Guessing Game!\n\n"
        #     f"Your reward code for {str(gold_coin_reward)} Gold Coins is: **{reward_code}**\n\n"
        #     "You can redeem your reward code here: https://www.heroesofnewerth.com/redeem/\n\n"
        #     f"Please contact {STAFF_MEMBER_THAT_HANDS_OUT_REWARDS} if any issues arise.\n\n"
        #     "Thank you for participating in the game! =)"
        # )

        message = (
            f"Congratulations {recipient}!\n\n"
            f"You have been chosen by the bot as a winner for the {version_string} Patch Notes Guessing Game!\n\n"
            f"Please contact /u/{STAFF_MEMBER_THAT_HANDS_OUT_REWARDS} via the Reddit Messaging system to obtain your code.\n\n"
            "Please include your In-Game Username in your message.\n\n"
            "Thank you for participating in the game! =)")
        try:
            reddit.redditor(recipient).message(subject=subject_line,
                                               message=message)
            tprint(
                f"Winner message sent to {recipient}, with code: {reward_code}"
            )

            # Pop reward code from list only if the message was sent successfully
            if len(reward_codes_list) > 0:
                reward_codes_list.pop(0)

        # Reddit API Exception
        except RedditAPIException as redditException:
            tprint(f"Full Reddit Exception: {redditException}\n\n")

            for subException in redditException.items:
                # Rate limit error handling
                if subException.error_type == "RATELIMIT":
                    failed_recipients_list.append(recipient)
                    tprint(
                        f"{redditException}\n{recipient} was not sent a message (added to retry list), "
                        "continuing to next recipient")
                    tprint(f"Subexception: {subException}\n\n")

                    # Sleep for the rate limit duration by parsing the minute and seconds count from
                    #   the message into named groups
                    regex_capture = re.search(
                        r"\s+((?P<minutes>\d+) minutes?)?\s?((?P<seconds>\d+) seconds)?\s+",
                        subException.message,
                    )
                    if regex_capture is None:
                        print(
                            "Invalid regex detected. Sleeping for 60 seconds..."
                        )
                        time.sleep(60)
                        break
                    else:
                        # Use named groups from regex capture and assign them to a dictionary
                        sleep_time_regex_groups = regex_capture.groupdict(
                            default=0)

                        # Add 1 extra second to account for millisecond-precision checking
                        secondsToSleep = (
                            60 * int(
                                sleep_time_regex_groups.get(
                                    "minutes")  # type: ignore
                            ) + int(
                                sleep_time_regex_groups.get(
                                    "seconds")  # type: ignore
                            ) + 1)  # type: ignore

                        print(f"Sleeping for {str(secondsToSleep)} seconds")
                        time.sleep(secondsToSleep)
                        break

            continue

        except Exception as error:
            tprint(
                f"{error}\n{recipient} was not sent a message (will not retry), continuing to next recipient"
            )
            continue

    # At the end of the function, recurse this function to re-send messages to failed recipients
    # Recurse only if failed_recipients_list has content in it
    # Prevents infinite loops by ensuring that the failed recipients count
    #   gradually progresses towards the end condition.
    failed_recipients = len(failed_recipients_list)
    if failed_recipients > 0 and failed_recipients < len(winners_list):
        send_message_to_winners(
            reddit,
            failed_recipients_list,
            reward_codes_list,
            version_string,
            gold_coin_reward,
        )