import logging , os , praw , re , time , traceback , sys , urllib2 , requests. exceptions ;

linked = [ ] ;

linkedsrc = [ ] ;

skipped = [ ] ;

skippedsrc = [ ] ;

linkedcount = 0 ;

errorcount = 0 ;

TESTING = False ;

ARCHIVE_TIME = 15778463 ; # currently 6 months (in seconds)

CRASH_TIMER = 60 ;

CJ_HEADER = u """This dank meme has been linked to from another place on le reddit.""" ;

HEADER = u """This thread has been linked to from another place on reddit.""" ;

FOOTER = u """[](#footer)*^If ^you ^follow ^any ^of ^the ^above ^links, ^respect ^the ^rules ^of ^reddit ^and ^don't ^vote. ^ \( [Info](/r/TotesMessenger/wiki/) ^/ ^[Contact](/message/compose/?to= \/ r \/ TotesMessenger))* [](#bot)""" ;

user = os . environ [ 'REDDIT_USER' ] ;

blacklist = [ "anime" , "asianamerican" , "askhistorians" , "askscience" , "aww" , "benfrick" , "bmw" , "chicagosuburbs" ,

"cosplay" , "cumberbitches" , "d3gf" , "deer" , "depression" , "depthhub" , "drinkingdollars" ,

"forwardsfromgrandma" , "futurology" , "geckos" , "giraffes" , "graphical_design" , "grindsmygears" ,

"indianfetish" , "misc" , "mixedbreeds" , "news" , "newtotf2" , "omaha" , "petstacking" , "pigs" ,

"politicaldiscussion" , "programmingcirclejerk" , "raerthdev" , "rants" , "salvia" , "science" ,

"seiko" , "shoplifting" , "sketches" , "sociopath" , "suicidewatch" , "talesfromtechsupport" , "unitedkingdom" ] ;

# Do not edit

srcblacklist = [ "depression" , "lifeafternarcissists" , "managedbynarcissists" , "moderationlog" , "raisedbynarcissists" ,

"rbnathome" , "rbnbookclub" , "rbnchildcare" , "rbnfavors" , "rbngames" , "rbnlifeskills" , "rbnmovienight" ,

"rbnrelationships" , "rbnspouses" , "suicidewatch" , "switcharoo" , "switcheroo" , "trolledbynarcissists" ,

"unremovable" , "politic" , "mlplite" , "risingthreads" , "uncensorship" , "leagueofriot" , "benlargefanclub" ,

"fitnesscirclejerk" , "taiwancirclejerk" , "requestedtweaks" , "jaxbrew" , "floridabrew" , "aggregat0r" ,

"gamecollectingjerk" , "technews2015" ] ;

banned = [ "reddit.com" , "minecraft" , "adviceanimals" , "askreddit" , "worldnews" , "femradebates" , "pcmasterrace" ,

"purplepilldebate" , "slrep" , "funny" , "theredpill" , "personalfinance" , "india" , "lifehacks" , "kotakuinaction" ,

"askmen" , "smashbros" , "android" , "neutralpolitics" , "dota2" , "wet_shavers" , "dogecoin" , "askphilosophy" ,

"suits" , "japanlife" , "photography" , "hiphopheads" , "apple" , "lifeprotips" , "nba" , "dbz" , "gender_critical" ,

"movies" ] ;

blockedusers = [ "amprobablypooping" , "evilrising" , "frontpagewatch" , "frontpagewatchmirror" , "moon-done" , "politicbot" ,

"rising_threads_bot" , "removal_rover" , "know_your_shit" , "drugtaker" , "nedsc" ] ;

# metabots = [user, "totesmessenger", "totes_meta_bot", "meta_bot", "meta_bot2", "originallinkbot"];

# Ban list:

# drugtaker - Meta bot NSFW marking evasion

# NedSc - By request

nsfwreddits = [ "srsshillwatch" , "srsshillswatch" , "srshillswatch" , "srshillwatch" , "gonewild" ] ;

test_reddits = [ "justcool393" , "tmtest" , "totesmessenger" ] ;

def main ( ) :

global linked ;

global linkedsrc ;

global skipped ;

global skippedsrc ;

global linkedcount ;

global errorcount ;

create_files ( ) ;

linked = load_list ( "linked.lst" ) ;

linkedsrc = load_list ( "linkedsrc.lst" ) ;

skipped = load_list ( "skipped.lst" ) ;

skippedsrc = load_list ( "skippedsrc.lst" ) ;

r = praw. Reddit ( "Links to reddit posts from other places in reddit" , domain = "api.reddit.com" , log_requests = 0 ) ;

r. login ( user , os . environ [ 'REDDIT_PASS' ] ) ;

logging . info ( "Logged in to reddit..." ) ;

add_linked ( r ) ;

logging . info ( "Linked: " + str ( len ( linked ) ) + ", source: " + str ( len ( linkedsrc ) ) ) ;

check_at = 3600 ;

# save_at = 60;

last_logged = 0 ;

# last_saved = 0;

times_zero = 1 ;

link_subs ( r , 100 , 120 ) ; # Check the last 100 posts on startup

while True :

'''

if time.time() >= (last_saved + save_at):

last_saved = time.time();

save_list("linked.lst", linked);

save_list("linkedsrc.lst", linkedsrc);

save_list("skipped.lst", skipped);

save_list("skippedsrc.lst", skippedsrc);

''' # Saving is disabled due to our host works.

if time . time ( ) >= ( last_logged + check_at ) :

last_logged = time . time ( ) ;

if linkedcount == 0 :

times_zero + = 1 ;

else :

logging . info ( "Last " + str ( ( check_at * times_zero ) / 60 / 60 ) + " hr(s): Linked " + str ( linkedcount )

+ ", " + str ( errorcount ) + " failed." ) ;

# logging.info("Linked " + str(count) + " in the last " + str((check_at * times_zero) / 60 / 60) + " hour(s)");

linkedcount = 0 ;

errorcount = 0 ;

times_zero = 1 ;

link_subs ( r , 25 , 60 ) ;

def add_linked ( r ) :

for c in r. user . get_comments ( sort = 'new' , limit = None ) :

pid = c. parent_id ;

if pid is None :

continue ;

if pid not in linked:

linked. append ( pid ) ;

# Add linkedsrc one in to one method.

posts = re . findall ( "http://np.reddit.com/r/.{1,20}/comments/.{1,8}/" , c. body ) ;

for p in posts:

linkedsrc. append ( re . sub ( "http://np.reddit.com/r/.{1,20}/comments/" , "" , p ) [ :- 1 ] ) ;

def create_files ( ) :

f = open ( "linked.lst" , "a" ) ;

f. close ( ) ;

f = open ( "linkedsrc.lst" , "a" ) ;

f. close ( ) ;

f = open ( "skipped.lst" , "a" ) ;

f. close ( ) ;

f = open ( "skippedsrc.lst" , "a" ) ;

f. close ( ) ;

def link_subs ( r , count , delay ) :

global linkedcount ;

global errorcount ;

for submission in r. get_domain_listing ( 'reddit.com' , sort = 'new' , limit = count ) :

if TESTING and submission. subreddit . display_name . lower ( ) not in test_reddits:

continue ;

try :

if link_submission ( r , submission ) :

linkedcount + = 1 ;

time . sleep ( 3 ) ;

else :

errorcount + = 1 ;

except ( requests. exceptions . HTTPError , requests. exceptions . ConnectionError ) as ex:

logging . error ( str ( ex ) ) ;

errorcount + = 1 ;

time . sleep ( 10 ) ;

time . sleep ( delay ) ;

def link_submission ( r , submission ) :

url = re . sub ( "( \# | \? ).{1,}" , "" , submission. url ) ;

if not is_comment ( url ) :

return False ;

linkedp = None ;

try :

linkedp = get_object ( r , url ) ;

except praw. errors . ClientException :

logging . error ( "Link is not a reddit post (id: " + submission. id + ")" ) ;

logging . error ( exi ( ) ) ;

return False ;

except ( requests. exceptions . HTTPError , requests. exceptions . ConnectionError ) as ex:

logging . error ( str ( ex ) ) ;

time . sleep ( 5 ) ;

return False ;

except Exception :

logging . error ( "Could not get comment!" ) ;

logging . error ( exi ( ) ) ;

if linkedp is None :

return False ;

lid = linkedp. id ;

sid = submission. id ;

# Skip conditions: Already deleted, undelete/scraper/mod log bots, blacklisted, banned/archived,

# archived, in source blacklist

if linkedp. subreddit is None or linkedp. author is None :

skipped. append ( lid ) ;

return False ;

srlower = linkedp. subreddit . display_name . lower ( ) ;

if srlower in blacklist or srlower in banned or linkedp. created < ( time . time ( ) - ARCHIVE_TIME ) :

skipped. append ( lid ) ;

return False ;

if linkedp. subreddit . user_is_banned :

skipped. append ( lid ) ;

return False ;

if submission. subreddit . display_name . lower ( ) in srcblacklist or submission. author is None :

skippedsrc. append ( sid ) ;

return False ;

if submission. author . name . lower ( ) in blockedusers:

skippedsrc. append ( sid ) ;

return False ;

if lid in skipped or sid in skippedsrc:

return False ;

if sid in linkedsrc:

return False ;

if lid in linked or check_commmented ( linkedp ) or get_bot_comment ( linkedp ) is not None :

success = edit_post ( get_bot_comment ( linkedp ) , submission ) ;

linkedsrc. append ( sid ) ;

if lid not in linked:

linked. append ( lid ) ;

return success ;

cj = srlower == "circlejerk" ; # check if our subreddit is /r/circlejerk so we can user our specialized msg for it

if isinstance ( linkedp , praw. objects . Comment ) :

comment ( linkedp , submission , cj ) ;

elif isinstance ( linkedp , praw. objects . Submission ) :

post ( linkedp , submission , cj ) ;

else :

logging . error ( "Not a Comment or Submission! (ID: " + lid + ")" ) ;

return False ;

linked. append ( lid ) ;

linkedsrc. append ( sid ) ;

return True ;

def edit_post ( totessubmission , original ) :

if totessubmission is None :

return False ;

text = re . sub ( " \[ \] \( #footer \) .{1,}" , "" , totessubmission. body ) ; # sub. invisible link for easier footer changes

text = re . sub ( " \* \^ If.{1,}" , "" , text ) ;

text = re . sub ( " \^ Please.{1,}" , "" , text ) ; # substitute old footer as well

text = re . sub ( "Do not vote.{1,}" , "" , text ) ; # substitute original footer as well

text = text + format_link ( original ) + u """

""" + FOOTER ;

totessubmission. edit ( text ) ;

return True ;

def get_comment ( r , s ) :

return get_linked ( r , s ) . comments [ 0 ] ;

def get_linked ( r , link ) :

return r. get_submission ( link ) ;

def check_commmented ( c ) :

if isinstance ( c , praw. objects . Comment ) :

comments = c. replies ;

elif isinstance ( c , praw. objects . Submission ) :

c. replace_more_comments ( limit = None , threshold = 0 ) ;

comments = praw. helpers . flatten_tree ( c. comments ) ;

for co in comments:

if co. author is None :

continue ;

if co. author . name . lower ( ) == user . lower ( ) :

return True ;

return False ;

def get_bot_comment ( s ) :

if isinstance ( s , praw. objects . Comment ) :

for c in s. replies :

if c. author is None :

continue ;

if c. author . name . lower ( ) == user . lower ( ) :

return c ;

else :

s. replace_more_comments ( limit = None , threshold = 0 ) ;

flat_comments = praw. helpers . flatten_tree ( s. comments ) ;

for c in flat_comments:

if c. author is None :

continue ;

if c. author . name . lower ( ) == user . lower ( ) :

return c ;

return None ;

def format_comment ( original , isrcirclejerk ) :

if isrcirclejerk:

cmt = CJ_HEADER ;

else :

cmt = HEADER ;

cmt = cmt + u """

{link}

""" + FOOTER ;

return cmt. format ( link = format_link ( original ) ) ;

def post ( s , original , isrcirclejerk ) :

try :

s. add_comment ( format_comment ( original , isrcirclejerk ) ) ;

except praw. errors . RateLimitExceeded :

logging . debug ( "Can't comment (comment karma is too low)" ) ;

except praw. errors . APIException as e:

logging . warning ( str ( e ) ) ;

except Exception :

logging . error ( "Error adding comment (SID: " + str ( s. id ) + ")" ) ;

logging . error ( exi ( ) ) ;

def comment ( c , original , isrcirclejerk ) :

try :

c. reply ( format_comment ( original , isrcirclejerk ) ) ;

except praw. errors . RateLimitExceeded :

logging . debug ( "Can't comment (comment karma is too low)" ) ;

except praw. errors . APIException as e:

logging . warning ( str ( e ) ) ;

except Exception as e:

logging . error ( "Error adding comment (CID: " + str ( c. id ) + ")" ) ;

logging . error ( str ( e ) ) ;

def format_link ( post ) :

srurl = post. subreddit . url ;

nsfw = post. subreddit . name . lower ( ) in nsfwreddits or post. subreddit . over18 or post. over_18 ;

text = u "- [" + srurl [ :- 1 ] + "] " ;

if nsfw:

text = text + u "[NSFW] " ;

return text + u "[" + post. title + "](" + np ( post. permalink ) + ")

" ;

def changesubdomain ( link , sub ) :

l = re . sub ( r "http[s]?://[a-z]{0,3} \. ?[a-z]{0,2} \. ?reddit \. com" , "" , link ) ;

return "http://" + sub + ".reddit.com" + l ;

def unnp ( link ) :

return changesubdomain ( link , "www" ) ;

def np ( link ) :

return changesubdomain ( link , "np" ) ;

def get_cid ( url ) :

l = re . sub ( r "http[s]?://[a-z]{0,3} \. ?reddit \. com/r/.{1,20}/comments/.{6,8}/.*/" , "" , url ) ;

l = re . sub ( r " \? .*" , "" , l ) ;

l = re . sub ( r " \. .*" , "" , l ) ;

return "t1_" + l ;

def get_object ( r , url ) :

url = unnp ( url ) ;

obj = praw. objects . Submission . from_url ( r , url ) ;

a = re . compile ( "http[s]?://[a-z]{0,3} \. ?reddit \. com/r/.{1,20}/comments/.{6,8}/.*/.{6,8}" ) ;

if a. match ( url ) :

o = r. get_info ( thing_id = get_cid ( url ) ) ;

if o is None :

logging . error ( "Not a comment! (URL: " + url + ")" ) ;

return o ;

else :

return obj ;

def is_comment ( link ) :

a = re . compile ( "http[s]?://[a-z]{0,3} \. ?[a-z]{0,2} \. ?reddit \. com/r/.{1,20}/comments/.*" ) ;

return a. match ( link ) ;

def load_list ( file ) :

f = open ( file , "r" ) ;

data = f. read ( ) ;

f. close ( ) ;

return data. split ( ) ;

def save_list ( file , list ) :

f = open ( file , "wb" ) ;

str = "" ;

for s in list :

str = str + s + " " ;

f. write ( str ) ;

f. close ( ) ;

def log_crash ( ) :

logging . error ( "Details: " ) ;

logging . error ( exi ( ) ) ;

logging . error ( "Unhandled exception. Restarting in " + str ( CRASH_TIMER ) + " seconds..." ) ;

time . sleep ( CRASH_TIMER ) ;

sys . exit ( 1 ) ;

def exi ( ) :

return traceback . format_exc ( ) ;

def setup_logging ( ) :

root = logging . getLogger ( ) ;

root. setLevel ( logging . INFO ) ;

logging . getLogger ( "requests" ) . setLevel ( logging . WARNING ) ;

ch = logging . StreamHandler ( sys . stdout ) ;

ch. setLevel ( logging . INFO ) ;

root. addHandler ( ch ) ;

try :

setup_logging ( ) ;

main ( ) ;

except ( AttributeError , NameError , SyntaxError , TypeError , UnboundLocalError ) as e:

logging . error ( "Crash due to syntactical error!" ) ;

logging . error ( exi ( ) ) ;

time . sleep ( 86400 ) ; # Sleep for 1 day so we don't restart.

except Exception :