def reply(comment: Comment, recipient: str, sender: str, times_received: int, reward: str, image: str, retries: int = 5) -> None: msg = (f'#[Aquí está tu {reward}, /u/{recipient}!]({image} "{reward}")\n\n' f'/u/{recipient} recibió {reward} {times_received} ' f'{"vez" if times_received == 1 else "veces"}. ' f'(dado por /u/{sender}) \n' '[^^¿Qué ^^es ^^esto?](https://github.com/ElectrWeakHyprCharge/' 'chorimate/blob/master/Ayuda.md)') for _ in range(retries): try: comment.reply(msg) print('Reply:') print(msg.replace('\n', '\n | ')) except Exception as e: print('Reply error') print_exc() print('Retrying in 5 seconds...') sleep(5) else: break
def reply_to_comment(comment: Comment) -> bool: """Replies to a single comment""" # Don't reply to comments we've already replied to for reply in comment.replies.list(): if reply.author.name == args.username: log.info(f'Already replied to comment {comment.id}') return False # Mark the comment as replied to in cache replies_per_thread = cache.get( f'replies_per_submission:{comment.submission.id}') or 0 if replies_per_thread >= MAX_COMMENT_REPLIES_PER_SUBMISSION: log.info( f'Already replied to thread {comment.submission.id}; skipping') return False if not args.dry_run: cache.set(f'replies_per_submission:{comment.submission.id}', replies_per_thread + 1) log.info(f'Commenting on comment: {comment.permalink}') comment.reply(COMMENT.format(signature=comment.submission.id)) comment.upvote() return True else: log.info(f'Dry-run, skipping comment: {comment.permalink}') return False
def listing(comment: Comment, message_to: str) -> None: try: LISTING_TEMPLATE = fill_template( template=read_file("./template/LISTING.md"), replaced={ "author": comment.author.name, "floods": "\n".join([f"- {key}" for key in list(floods.keys())]), "mark": config["mark"], "command-base": config["commands"]["base"], "source-code": config["source-code"], "add-flood": f"https://www.reddit.com/message/compose?to=u/{message_to}&{urlencode(config['send-message']['add-flood'], quote_via=quote)}", "make-suggestion": f"https://www.reddit.com/message/compose?to=u/{message_to}&{urlencode(config['send-message']['make-suggestion'], quote_via=quote)}", "report-error": f"https://www.reddit.com/message/compose?to=u/{message_to}&{urlencode(config['send-message']['report-error'], quote_via=quote)}", }, ) if ( f"{config['mark']}{config['commands']['list']}" in comment.body and not comment.saved ): logger.info( f"{comment.body} from u/{comment.author} in r/{comment.subreddit}" ) logger.info(f"Replied to https://reddit.com{comment.permalink}") if PY_ENV == "production": comment.reply(LISTING_TEMPLATE) comment.save() elif PY_ENV == "development": logger.info(LISTING_TEMPLATE) except Exception: comment.unsave() logger.exception( f"Error occurred while replied to https://reddit.com{comment.permalink}" )
def replay_remove_pending_tip(reddit, tx_queue, failover_time): # check if user have pending tips list_tips = user_function.get_unregistered_tip() if list_tips: for arr_tip in list_tips: tip = models.Tip().create_from_array(arr_tip) bot_logger.logger.info("replay tipping check for %s" % str(tip.id)) # check if it's not too old & replay tipping if not tip.is_expired(): if tip.receiver.is_registered(): bot_logger.logger.info( "replay tipping %s - %s send %s to %s " % (str(tip.id), tip.sender.username, tip.amount, tip.receiver.username)) tip.tx_id = crypto.tip_user(tip.sender.address, tip.receiver.address, tip.amount, tx_queue, failover_time) if tip.tx_id: tip.finish = True user_function.remove_pending_tip(tip.id) if tip.message_fullname is not None: msg_id = re.sub(r't\d+_(?P<id>\w+)', r'\g<id>', tip.message_fullname) msg = Comment(reddit, msg_id) msg.reply( Template(lang.message_tip).render( sender=tip.sender.username, receiver=tip.receiver.username, amount=str(tip.amount), value_usd=str(tip.get_value_usd()), txid=tip.tx_id)) else: tip.status = "waiting registration of receiver" bot_logger.logger.info( "replay check for %s - user %s not registered " % (str(tip.id), tip.receiver.username)) else: tip.status = "receiver not registered in time" tip.finish = "" bot_logger.logger.info( "delete old tipping - %s send %s to %s " % (tip.sender.username, tip.amount, tip.receiver.username)) user_function.remove_pending_tip(tip.id) # update tip status history.update_tip(tip.sender.username, tip) history.update_tip(tip.receiver.username, tip) else: bot_logger.logger.info("no pending tipping")
def replay_remove_pending_tip(rpc, reddit, tx_queue, failover_time): # check if it's not too old & replay tipping limit_date = datetime.datetime.now() - datetime.timedelta(days=3) # check if user have pending tips list_tips = user_function.get_unregistered_tip() if list_tips: for tip in list_tips: bot_logger.logger.info("replay tipping check for %s" % str(tip['id'])) if (datetime.datetime.strptime(tip['time'], '%Y-%m-%dT%H:%M:%S.%f') > limit_date): if (user_function.user_exist(tip['receiver'])): bot_logger.logger.info( "replay tipping %s - %s send %s to %s " % (str(tip['id']), tip['sender'], tip['amount'], tip['receiver'])) txid = crypto.tip_user(rpc, tip['sender'], tip['receiver'], tip['amount'], tx_queue, failover_time) user_function.remove_pending_tip(tip['id']) value_usd = utils.get_coin_value(tip['amount']) if 'message_fullname' in tip.keys(): msg_id = re.sub(r't\d+_(?P<id>\w+)', r'\g<id>', tip['message_fullname']) msg = Comment(reddit, msg_id) msg.reply( Template(lang.message_tip).render( sender=tip['sender'], receiver=tip['receiver'], amount=str(tip['amount']), value_usd=str(value_usd), txid=txid)) else: bot_logger.logger.info( "replay check for %s - user %s not registered " % (str(tip['id']), tip['receiver'])) else: bot_logger.logger.info( "delete old tipping - %s send %s for %s " % (tip['sender'], tip['amount'], tip['receiver'])) user_function.remove_pending_tip(tip['id']) else: bot_logger.logger.info("no pending tipping")
def safe_comment_reply(self, comment: Comment, text_body: str): """ Attempts to reply to a comment & safely handles a RedditAPIException (e.g. if that comment has been deleted & cannot be responded to) Attributes: comment: a praw Comment model instance text_body: the markdown text to include in the reply made """ try: comment.reply(body=text_body) except RedditAPIException as redditErr: tprint(f"Unable to reply (RedditAPIException): {redditErr}") return None except Exception as err: tprint(f"Unable to reply (general Exception): {err}") return None
def test_reply(self): self.reddit.read_only = False with self.recorder.use_cassette("TestComment.test_reply"): parent_comment = Comment(self.reddit, "d1616q2") comment = parent_comment.reply("Comment reply") assert comment.author == self.reddit.config.username assert comment.body == "Comment reply" assert not comment.is_root assert comment.parent_id == parent_comment.fullname
def test_reply(self): self.reddit.read_only = False with self.recorder.use_cassette('TestComment.test_reply'): parent_comment = Comment(self.reddit, 'd1616q2') comment = parent_comment.reply('Comment reply') assert comment.author == self.reddit.config.username assert comment.body == 'Comment reply' assert not comment.is_root assert comment.parent_id == parent_comment.fullname
def flood(comment: Comment, message_to: str) -> None: try: FLOOD_TEMPLATE = fill_template( template=read_file("./template/FLOOD.md"), replaced={ "author": comment.author.name, "mark": config["mark"], "command-list": config["commands"]["list"], "source-code": config["source-code"], "add-flood": f"https://www.reddit.com/message/compose?to=u/{message_to}&{urlencode(config['send-message']['add-flood'], quote_via=quote)}", "make-suggestion": f"https://www.reddit.com/message/compose?to=u/{message_to}&{urlencode(config['send-message']['make-suggestion'], quote_via=quote)}", "report-error": f"https://www.reddit.com/message/compose?to=u/{message_to}&{urlencode(config['send-message']['report-error'], quote_via=quote)}", "flood": "{flood}", }, ) for key, value in floods.items(): if ( key.lower().strip() in comment.body and f"{config['mark']}{config['commands']['base']}" in comment.body and not comment.saved ): FLOOD_TEMPLATE = fill_template( template=FLOOD_TEMPLATE, replaced={"flood": value.strip()} ) logger.info( f"{comment.body} from u/{comment.author} in r/{comment.subreddit}" ) logger.info(f"Replied to https://reddit.com{comment.permalink}") # DEBUG: if PY_ENV == "production": comment.reply(FLOOD_TEMPLATE) elif PY_ENV == "development": logger.info(FLOOD_TEMPLATE) except Exception: comment.unsave() logger.exception( f"Error occurred while replied to https://reddit.com{comment.permalink}" )
def test_reply__none(self): self.reddit.read_only = False comment = Comment(self.reddit, "eear2ml") with self.recorder.use_cassette("TestComment.test_reply__none"): reply = comment.reply("TEST") assert reply is None
async def process_comment(comment: Comment, reddit: Reddit): # Mod override - if the user is a moderator, has "override" in his comment, and if not comment.is_root and comment.author.name in comment.subreddit.moderator() and "override" in comment.body: print("Moderator override activated.") # get rid of any override leftovers body = comment.body.replace('override', '').strip() url = extract_url(body) if not url: # no URL present, so we execute the british comment.reply("That doesn't seem to be a valid URL. Try again?" f'\n\n{config["suffix"]}') return if 'nhentai.net' in url: nums_regex = re.compile(r"https://nhentai\.net/g/(\d+)/") nums_match = nums_regex.match(url) nums = nums_match.group(1) try: magazine, market, data = await nhentai_fetcher.check_link(url) except Exception: print("Invalid page.") comment.reply("That doesn't seem to be a valid nhentai page. Try again?" f'\n\n{config["suffix"]}') return parodies = '' if len(data[3]) == 0 else f"**Parodies:** \n{', '.join(data[3])}\n\n" characters = '' if len(data[4]) == 0 else f"**Characters:** \n{', '.join(data[4])}\n\n" tags = '**Tags:** \nNone\n\n' if len(data[2]) == 0 else f"**Tags:** \n{', '.join(data[2])}\n\n" god_list = "" try: has_entry, entry = await wholesomelist_fetcher.process_nums(nums) if has_entry: print(entry) god_list = f"\\-\\-\\-\n\n[Wholesome Hentai God List #{entry['id']}](https://wholesomelist.com/list/{entry['uuid']}) \n" \ '\n' + (# f'**Tier: {entry["tier"]}**\n\n' + ( '' if (entry['note'] == 'None') else f'**Note:** {entry["note"]} \n') + \ f'**Tags:** ' + ('None' if len(entry["tags"]) == 0 else entry['tags']) + "\n\n" except Exception: god_list = "" comment.parent().edit( f"The source OP provided: \n> <{url}>\n\nAlt link: [cubari.moe](https://cubari.moe/read/nhentai/{nums}/1/1/)\n\n" f'**{markdown_escape(data[0])}** \nby {data[1] if data[1] else "Unknown"}\n\n{data[5]} pages\n\n{parodies}{characters}{tags}{god_list}' f'{config["suffix"]}' ) else: imgur = re.compile(r"https://imgur\.com/a/(.{5,7})/") imgur_match = imgur.match(url) if imgur_match: comment.parent().edit( f"The source OP provided: \n> <{url}>\n\nAlt link: [cubari.moe](https://cubari.moe/read/imgur/{imgur_match.group(1)}/1/1/)\n\n" f'{config["suffix"]}' ) else: comment.parent().edit( f"The source OP provided: \n> <{url}>\n\n" f'{config["suffix"]}' ) # The post is good. print('Updating database and cleaning up...') c.execute('DELETE FROM posts WHERE source=?', (url,)) approve_post(reddit, comment, url) # Reapprove the post if it was removed if comment.submission.removed: print("This post was removed. Reapproving...") comment.submission.mod.approve() else: print("This post was not removed. Ignoring...") # If this is the reply to a sauce request, handle it elif not comment.is_root and c.execute('SELECT * FROM pendingposts WHERE submission_id=?', (comment.submission.id,)).fetchone(): c.execute('SELECT * FROM pendingposts WHERE submission_id=?', (comment.submission.id,)) submission_id, author, comment_id = c.fetchone() # Normal handling if comment_id == comment.parent_id[3:] and author == comment.author.name: # It's a reply to a sauce request. print("Sauce reply found.") print(comment.body) # If there is no applicable source... if comment.body.lower() == 'none': comment.parent().edit( 'OP has indicated that there is no applicable source.' f'\n\n{config["suffix"]}' ) c.execute('DELETE FROM pendingposts WHERE submission_id=?', (comment.submission.id,)) conn.commit() comment.submission.mod.approve() return url = extract_url(comment.body) if not url: # no URL present, so we execute the british comment.reply("That doesn't seem to be a valid URL. Try again?" f'\n\n{config["suffix"]}') return # Handle any licensed sites here for site in licensed_sites: if site in url: print("It's a licensed site.") remove_post(reddit, comment, 'The link you provided links to a site that solely rips licensed content. As such, it breaks rule 4.\n\n' 'Please read our [guide on how to spot licensed doujins](https://www.reddit.com/r/wholesomehentai/wiki/licensedguide)' ' to avoid making this mistake in the future.', 'Licensed link', 'Rule 4 - Linked to hentai.cafe/hentainexus/hentaimimi', True ) return # Check if the post is a repost or not c.execute('SELECT * FROM commonreposts WHERE source=?', (url,)) is_common_repost = c.fetchone() if is_common_repost: # if this tuple even exists, it's uh oh stinky # It's a really common repost. Kill it. print('Common repost detected!') remove_post(reddit, comment, 'The link you provided is a **very common repost** on this subreddit.\n\n' 'Please read our [list of common reposts](https://www.reddit.com/r/wholesomehentai/wiki/posts), to avoid ' 'posting common reposts in the future.', 'Really common repost.', 'Rule 10 - Common Repost', True ) return # Check if the post is a repost that isn't that common c.execute('SELECT * FROM posts WHERE source=?', (url,)) post = c.fetchone() if post: print('Post found in repost database.') # It's a repost. # Check how recently it was reposted (604800 seconds/week) if comment.submission.created_utc - post[2] > (12 * 604800): # It's already been enough since this was last posted. # Delete the entry, and we'll add it back later. (With the current timestamp) c.execute('DELETE FROM posts WHERE source=?', (url,)) conn.commit() print('It\'s been long enough since this was last posted!') else: old_submission = reddit.submission(url=f'https://reddit.com{post[0]}') if (not old_submission.removed) and ((not (old_submission and old_submission.author)) or (old_submission.author.name == author)): # It's the same person. It's fine. print('OP is the same person. Ignoring...') c.execute('DELETE FROM posts WHERE source=?', (url,)) conn.commit() else: if post[3] != 0: print('It\'s a recently removed repost. Removing...') remove_post(reddit, comment, f'The link you provided has already been [posted and removed](https://reddit.com{post[0]}) recently.\n\n' 'Please check the previous post to see why it was removed. If you believe that the previous post was wrongly removed ' f'or some other exception has occurred, please [contact the mods](https://www.reddit.com/message/compose?to=/r/{config["subreddit"]}).\n\n' 'Otherwise, please make sure to [read the rules](https://reddit.com/r/wholesomehentai/wiki/rules) in the future.', 'Removed repost.', 'Reposting a removed post', True ) else: # It's not been long enough since the last post. Link them to the last post and delete the entry. print('It\'s a recent repost. Removing...') remove_post(reddit, comment, f'The link you provided has [already been posted](https://reddit.com{post[0]}) recently.\n\n' 'Please [check here](https://reddit.com/r/wholesomehentai/wiki/posts) before posting to avoid posting reposts in the future.', 'Repost.', 'Rule 10 - Repost', True ) return if 'nhentai.net' in url: # hoo boy print('nhentai URL detected, parsing info / magazines') if "nhentai.net/g/" not in url: comment.reply(f'That\'s not a valid nhentai page!\n\n{config["suffix"]}') return nums_regex = re.compile(r"https://nhentai\.net/g/(\d+)/") nums_match = nums_regex.match(url) nums = nums_match.group(1) for attempt in range(3): try: magazine, market, data = await nhentai_fetcher.check_link(url) break except Exception: if attempt == 2: print("Invalid page.") print(url) comment.reply("Either that isn't a valid nhentai page, or my connection to nhentai has a problem currently. Try again?" f'\n\n{config["suffix"]}') return else: await asyncio.sleep(1) if magazine: # It's licensed! print("Licensed magazine detected.") remove_post(reddit, comment, f'The provided source is licensed! It appears in the licensed magazine issue `{magazine}`.\n\n' f'Please [contact the mods](https://www.reddit.com/message/compose?to=/r/{config["subreddit"]}) if you think this is a mistake. Otherwise, please read the ' '[guide on how to spot licensed doujins.](https://www.reddit.com/r/wholesomehentai/wiki/licensedguide)', f'Licensed, appears in magazine {magazine}', f'Rule 4 - Licensed (appears in {magazine})', True ) return if market: # It literally has 2d-market.com in the title. print("2d-market in title.") remove_post(reddit, comment, f'The provided source is licensed! It has `2d-market.com` in the title.\n\n' f'Please [contact the mods](https://www.reddit.com/message/compose?to=/r/{config["subreddit"]}) if you think this is a mistake. Otherwise, please read the ' '[guide on how to spot licensed doujins.](https://www.reddit.com/r/wholesomehentai/wiki/licensedguide)', f'Licensed, has 2d-market.com in title', f'Rule 4 - Licensed (2d-market.com in title)', True ) return if "english" not in data[6]: print("The language of this doujin does not seem to be English.") remove_post(reddit, comment, 'The provided source does not seem to be in English.\n\n' 'This subreddit only allows English submissions, as most people cannot understand other languages.\n\n' f'If you believe this was a mistake, you can [contact the mods](https://www.reddit.com/message/compose?to=/r/{config["subreddit"]}).', 'Not English', 'Rule 2 - Non-English Source', False ) return detected_artists = [] for artist in licensed_artists: if artist in data[1].lower(): detected_artists.append(artist) if len(detected_artists) != 0: # Oh no, there's an illegal artist! print("Illegal artists detected: " + ', '.join(detected_artists)) remove_post(reddit, comment, f'The provided source has the following disallowed artists:\n```\n{", ".join(detected_artists)}\n```\n' 'These artists are banned because their works are always or almost always licensed. ' f'Please [contact the mods](https://www.reddit.com/message/compose?to=/r/{config["subreddit"]}) if you think this is ' 'an unlicensed exception. ' 'Otherwise, make sure you understand Rule 4.', f'Has the licensed artist(s): {", ".join(detected_artists)}', f'Rule 4 - Has the artists {", ".join(detected_artists)}', True ) return detected_tags = [] for tag in data[2]: if tag in unwholesome_tags: detected_tags.append(tag) if len(detected_tags) != 0: # Oh no, there's an illegal tag! print("Illegal tags detected: " + ', '.join(detected_tags)) remove_post(reddit, comment, f'The provided source has the following disallowed tags:\n```\n{", ".join(detected_tags)}\n```\n' 'These tags are banned because they are either almost never wholesome or almost always licensed. ' f'Please [contact the mods](https://www.reddit.com/message/compose?to=/r/{config["subreddit"]}) if you think this is either ' 'a mistagged doujin or a wholesome/unlicensed exception. ' 'Otherwise, make sure you understand Rules 1, 4, and 5.', f'Has the illegal tag(s): {", ".join(detected_tags)}', f'Rule 1/4/5 - Has the tags {", ".join(detected_tags)}', True ) return detected_characters = [] for character in data[4]: if character in underage_characters: cur_list = underage_characters[character] parodies = data[3] for parody in parodies: for item in cur_list: series_list = item['series'] for series in series_list: if series.lower().strip() == parody: detected_characters.append([character, series, item['age'], item['note']]) if len(detected_characters) != 0: # Oh no, there's an illegal character! chars_list = [] for character in detected_characters: chars_list.append(character[0]) chars_str = ', '.join(chars_list) print("Illegal characters detected: " + chars_str) remove_post(reddit, comment, f'The provided source has the following disallowed characters:\n\n{generate_character_string(detected_characters)}\n' 'These characters are banned because they are underage.\n\n' f'If you believe one of these characters is actually 18+ (because either the Note exception applies, or the mod team made a mistake), please [contact the mods](https://www.reddit.com/message/compose?to=/r/{config["subreddit"]}). ' 'Otherwise, make sure you understand Rule 1, and have checked our [spreadsheet of underage characters.](https://docs.google.com/spreadsheets/d/1rnTIzml80kQJPlNCQzluuKHK8Dzejk2Xg7J4YYN4FaM/)', f'Has the underage char(s): {chars_str}', f'Rule 1 - Has the chars {chars_str}', True ) return parodies = '' if len(data[3]) == 0 else f"**Parodies:** \n{', '.join(data[3])}\n\n" characters = '' if len(data[4]) == 0 else f"**Characters:** \n{', '.join(data[4])}\n\n" tags = '**Tags:** \nNone\n\n' if len(data[2]) == 0 else f"**Tags:** \n{', '.join(data[2])}\n\n" god_list = "" try: has_entry, entry = await wholesomelist_fetcher.process_nums(nums) if has_entry: print(entry) god_list = f"\\-\\-\\-\n\n[Wholesome Hentai God List #{entry['id']}](https://wholesomelist.com/list/{entry['uuid']}) \n" \ '\n' + ( # f'**Tier: {entry["tier"]}**\n\n' + ( '' if (entry['note'] == 'None') else f'**Note:** {entry["note"]} \n') + \ f'**Tags:** ' + ('None' if len(entry["tags"]) == 0 else entry['tags']) + "\n\n" except Exception: god_list = "" comment.parent().edit( f"The source OP provided: \n> <{url}>\n\nAlt link: [cubari.moe](https://cubari.moe/read/nhentai/{nums}/1/1/)\n\n" f'**{markdown_escape(data[0])}** \nby {data[1] if data[1] else "Unknown"}\n\n{data[5]} pages\n\n{parodies}{characters}{tags}{god_list}' f'{config["suffix"]}' ) else: imgur = re.compile(r"https://imgur\.com/a/(.{5,7})/") imgur_match = imgur.match(url) if imgur_match: comment.parent().edit( f"The source OP provided: \n> <{url}>\n\nAlt link: [cubari.moe](https://cubari.moe/read/imgur/{imgur_match.group(1)}/1/1/)\n\n" f'{config["suffix"]}' ) else: comment.parent().edit( f"The source OP provided: \n> <{url}>\n\n" f'{config["suffix"]}' ) # If we made it here, the post is good. Clean up any trackers and add a post entry to the database. print('Updating database and cleaning up...') approve_post(reddit, comment, url)
def run(self, comment: Comment): """Nightmare the given comment""" comment.reply(self.gen_reply(comment)) comment.downvote()