├── LICENSE ├── README.md └── markov_bot.py /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Josh Newlan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # markov_bot 2 | A python reddit bot for responding to mentions or comments with a markov chain sentence. 3 | Relies on Markovify. 4 | 5 | ### Usage 6 | ``` 7 | $ python markov_bot.py 8 | or 9 | $ python markov_bot.py --loop 10 | ``` 11 | Add the --loop argument to run the script as a while loop that checks for new 12 | mentions every minute. 13 | 14 | Otherwise the script checks for new mentions and comments once and replies. 15 | 16 | -------------------------------------------------------------------------------- /markov_bot.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from requests.auth import HTTPBasicAuth 3 | import markovify 4 | import time 5 | import sys 6 | 7 | """ 8 | Usage: 9 | $ python markov_bot.py 10 | or 11 | $ python markov_bot.py --loop 12 | 13 | Add the --loop argument to run the script as a while loop that checks for new 14 | mentions every minute. 15 | 16 | Otherwise the script checks for new mentions and comments once and replies. 17 | 18 | """ 19 | 20 | # Define some globals 21 | client_auth = HTTPBasicAuth('CLIENT_ID', 'CLIENT_SECRET') 22 | headers = {"User-Agent": "BOT_NAME/0.1 by /u/USER"} 23 | post_data = { 24 | "grant_type": "password", 25 | "username": "BOT_USERNAME", 26 | "password": "PASSWORD" 27 | } 28 | 29 | # A newline delimited text file 30 | markov_model_file = '/path/to/markov_file.txt' 31 | # A checkpoint file to avoid responding to mentions twice 32 | mentions_checkpoint = '/path/to/mentions.txt' 33 | # Used to check if we need to get a new auth token 34 | token_time = time.time() 35 | 36 | def authenticate(): 37 | # Collect new access_token 38 | response = requests.post("https://www.reddit.com/api/v1/access_token", 39 | auth=client_auth, data=post_data, headers=headers) 40 | token = response.json()['access_token'] 41 | token_time = time.time() 42 | return token 43 | 44 | def authenticated_request(url, token): 45 | # Make an authenticated get request using an access_token 46 | headers["Authorization"] = "bearer %s" % token 47 | if int(time.time() - token_time) < 3600: 48 | try: 49 | # Try with token 50 | response = requests.get("https://oauth.reddit.com/%s" % (url), 51 | headers=headers) 52 | except Exception, e: 53 | print "Error: ", e 54 | else: 55 | # Get a new token 56 | token = authenticate() 57 | headers["Authorization"] = "bearer %s" % token 58 | try: 59 | # Try again with new token 60 | response = requests.get("https://oauth.reddit.com/%s" % (url), 61 | headers=headers) 62 | except Exception, e: 63 | print "Error: ", e 64 | return response 65 | 66 | def initialize_model(text_file): 67 | # Load markovify model with a text file filepath 68 | with open('%s' % (text_file)) as f: 69 | text = f.read() 70 | text_model = markovify.Text(text) 71 | return text_model 72 | 73 | def generate_statement(text_model): 74 | comment = "" 75 | for i in range(3): 76 | sentence = text_model.make_short_sentence(140) 77 | comment += " " + sentence 78 | return comment 79 | 80 | def check_mail(token): 81 | # Returns a boolean. True if unread mail exists. 82 | me = authenticated_request("api/v1/me", token) 83 | return me.json()['has_mail'] 84 | 85 | def get_comments_and_mentions(token): 86 | # Returns all comments and mentions data 87 | mentions = authenticated_request("message/mentions.json",token) 88 | data = mentions.json()['data']['children'] 89 | time.sleep(1) 90 | comments = authenticated_request("message/comments.json",token) 91 | data += comments.json()['data']['children'] 92 | return data 93 | 94 | def process_mentions(mentions_data): 95 | # Returns a list of comment ids to reply to or and empty list 96 | # Appends new ids to previously replied file. 97 | 98 | comment_mentions = [] 99 | with open(mentions_checkpoint,'r') as f: 100 | previous_mentions = f.read().splitlines() 101 | print "Previous comments and mentions: ", previous_mentions 102 | 103 | mention_ids = [child['data']['id'] for child in mentions_data 104 | if child['data']['was_comment'] == True and 105 | child['data']['id'] not in previous_mentions] 106 | 107 | print "New mentions: ", mention_ids 108 | 109 | for mention in mention_ids: 110 | with open(mentions_checkpoint, "a") as f: 111 | f.write(mention + "\n") 112 | comment_mentions.append(mention) 113 | return comment_mentions 114 | 115 | def reply_to_mentions(comment_mentions,text_model): 116 | # Takes a list of comment thing_ids. 117 | # Prepends the thing code (t1) and comments as the authenticated user 118 | if int(time.time() - token_time) > 3600: 119 | token = authenticate() 120 | headers["Authorization"] = "bearer %s" % token 121 | 122 | for mention in comment_mentions: 123 | requests.post('https://oauth.reddit.com/api/comment', 124 | data={'parent':'t1_%s' % (mention), 125 | 'text': generate_statement(text_model)}, 126 | headers=headers) 127 | print "Replied to %s" % mention 128 | time.sleep(1) 129 | 130 | def mark_as_read(token): 131 | # Successful response code from post is 202. 132 | # If 429 received, request rate has exceeded api limits 133 | if int(time.time() - token_time) > 3600: 134 | token = authenticate() 135 | headers["Authorization"] = "bearer %s" % token 136 | 137 | r = requests.post('https://oauth.reddit.com/api/read_all_messages', 138 | headers=headers) 139 | 140 | if int(r.status_code) == 202: 141 | while check_mail(token) == True: 142 | print "Waiting until unread messages are cleared" 143 | time.sleep(1) 144 | print "Unread messages removed" 145 | elif int(r.status_code) == 429: 146 | print r.headers 147 | print "Rate limited, please wait 60 seconds" 148 | time.sleep(60) 149 | mark_as_read(token) 150 | 151 | def run_mark_loop(text_model): 152 | token = authenticate() 153 | while True: 154 | if check_mail(token): 155 | all_coms_and_mentions = get_comments_and_mentions(token) 156 | reply_mentions = process_mentions(all_coms_and_mentions) 157 | if len(reply_mentions) > 0: 158 | reply_to_mentions(reply_mentions,text_model) 159 | mark_as_read(token) 160 | else: 161 | print "Nothing found, sleeping for 60 seconds" 162 | time.sleep(60) 163 | 164 | def run_mark(text_model): 165 | token = authenticate() 166 | if check_mail(token): 167 | all_coms_and_mentions = get_comments_and_mentions(token) 168 | reply_mentions = process_mentions(all_coms_and_mentions) 169 | if len(reply_mentions) > 0: 170 | reply_to_mentions(reply_mentions,text_model) 171 | mark_as_read(token) 172 | else: 173 | print "No new comments or mentions found." 174 | 175 | def main(): 176 | text_model = initialize_model(markov_model_file) 177 | if len(sys.argv) == 2 and sys.argv[1] == '--loop': 178 | run_mark_loop(text_model) 179 | else: 180 | run_mark(text_model) 181 | 182 | if __name__ == '__main__': 183 | main() 184 | --------------------------------------------------------------------------------