├── .gitignore ├── README.md ├── LICENSE.txt └── redditagain.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.csv 2 | *.py[cod] 3 | 4 | # C extensions 5 | *.so 6 | 7 | # Packages 8 | *.egg 9 | *.egg-info 10 | dist 11 | build 12 | eggs 13 | parts 14 | bin 15 | var 16 | sdist 17 | develop-eggs 18 | .installed.cfg 19 | lib 20 | lib64 21 | 22 | # Installer logs 23 | pip-log.txt 24 | 25 | # Unit test / coverage reports 26 | .coverage 27 | .tox 28 | nosetests.xml 29 | 30 | # Translations 31 | *.mo 32 | 33 | # Mr Developer 34 | .mr.developer.cfg 35 | .project 36 | .pydevproject 37 | 38 | *.komodoproject 39 | .komodotools/ 40 | 41 | .DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | RedditAgain 2 | =========== 3 | 4 | Migrate an old Reddit account to a new one. 5 | 6 | Features 7 | =========== 8 | 9 | * Easy to use 10 | * No change in source code required 11 | * Fast 12 | * Saves all comments and submissions in csv files. 13 | * Deletes all comments and submissions 14 | * Creates a new reddit account 15 | * Copies subreddit subscriptions from old account to new account 16 | 17 | Usage 18 | ======= 19 | 20 | $ python redditagain.py 21 | 22 | 23 | Donations 24 | ============= 25 | 26 | If RedditAgain has helped you in any way, and you'd like to help the developer, please consider donating. 27 | 28 | **- BTC: [19dLDL4ax7xRmMiGDAbkizh6WA6Yei2zP5](http://i.imgur.com/bAQgKLN.png)** 29 | 30 | **- Flattr: [https://flattr.com/profile/thekarangoel](https://flattr.com/profile/thekarangoel)** 31 | 32 | 33 | Contribute 34 | ======== 35 | 36 | If you want to add any new features, or improve existing ones, feel free to send a pull request! 37 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2013 Karan Goel 3 | 4 | Permission is hereby granted, free of charge, to any person 5 | obtaining a copy of this software and associated documentation 6 | files (the "Software"), to deal in the Software without restriction, 7 | including without limitation the rights to use, copy, modify, 8 | merge, publish, distribute, sublicense, and/or sell copies of the 9 | Software, and to permit persons to whom the Software is furnished 10 | to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 19 | BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 20 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. -------------------------------------------------------------------------------- /redditagain.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys 4 | import time 5 | import csv 6 | import os 7 | import getpass 8 | 9 | import praw 10 | 11 | USER_AGENT = 'RedditAgain by /u/karangoeluw // github: karan' 12 | 13 | 14 | def print_dot(): 15 | """Prints out a dot on the same line when called""" 16 | sys.stdout.write('. ') 17 | sys.stdout.flush() 18 | 19 | def csv_file(fp, header): 20 | """Create or append a CSV file.""" 21 | if os.path.exists(fp): 22 | f = open(fp, 'ab') 23 | writer = csv.writer(f) 24 | else: 25 | f = open(fp, 'wb') 26 | writer = csv.writer(f) 27 | writer.writerow(header) 28 | 29 | return f, writer 30 | 31 | def format_time(created): 32 | return time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(created)) 33 | 34 | # Ask the user for a decision. 'y' and 'Y' will evaluate to True, anything else will equate to False. 35 | # A better implementation is welcome, it's just a quick-n-dirty slap-together job! -ChainsawPolice 36 | def y_or_n(decision): 37 | if decision.lower() == 'y': 38 | return True 39 | else: 40 | return False 41 | 42 | 43 | if __name__ == '__main__': 44 | print '>> Login to OLD account..' 45 | 46 | old_r = praw.Reddit(USER_AGENT) # praw.Reddit 47 | old_r.login() 48 | 49 | print '\t>>Login successful..' 50 | old_user = old_r.user # get a praw.objects.LoggedInRedditor object 51 | 52 | print 'Would you like to remove all your old comments? (y/n)' 53 | if y_or_n(raw_input('> ')) == True: 54 | print '>> Saving and editing all comments...' 55 | 56 | comment_file, comment_csv = csv_file( 57 | '{}_comments.csv'.format(old_user.name), 58 | ['Comment', "Posted on", "Thread"]) 59 | 60 | with comment_file: 61 | removed = 1 62 | while removed > 0: # keep going until everything is gone 63 | removed = 0 64 | for com in old_user.get_comments(limit=None): 65 | link = com.submission.permalink.encode('utf-8') 66 | body = com.body.encode('utf-8') 67 | row = [body, format_time(com.created), link] 68 | try: 69 | comment_csv.writerow(row) 70 | com.edit('.') 71 | removed += 1 72 | print_dot() 73 | except Exception as e: 74 | print 'Failed to store', link 75 | print e 76 | print '\n\t>> Saved to {0}_comments.csv'.format(old_user.name) 77 | 78 | print 'Would you like to remove all your old submissions? (y/n)' 79 | if y_or_n(raw_input('> ')) == True: 80 | print '>> Saving and editing all submissions...' 81 | submission_header = ['Title', "Body/Link", "Created", "Karma"] 82 | submission_file, submission_csv = csv_file( 83 | '{}_submissions.csv'.format(old_user.name), 84 | submission_header) 85 | 86 | with submission_file: 87 | removed = 1 88 | while removed > 0: # keep going until everything is gone 89 | removed = 0 90 | for sub in old_user.get_submitted(limit=None): 91 | if sub.is_self: 92 | submission = sub.selftext.encode('utf-8') 93 | else: 94 | submission = sub.url.encode('utf-8') 95 | title = sub.title.encode('utf-8') 96 | row = [title, submission, format_time(sub.created), sub.score] 97 | try: 98 | submission_csv.writerow(row) 99 | sub.edit('.') 100 | removed += 1 101 | print_dot() 102 | except Exception as e: 103 | print 'Failed to store', submission 104 | print e 105 | print '\n\t>> Saved to {0}_submissions.csv'.format(old_user.name) 106 | 107 | print '>> Preparing to migrate subscriptions.' 108 | subs = old_r.get_my_subreddits(limit=None) 109 | 110 | new_r = praw.Reddit(USER_AGENT) 111 | new_username = raw_input('>> Enter username of new account: ') 112 | 113 | while True: 114 | new_pass = getpass.getpass( 115 | '\t>> Enter password for `{}`: '.format(new_username)) 116 | new_pass2 = getpass.getpass('\t>> Retype password to confirm: ') 117 | if new_pass != new_pass2: 118 | print 'Passwords do not match!' 119 | else: 120 | break 121 | 122 | # create the new account, if available 123 | if new_r.is_username_available(new_username): 124 | new_r.create_redditor(new_username, new_pass) 125 | 126 | new_r.login(new_username, new_pass) 127 | 128 | new_user = new_r.user # get a praw.objects.LoggedInRedditor object 129 | print '\t>>Login successful..' 130 | 131 | print '>> Migrating subscriptions...' 132 | for sub in subs: 133 | new_r.get_subreddit(sub.display_name).subscribe() 134 | old_r.get_subreddit(sub.display_name).unsubscribe() 135 | print_dot() 136 | print '\n\t>> Done migrating.' 137 | 138 | print '>> Go to https://ssl.reddit.com/prefs/delete/', 139 | print 'to delete your old account.' --------------------------------------------------------------------------------