├── .gitignore ├── filter_handler.py ├── filter_creation.py ├── unsub_notifications.py ├── check.py ├── Target_Manager.py ├── add_subscriber.py ├── Target.py ├── auto_subscriber.py ├── AchievementHandler.py └── oauth_notifications.py /.gitignore: -------------------------------------------------------------------------------- 1 | sn_info.cfg 2 | *.pyc 3 | filter-test/ -------------------------------------------------------------------------------- /filter_handler.py: -------------------------------------------------------------------------------- 1 | import filter_creation 2 | from Target import NameTarget 3 | 4 | def target_from_filter(name,filters): 5 | return NameTarget(name,filters['inc_filters'],filters['out_filters']) 6 | 7 | targets = {} 8 | def get_target(sub,name): 9 | global targets 10 | return targets[sub][name] 11 | 12 | def load_targets(): 13 | pass -------------------------------------------------------------------------------- /filter_creation.py: -------------------------------------------------------------------------------- 1 | def create_filter(inc,out): 2 | filters = {} 3 | filters['inc_filters'] = inc 4 | filters['out_filters'] = out 5 | return filters 6 | 7 | def get_default_inc(): 8 | filters = {} 9 | filters['not_user'] = ['automoderator','totesmessenger'] 10 | return filters 11 | 12 | def get_default_out(): 13 | filters = {} 14 | filters['karma'] = 1 15 | return filters -------------------------------------------------------------------------------- /unsub_notifications.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import pymongo 3 | import ConfigParser 4 | 5 | Config = ConfigParser.ConfigParser() 6 | Config.read('sn_info.cfg') 7 | 8 | client = pymongo.MongoClient(Config.get('Mongo Info','conn_str')) 9 | db = client[Config.get('Mongo Info','database')] 10 | coll = db[Config.get('Mongo Info','collection')] 11 | 12 | if(len(sys.argv) > 1): 13 | name = sys.argv[1] 14 | else: 15 | name = raw_input('Enter sub name: ') 16 | if(name[0:2]=='r/'): 17 | name = '/'+name 18 | elif(name[0:3]!='/r/'): 19 | name = '/r/'+name 20 | 21 | target = raw_input('Enter target name: ') 22 | 23 | coll.update({'sub':name},{'$pull' : {'filters':target}}) -------------------------------------------------------------------------------- /check.py: -------------------------------------------------------------------------------- 1 | import pymongo 2 | import sys 3 | import ConfigParser 4 | 5 | Config = ConfigParser.ConfigParser() 6 | Config.read('sn_info.cfg') 7 | 8 | client = pymongo.MongoClient(Config.get('Mongo Info','conn_str')) 9 | db = client[Config.get('Mongo Info','database')] 10 | coll = db[Config.get('Mongo Info','collection')] 11 | 12 | if(len(sys.argv) > 1): 13 | name = sys.argv[1] 14 | else: 15 | name = raw_input('Enter sub name: ') 16 | if(name[0:2]=='r/'): 17 | name = '/'+name 18 | elif(name[0:3]!='/r/'): 19 | name = '/r/'+name 20 | 21 | target = raw_input('Enter target name: ') 22 | 23 | c = coll.find_one({'sub':name}) 24 | if(c['filters'][target] != None): 25 | print c['filters'][target] 26 | else: 27 | print "That sub is not subscribed." -------------------------------------------------------------------------------- /Target_Manager.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | class TargetManager(): 4 | 5 | def __init__(self,comment,sub): 6 | self.comment = comment 7 | self.sub = sub 8 | self.targets = [] 9 | self.count = 0 10 | self.last_check = 0 11 | 12 | def add_target(self,target): 13 | self.targets+=[target] 14 | 15 | def get_targets(self): 16 | return self.targets 17 | 18 | def remove_target(self,target): 19 | try: 20 | self.targets.remove(target) 21 | except ValueError: 22 | pass 23 | 24 | def target_count(self): 25 | return len(self.targets) 26 | 27 | def increment(self): 28 | self.count+=1 29 | 30 | def get_count(self): 31 | return self.count 32 | 33 | def get_sub(self): 34 | return self.sub 35 | 36 | def get_comment(self): 37 | return self.comment 38 | 39 | def reset_time(self): 40 | self.last_check = time.time() 41 | 42 | def get_time(self): 43 | return self.last_check -------------------------------------------------------------------------------- /add_subscriber.py: -------------------------------------------------------------------------------- 1 | import ConfigParser 2 | import pymongo 3 | 4 | Config = ConfigParser.ConfigParser() 5 | Config.read('sn_info.cfg') 6 | 7 | client = pymongo.MongoClient(Config.get('Mongo Info','conn_str')) 8 | db = client[Config.get('Mongo Info','database')] 9 | coll = db[Config.get('Mongo Info','collection')] 10 | 11 | sub = raw_input('Enter sub name: ') 12 | if(sub[0:2]=='r/'): 13 | sub = '/'+sub 14 | elif(sub[0:3]!='/r/'): 15 | sub = '/r/'+sub 16 | 17 | name = raw_input('Enter subscriber name: ') 18 | 19 | karma = int(raw_input('Enter karma: ')) 20 | 21 | not_user = [] 22 | excluded = raw_input('Enter user to ignore: ') 23 | while(excluded!=''): 24 | not_user+=[excluded] 25 | excluded = raw_input('Enter user to ignore: ') 26 | 27 | not_subreddit = [] 28 | excluded = raw_input('Enter subreddit to ignore: ') 29 | while(excluded!=''): 30 | not_subreddit+=[excluded] 31 | excluded = raw_input('Enter subreddit to ignore: ') 32 | 33 | filters = {} 34 | inc_filters = {} 35 | out_filters = {} 36 | 37 | inc_filters['not_user'] = not_user 38 | inc_filters['not_subreddit'] = not_subreddit 39 | out_filters['karma'] = karma 40 | 41 | filters['inc_filters'] = inc_filters 42 | filters['out_filters'] = out_filters 43 | 44 | coll.find_one_and_update({'sub':sub},{'$set': {'filters.'+name : filters}}, upsert=True) -------------------------------------------------------------------------------- /Target.py: -------------------------------------------------------------------------------- 1 | class NameTarget(): 2 | #POSSIBLE INC_FILTERS# 3 | # by_user 4 | # not_user 5 | # in_subreddit 6 | # not_subreddit 7 | ###################### 8 | 9 | #POSSIBLE OUT_FILTERS# 10 | # karma 11 | ###################### 12 | 13 | def __init__(self,name,inc_filters,out_filters): 14 | self.name = name 15 | self.inc_filters = inc_filters 16 | self.out_filters = out_filters 17 | 18 | def check_inc(self,comment): 19 | for f in self.inc_filters.keys(): 20 | if(not self.pass_filter(f,self.inc_filters[f],comment)): 21 | return False 22 | return True 23 | 24 | def check_out(self,comment): 25 | for f in self.out_filters.keys(): 26 | if(not self.pass_filter(f,self.out_filters[f],comment)): 27 | return False 28 | return True 29 | 30 | def pass_filter(self,f,data,comment): 31 | if(f=='by_user'): 32 | return comment.author.name.lower() in list(map(lambda name: name.lower(), data)) 33 | if(f=='not_user'): 34 | return comment.author.name.lower() not in list(map(lambda name: name.lower(), data)) 35 | if(f=='in_subreddit'): 36 | return comment.subreddit.display_name.lower() in list(map(lambda name: name.lower(), data)) 37 | if(f=='not_subreddit'): 38 | return comment.subreddit.display_name.lower() not in list(map(lambda name: name.lower(), data)) 39 | if(f=='karma'): 40 | return comment.score >= data 41 | return True -------------------------------------------------------------------------------- /auto_subscriber.py: -------------------------------------------------------------------------------- 1 | # oauth PRAW template by /u/The1RGood # 2 | #==================================================Config stuff==================================================== 3 | import configparser 4 | import time, praw 5 | import webbrowser 6 | import pymongo 7 | import json 8 | from flask import Flask, request 9 | from threading import Thread 10 | 11 | Config = configparser.ConfigParser() 12 | Config.read('sn_info.cfg') 13 | 14 | client = pymongo.MongoClient(Config.get('Mongo Info','conn_str')) 15 | db = client[Config.get('Mongo Info','database')] 16 | coll = db[Config.get('Mongo Info','collection')] 17 | subs = {} 18 | access_information = None 19 | scope = "identity privatemessages read" 20 | 21 | access_information = '' 22 | scope = "identity privatemessages submit read" #SET THIS. SEE http://praw.readthedocs.org/en/latest/pages/oauth.html#oauth-scopes FOR DETAILS. 23 | #==================================================End Config====================================================== 24 | #==================================================OAUTH APPROVAL================================================== 25 | app = Flask(__name__) 26 | 27 | CLIENT_ID = Config.get('Reddit Access','cid') 28 | CLIENT_SECRET = Config.get('Reddit Access','csec') 29 | REDIRECT_URI = 'http://127.0.0.1:65010/authorize_callback' 30 | 31 | def kill(): 32 | func = request.environ.get('werkzeug.server.shutdown') 33 | if func is None: 34 | raise RuntimeError('Not running with the Werkzeug Server') 35 | func() 36 | return "Shutting down..." 37 | 38 | @app.route('/authorize_callback') 39 | def authorized(): 40 | global access_information 41 | state = request.args.get('state', '') 42 | code = request.args.get('code', '') 43 | access_information = r.get_access_information(code) 44 | user = r.get_me() 45 | text = 'Bot successfully started.' 46 | kill() 47 | return text 48 | 49 | def refresh_access(): 50 | while(True): 51 | time.sleep(1800) 52 | r.refresh_access_information(access_information['refresh_token']) 53 | 54 | r = praw.Reddit('OAuth2 SubNotifications Subscriber Bot') 55 | r.set_oauth_app_info(CLIENT_ID, CLIENT_SECRET, REDIRECT_URI) 56 | webbrowser.open(r.get_authorize_url('DifferentUniqueKey',scope,True)) 57 | app.run(debug=False, port=65010) 58 | #amt = Thread(target=refresh_access,args=()) 59 | #amt.daemon=True 60 | #amt.start() 61 | #==================================================END OAUTH APPROVAL-============================================= 62 | def push_to_seen(m): 63 | seen.insert(0,m) 64 | if(len(seen)>100): 65 | seen.pop() 66 | 67 | print('Buffering old mail...') 68 | seen = [] 69 | mail = r.get_messages(limit=50) 70 | for m in mail: 71 | push_to_seen(m.name) 72 | 73 | print('Scanning.') 74 | running = True 75 | err_count = 0 76 | while(running): 77 | try: 78 | r.refresh_access_information(access_information['refresh_token'],update_session=True) 79 | mail = r.get_messages(limit=25) 80 | for m in mail: 81 | if(m.name not in seen): 82 | push_to_seen(m.name) 83 | if m.subject == "Action: Unsubscribe" and m.parent_id == None: 84 | body = None 85 | try: 86 | body = json.loads(m.body) 87 | target = ("/r/" + m.subreddit.display_name) if (m.subreddit != None) else (m.author.name) 88 | print("Unsubscribing " + target + " from " + body['subreddit']) 89 | coll.update({'sub':"/r/"+body['subreddit'].lower()},{'$unset' : {'filters.'+target:""}}) 90 | m.reply("You have been successfully unsubscribed.") 91 | except: 92 | print("Error parsing unsubscribe request.") 93 | m.reply("There was an error processing your request. Please check the JSON syntax and try again.\n\nIf you cannot resolve the problem, please message /u/The1RGood.") 94 | elif m.subject == "Action: Subscribe" and m.parent_id == None: 95 | body = None 96 | try: 97 | body = json.loads(m.body) 98 | 99 | filters = {} 100 | inc_filters = {} 101 | out_filters = {} 102 | 103 | inc_filters['not_user'] = body['filter-users'] 104 | inc_filters['not_subreddit'] = body['filter-subreddits'] 105 | out_filters['karma'] = body['karma'] 106 | 107 | filters['inc_filters'] = inc_filters 108 | filters['out_filters'] = out_filters 109 | 110 | print("Filters made") 111 | 112 | target = ("/r/" + m.subreddit.display_name) if (m.subreddit != None) else (m.author.name) 113 | print("Subscribing " + target + " to " + body['subreddit']) 114 | coll.find_one_and_update({'sub':"/r/"+body['subreddit'].lower()},{'$set': {'filters.'+target : filters}}, upsert=True) 115 | m.reply("You have been successfully subscribed.") 116 | except: 117 | print("Error parsing subscribe request.") 118 | m.reply("There was an error processing your request. Please check the JSON syntax and try again.\n\nIf you cannot resolve the problem, please message /u/The1RGood.") 119 | except KeyboardInterrupt: 120 | print("Break.") 121 | break 122 | except Exception as e: 123 | err_count+=1 124 | print(err_count) 125 | print(e) -------------------------------------------------------------------------------- /AchievementHandler.py: -------------------------------------------------------------------------------- 1 | class AchievementHandler(): 2 | 3 | def __init__(self,name,sub,coll): 4 | self.coll = coll 5 | self.name = name 6 | self.sub = sub 7 | 8 | def get_record(self): 9 | record = coll.find_one({"name":self.name,"sub":self.sub}) 10 | if(record): 11 | return record 12 | else: 13 | record = { 14 | "name":self.name, 15 | "sub":self.sub, 16 | "notifications":0, 17 | "replies":0, 18 | "config":0, 19 | "mentions":{}, 20 | "gilded":False, 21 | "mod":False, 22 | "admin":False, 23 | "controversial":False 24 | } 25 | return coll.insert_one(record) 26 | 27 | def save_record(self,record): 28 | coll.update({"name":self.name,"sub":self.sub},record,upsert=False) 29 | 30 | def user_mention(self,user): 31 | record = get_record() 32 | try: 33 | record['mentions']['user']+=1 34 | except KeyError: 35 | record['mentions']['user'] = 1 36 | save_record(record) 37 | value = record['mentions']['user'] 38 | if(value==10): 39 | return { 40 | 'type':'Single-User', 41 | 'name':"Obsession I", 42 | 'description': "/u/"+user+" has mentioned your subreddit "+str(value)+" times." 43 | } 44 | elif(value==25): 45 | return { 46 | 'type':'Single-User', 47 | 'name':"Obsession II", 48 | 'description': "/u/"+user+" has mentioned your subreddit "+str(value)+" times." 49 | } 50 | elif(value==50): 51 | return { 52 | 'type':'Single-User', 53 | 'name':"Obsession III", 54 | 'description': "/u/"+user+" has mentioned your subreddit "+str(value)+" times." 55 | } 56 | elif(value==100): 57 | return { 58 | 'type':'Single-User', 59 | 'name':"Obsession IV", 60 | 'description': "/u/"+user+" has mentioned your subreddit "+str(value)+" times." 61 | } 62 | else: 63 | return None 64 | 65 | 66 | def add_notification(self): 67 | record = get_record 68 | record['notifications']+=1 69 | save_record(record) 70 | value = record['notifications'] 71 | if(value==10): 72 | return { 73 | 'type':'Notifications', 74 | 'name':'Ears Burning I', 75 | 'description':"You have received "+str(value)+" notifications." 76 | } 77 | elif(value==100): 78 | return { 79 | 'type':'Notifications', 80 | 'name':'Ears Burning II', 81 | 'description':"You have received "+str(value)+" notifications." 82 | } 83 | elif(value==1000): 84 | return { 85 | 'type':'Notifications', 86 | 'name':'Ears Burning III', 87 | 'description':"You have received "+str(value)+" notifications." 88 | } 89 | elif(value==5000): 90 | return { 91 | 'type':'Notifications', 92 | 'name':'Ears Burning IV', 93 | 'description':"You have received "+str(value)+" notifications." 94 | } 95 | else: 96 | return None 97 | 98 | def add_reply(self): 99 | record = get_record() 100 | record['replies']+=1 101 | save_record(record) 102 | #Check value and return possible achievement 103 | value = record['replies'] 104 | if(value==5): 105 | return { 106 | 'type':'Reply', 107 | 'name':'Chatty I', 108 | 'description':"You have sent "+str(value)+" replies to subreddit notifications." 109 | } 110 | elif(value==50): 111 | return { 112 | 'type':'Reply', 113 | 'name':'Chatty II', 114 | 'description':"You have sent "+str(value)+" replies to subreddit notifications." 115 | } 116 | elif(value==500): 117 | return { 118 | 'type':'Reply', 119 | 'name':'Chatty III', 120 | 'description':"You have sent "+str(value)+" replies to subreddit notifications." 121 | } 122 | elif(value==5000): 123 | return { 124 | 'type':'Reply', 125 | 'name':'Chatty IV', 126 | 'description':"You have sent "+str(value)+" replies to subreddit notifications." 127 | } 128 | elif(value==10000): 129 | return { 130 | 'type':'Reply', 131 | 'name':'Chatty V', 132 | 'description':"You have sent "+str(value)+" replies to subreddit notifications." 133 | } 134 | else: 135 | return None 136 | 137 | def add_config(self): 138 | record = get_record() 139 | record['config']+=1 140 | save_record(record) 141 | #Check value and return possible achievement 142 | value = record['config'] 143 | if(value==5): 144 | return { 145 | 'type':'Config', 146 | 'name':'Stay With The Times I', 147 | 'description':"You have updated the config "+str(value)+" times." 148 | } 149 | elif(value==10): 150 | return { 151 | 'type':'Config', 152 | 'name':'Stay With The Times II', 153 | 'description':"You have updated the config "+str(value)+" times." 154 | } 155 | elif(value==50): 156 | return { 157 | 'type':'Config', 158 | 'name':'Stay With The Times III', 159 | 'description':"You have updated the config "+str(value)+" times." 160 | } 161 | elif(value==100): 162 | return { 163 | 'type':'Config', 164 | 'name':'Stay With The Times IV', 165 | 'description':"You have updated the config "+str(value)+" times." 166 | } 167 | else: 168 | return None 169 | 170 | 171 | def flag_gilded(self): 172 | record = get_record() 173 | if(not record['gilded']): 174 | record['gilded'] = True 175 | save_record(record) 176 | #Return achievement 177 | return { 178 | 'type':'Gild', 179 | 'name':'Golden Mention', 180 | 'description':'Your subreddit has been mentioned in a gilded comment.' 181 | } 182 | 183 | def flag_mod(self): 184 | record = get_record() 185 | if(not record['mod']): 186 | record['mod'] = True 187 | save_record(record) 188 | return { 189 | 'type':'Mod', 190 | 'name':'Mod Mention', 191 | 'description':'Your subreddit has been mentioned by a distinguished moderator comment.' 192 | } 193 | 194 | def flag_admin(self): 195 | record = get_record() 196 | if(not record['admin']): 197 | record['admin'] = True 198 | save_record(record) 199 | #Return achievement 200 | return { 201 | 'type':'Admin', 202 | 'name':'Serious Business', 203 | 'description':'Your subreddit has been mentioned by a distinguished administrator comment.' 204 | } 205 | 206 | def flag_controversial(self): 207 | record = get_record() 208 | if(not record['controversial']): 209 | record['controversial'] = True 210 | save_record(record) 211 | #Return achievement 212 | return { 213 | 'type':'Controversial', 214 | 'name':'Not The Best PR', 215 | 'description':'Your subreddit has been mentioned in a controversial comment.' 216 | } -------------------------------------------------------------------------------- /oauth_notifications.py: -------------------------------------------------------------------------------- 1 | # oauth_notifications.py # 2 | # Written by /u/The1RGood # 3 | ########################### 4 | #==================================================Config stuff==================================================== 5 | import configparser 6 | import time 7 | import praw, prawcore 8 | import pymongo 9 | import webbrowser 10 | from threading import Timer 11 | from threading import Thread 12 | from flask import Flask, request 13 | from filter_handler import * 14 | from Target import * 15 | from Target_Manager import * 16 | import json 17 | import sys, traceback 18 | import re 19 | 20 | Config = configparser.ConfigParser() 21 | Config.read('sn_info.cfg') 22 | 23 | client = pymongo.MongoClient(Config.get('Mongo Info','conn_str')) 24 | db = client[Config.get('Mongo Info','database')] 25 | coll = db[Config.get('Mongo Info','collection')] 26 | scope = "identity privatemessages read" 27 | 28 | app = Flask(__name__) 29 | 30 | CLIENT_ID = Config.get('Reddit Access','cid') 31 | CLIENT_SECRET = Config.get('Reddit Access','csec') 32 | REDIRECT_URI = Config.get('Reddit Access','callback') 33 | REFRESH_TOKEN = '' 34 | #==================================================End Config====================================================== 35 | 36 | def kill(): 37 | func = request.environ.get('werkzeug.server.shutdown') 38 | if func is None: 39 | raise RuntimeError('Not running with the Werkzeug Server') 40 | func() 41 | return("Shutting down...") 42 | 43 | @app.route('/authorize_callback') 44 | def authorized(): 45 | global REFRESH_TOKEN 46 | code = request.args.get('code','') 47 | try: 48 | REFRESH_TOKEN = r.auth.authorize(code) 49 | except: 50 | traceback.print_exc(file=sys.stdout) 51 | text = 'Bot started on /u/' + r.user.me().name 52 | kill() 53 | return text 54 | 55 | #==================================================Botting Functions=============================================== 56 | #Update list of subs subscribed to notifications about them 57 | def fetch_subscribed(): 58 | global subs 59 | n_subscribed = {} 60 | c = coll.find() 61 | for e in c: 62 | targets = {} 63 | for s in e['filters'].keys(): 64 | targets[s] = target_from_filter(s,e['filters'][s]) 65 | n_subscribed[e['sub']] = targets 66 | subs = n_subscribed 67 | 68 | #Check if a comment mentioning a sub meets the threshold 69 | #comment = get_comment 70 | #sub = get_sub 71 | #targets = get_targets 72 | #count = increment / get_count 73 | def check_comment(target_manager): 74 | #Logging 75 | try: 76 | print("Checking comment: "+target_manager.get_comment().permalink) 77 | except: 78 | traceback.print_exc(file=sys.stdout) 79 | try: 80 | if(target_manager.get_count()>0): 81 | target_manager.get_comment().refresh() 82 | except Exception as e: 83 | try: 84 | print("Dropping comment: "+target_manager.get_comment().permalink) 85 | print("Could not refresh.") 86 | traceback.print_exc(file=sys.stdout) 87 | except: 88 | pass 89 | return True 90 | #If it's been edited, drop it 91 | if(not mentions_sub(target_manager.get_comment().body.lower(),target_manager.get_sub()[1:])): 92 | try: 93 | print("Dropping comment: "+target_manager.get_comment().permalink) 94 | print("Reason: Edited.") 95 | except: 96 | pass 97 | return True 98 | #If the threshold is met: 99 | to_remove = [] 100 | for t in target_manager.get_targets(): 101 | try: 102 | if subs[t[0]][t[1]].check_out(target_manager.get_comment()): 103 | print("Sending notification about "+target_manager.get_sub()) 104 | title = '[Notification] Your subreddit has been mentioned in /r/' + target_manager.get_comment().subreddit.display_name+'!' 105 | body = 'Author: /u/'+target_manager.get_comment().author.name +'\n\n['+target_manager.get_comment().submission.title+']('+target_manager.get_comment().permalink+'?context=3)\n\n___\n\n'+target_manager.get_comment().body+'\n\n___\n\n[What is this?](https://www.reddit.com/r/SubNotifications/comments/3dxono/general_information/) | [Contact My Creator](https://www.reddit.com/message/compose/?to=The1RGood&subject=Sub%20Notifications%20Bot) | [Latest Update](https://www.reddit.com/r/SubNotifications/new?limit=1)' 106 | #Notify the sub 107 | send_message(subs[t[0]][t[1]].name,title,body) 108 | to_remove += [t] 109 | except: 110 | traceback.print_exc(file=sys.stdout) 111 | to_remove += [t] 112 | 113 | for t in to_remove: 114 | target_manager.remove_target(t) 115 | 116 | if(target_manager.target_count()>0 and target_manager.get_count()<12): 117 | target_manager.increment() 118 | return False 119 | else: 120 | try: 121 | print("Dropping comment: "+target_manager.get_comment().permalink) 122 | print("Reason: All targets notified or expired.") 123 | except: 124 | traceback.print_exc(file=sys.stdout) 125 | return True 126 | 127 | #This bit is to avoid repeated comment checking. 128 | seen_mail = [] 129 | def push_to_mail(comment): 130 | global seen_mail 131 | seen_mail.insert(0,comment) 132 | if(len(seen_mail)>10000): 133 | seen_mail.pop() 134 | 135 | #This makes sure a sub notification is accurate, and not part of the name of a different sub 136 | def mentions_sub(body,sub): 137 | result = (sub in body) 138 | if(result): 139 | result &= ((body.find(sub) + len(sub) == len(body)) or not (body[body.find(sub) + len(sub)].isalnum() or body[body.find(sub) + len(sub)]=='_')) 140 | if(result): 141 | result &= ((body.find(sub)==0) or not (body[body.find(sub)-1].isalnum() or body[body.find(sub) - 1]=='_')) 142 | return result 143 | 144 | def handle_mail(): 145 | regex = "[a-zA-Z0-9\s_-]*" 146 | mail = r.inbox.messages(limit=25) 147 | for m in mail: 148 | if(m.name not in seen_mail): 149 | push_to_mail(m.name) 150 | if m.subject == "Action: Unsubscribe" and m.parent_id == None: 151 | print("Received Unsubscribe Action") 152 | body = None 153 | try: 154 | body = json.loads(m.body) 155 | if(not re.fullmatch(regex,body['subreddit'])): 156 | m.reply('Unable to parse subreddit. Please double-check the subreddit(s) being unsubscribed from.') 157 | return 158 | 159 | target = ("/r/" + m.subreddit.display_name.lower()) if (m.subreddit != None) else (m.author.name) 160 | print("Unsubscribing " + target + " from " + body['subreddit']) 161 | subreddits = body['subreddit'].split(' ') 162 | for s in subreddits: 163 | coll.update({'sub':"/r/"+body['subreddit'].lower()},{'$unset' : {'filters.'+target:""}}) 164 | names = "" 165 | for s in subreddits: 166 | names+=s+" \n" 167 | m.reply("You have been successfully unsubscribed from the following subreddit" + (':\n\n' if len(subreddits)==1 else 's:\n\n') + names) 168 | except: 169 | traceback.print_exc(file=sys.stdout) 170 | print("Error parsing unsubscribe request.") 171 | m.reply("There was an error processing your request. Please check the JSON syntax and try again.\n\nIf you cannot resolve the problem, please message /u/The1RGood.") 172 | elif m.subject == "Action: Subscribe" and m.parent_id == None: 173 | print("Received Subscribe Action") 174 | body = None 175 | try: 176 | body = json.loads(m.body.replace('\n', '')) 177 | if(not re.fullmatch(regex,body['subreddit'])): 178 | m.reply('Unable to parse subreddit. Please double-check the subreddit(s) being subscribed to.') 179 | return 180 | 181 | filters = {} 182 | inc_filters = {} 183 | out_filters = {} 184 | 185 | inc_filters['not_user'] = body['filter-users'] 186 | inc_filters['not_subreddit'] = body['filter-subreddits'] 187 | out_filters['karma'] = body['karma'] 188 | 189 | filters['inc_filters'] = inc_filters 190 | filters['out_filters'] = out_filters 191 | 192 | print("Filters made") 193 | 194 | target = ("/r/" + m.subreddit.display_name.lower()) if (m.subreddit != None) else (m.author.name) 195 | print("Subscribing " + target + " to " + body['subreddit']) 196 | subreddits = body['subreddit'].split(' ') 197 | for s in subreddits: 198 | coll.find_one_and_update({'sub':"/r/"+s.lower()},{'$set': {'filters.'+target : filters}}, upsert=True) 199 | names = "" 200 | for s in subreddits: 201 | names+=s+" \n" 202 | m.reply("You have been successfully subscribed to the following subreddit" + (':\n\n' if len(subreddits)==1 else 's:\n\n') + names) 203 | except: 204 | traceback.print_exc(file=sys.stdout) 205 | print("Error parsing subscribe request.") 206 | m.reply("There was an error processing your request. Please check the JSON syntax and try again.\n\nIf you cannot resolve the problem, please message /u/The1RGood.") 207 | 208 | def handle_comments(): 209 | to_remove = [] 210 | for c in active_comments: 211 | if((c.get_time == 0) or (c.get_time() < (time.time() - 3600))): 212 | c.reset_time() 213 | result = check_comment(c) 214 | if(result): 215 | to_remove+=[c] 216 | for c in to_remove: 217 | active_comments.remove(c) 218 | 219 | def call_delay_repeat(function,args,delay=5): 220 | while(True): 221 | try: 222 | function(*args) 223 | except (prawcore.exceptions.Forbidden, prawcore.exceptions.ServerError): 224 | refresh_client() 225 | except: 226 | traceback.print_exc(file=sys.stdout) 227 | refresh_client() 228 | time.sleep(delay) 229 | 230 | def send_message(target,title,body): 231 | data = { 232 | 'to': target, 233 | 'subject': title, 234 | 'text': body 235 | } 236 | r.post(praw.const.API_PATH['compose'],data) 237 | 238 | def refresh_client(): 239 | global r 240 | r = praw.Reddit( 241 | client_id=CLIENT_ID, 242 | client_secret=CLIENT_SECRET, 243 | refresh_token=REFRESH_TOKEN, 244 | user_agent='Sub Mentions general usage client', 245 | api_request_delay=0) 246 | 247 | 248 | #==================================================End Botting Functions=========================================== 249 | 250 | #==================================================Globals========================================================= 251 | active_comments = [] #Comments matching incoming filters 252 | subs = {} #Structure of subs and their filter criteria 253 | #================================================================================================================== 254 | r = praw.Reddit( 255 | client_id=CLIENT_ID, 256 | client_secret=CLIENT_SECRET, 257 | redirect_uri=REDIRECT_URI, 258 | user_agent='Sub Mentions general usage client', 259 | api_request_delay=0) 260 | print(r.auth.url(scope.split(' '),'UniqueKey')) 261 | app.run(host="0.0.0.0",debug=False, port=65010) 262 | #================================================================================================================== 263 | def main(): 264 | global subs, active_comments 265 | mail = r.inbox.messages(limit=25) 266 | for m in mail: 267 | push_to_mail(m.name) 268 | 269 | print('Bot Starting') 270 | mail_thread = Thread(target=call_delay_repeat, daemon=True, args=(handle_mail,())) 271 | subs_thread = Thread(target=call_delay_repeat, daemon=True, args=(fetch_subscribed,(),10)) 272 | comm_thread = Thread(target=call_delay_repeat, daemon=True, args=(handle_comments,(),0)) 273 | mail_thread.start() 274 | subs_thread.start() 275 | comm_thread.start() 276 | 277 | while(True): 278 | try: 279 | for c in r.subreddit('all').stream.comments(): 280 | for n in subs.keys(): 281 | if mentions_sub(c.body.lower(),n[1:]) and c.subreddit.display_name.lower() != n[3:]: 282 | tm = TargetManager(c,n) 283 | for t in subs[n].keys(): 284 | if(subs[n][t].check_inc(c)): 285 | print("Comment found mentioning "+n) 286 | tm.add_target([n,t]) 287 | if(tm.target_count()>0): 288 | active_comments += [tm] 289 | print("Reached end of comment stream... Somehow.") 290 | except KeyboardInterrupt: 291 | print("Stopping.") 292 | break 293 | except (prawcore.exceptions.Forbidden, prawcore.exceptions.ServerError): 294 | print('Auth Failed. Refreshing Client.') 295 | refresh_client() 296 | except: 297 | traceback.print_exc(file=sys.stdout) 298 | refresh_client() 299 | #pass 300 | 301 | if __name__ == '__main__': 302 | main() 303 | --------------------------------------------------------------------------------