Summary
I’m an avid reddit user, constantly searching for new subreddits to subscribe to in my areas of my interests. Having the massive user base that it does, there are various extensions and addons for reddit to enhance its capabilities. However, an area lacking attention was the backup and restoration of subscribed subreddits.
For example, if I decided to change my reddit username, I’d have to manually subscribe to each subreddit of my previous account. There’s a somewhat simple hands on workaround available, however, an automated approach offers a few additional benefits.
So I decided to build a python script with the following features:
- backup – saves a list of subreddit subscriptions to a file
- restore – subscribes to subreddits listed in backup file, unsubscribing from current subreddits
- clear – unsubscribes from all subreddits in an account
- merge – subscribes to subreddits listed in backup file, keeping current subscriptions.
Subreddits are backed up into a text file named subreddits.txt in the same directory of the script – this is also the file that subreddits are restored from. Feel free to rename the file if saving subreddits from multiple accounts, but ensure you rename it back to subreddits.txt when looking to restore. The file itself contains each subreddit stored line by line – starting with the subreddit’s ID, then the URL separated by “|”.
When ran, the script asks the user what function they would like to perform. After the user inputs the function as well as their credentials, the script logs into their account and begins backing up, clearing, restoring or merging subreddits. If the subreddits.txt is missing or blank during a restore, the script will alert the user of such and end the session.
Installation Details
Requires Python 2.7 with Beautiful Soup 4 and Requests libraries installed
Known Bugs
Not a bug, but this script will break should reddit alter the format of the login + subreddit manipulation post requests
Source Code
# Created by Michael Fessenden (MikeFez) # Requires Python 2.7 # Must have beautifulsoup4 & requests installed # -*- coding: utf-8 -*- # Downloaded Packages import requests import requests.utils # Used for receiving webpage responses import bs4 # Used to parse webpage import os session = requests.session() headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko'} # Logs into SF def main(): print("Please Choose An Option:") print("[1] backup\t- saves a list of subreddit subscriptions to a file") print("[2] restore\t- subscribes to subreddits listed in backup file, unsubscribing from current subreddits") print("[3] clear\t- unsubscribes from all subreddits in an account") print("[4] merge\t- subscribes to subreddits listed in backup file, keeping current subscriptions.") print("\n") run_type = int(raw_input("Please enter the number of the option you'd like: ")) while run_type < 1 or run_type > 4: print("Unexpected input!") run_type = int(raw_input("Please enter the number of the option you'd like: ")) logged_in = False while logged_in is False: logged_in = login() if run_type == 1: # backup if confirm("Warning - This will overwrite previous backup - are you sure?"): backup() elif run_type == 2: # restore if confirm("Warning - This will clear your current subreddits before restoring - are you sure?"): if file_check(): clear() restore() elif run_type == 3: # clear if confirm("Warning - This will clear your current subreddits - are you sure?"): clear() elif run_type == 4: # merge if confirm("Warning - This will merge your current subreddits with those backed up - are you sure?"): restore() return def confirm(message): state = False option = raw_input(message + " (y/n): ") while option != "y" and option != "n": print("Unexpected input!") option = raw_input(message + " (y/n): ") if option == "y": state = True elif option == "n": print("Canceling...") return state def file_check(): state = False try: if os.stat("subreddits.txt").st_size > 0: state = True else: print "Warning - Backup file (subreddits.txt) was found, but is empty!" print("Canceling...") except OSError: print "Warning - No backup file (subreddits.txt) was found!" print("Canceling...") return state # Logs into SF def login(): username = raw_input("Enter your reddit username: ") var = getpass.getpass() payload = { "dest": "https://www.reddit.com/", "user": username, "passwd": password, "rem": "", "op": "login" } res = session.post("https://www.reddit.com/api/login/" + username, data=payload, headers=headers) res.raise_for_status() success = False if "WRONG_PASSWORD" not in res.text.encode("ascii"): print "Successfully Logged In\n" success = True else: print "Error - Wrong Password or Username! Please try again." return success def backup(): res = session.get("https://www.reddit.com/subreddits/mine", headers=headers) res.raise_for_status() sub_result = bs4.BeautifulSoup(res.text, "html.parser") sub_box = sub_result.find("div", {"class": "subscription-box"}) sub_items = sub_box.findAll('li') sub_dict = {} for li in sub_items: ban_check = li.find('span', {"class": "title banned"}) if ban_check is None: id_raw = li.find('a', href=True, text='unsubscribe') id_raw = id_raw["onclick"].split("('") id_raw = id_raw[1].split("')") sub_id = id_raw[0] sub_link = li.find('a', {"class": "title"}) sub_dict[sub_id] = sub_link['href'] with open("subreddits.txt", 'w+') as f: for sub_id, sub_link in sub_dict.items(): print("Saving to file: " + sub_link) f.write(sub_id + '|' + sub_link + '\n') return def clear(): res = session.get("https://www.reddit.com/subreddits/mine", headers=headers) res.raise_for_status() sub_result = bs4.BeautifulSoup(res.text, "html.parser") modhash_raw = sub_result.find("input", {"name": "uh"}) modhash = modhash_raw["value"] sub_box = sub_result.find("div", {"class": "subscription-box"}) sub_items = sub_box.findAll('li') sub_dict = {} for li in sub_items: id_raw = li.find('a', href=True, text='unsubscribe') id_raw = id_raw["onclick"].split("('") id_raw = id_raw[1].split("')") sub_id = id_raw[0] sub_link = li.find('a', {"class": "title"}) sub_dict[sub_id] = sub_link['href'] for sub_id, sub_link in sub_dict.items(): payload = { "sr": sub_id, "action": "unsub", "uh": modhash, "renderstyle": "html" } print("Unsubscribing from " + sub_link) res = session.post("https://www.reddit.com/api/subscribe", data=payload, headers=headers) res.raise_for_status() return def restore(): sub_dict = {} with open("subreddits.txt", 'r') as f: for line in f: line_raw = line.rstrip('\n') line_raw = line_raw.split('|') sub_dict[line_raw[0]] = line_raw[1] res = session.get("https://www.reddit.com/subreddits/mine", headers=headers) res.raise_for_status() sub_result = bs4.BeautifulSoup(res.text, "html.parser") modhash_raw = sub_result.find("input", {"name": "uh"}) modhash = modhash_raw["value"] sub_box = sub_result.find("div", {"class": "subscription-box"}) sub_items = sub_box.findAll('li') id_list = [] for li in sub_items: id_raw = li.find('a', href=True, text='unsubscribe') id_raw = id_raw["onclick"].split("('") id_raw = id_raw[1].split("')") sub_id = id_raw[0] id_list.append(sub_id) for sub_id, sub_link in sub_dict.items(): if sub_id not in id_list: payload = { "sr": sub_id, "action": "sub", "uh": modhash, "renderstyle": "html" } try: res = session.post("https://www.reddit.com/api/subscribe", data=payload, headers=headers) res.raise_for_status() print("Subscribing to " + sub_link) except requests.exceptions.RequestException as e: # This is the correct syntax if "403 Client Error" in str(e): print("Could Not Subscribe to " + sub_link + " - Community is invite only or banned") else: print("Already Subscribed to " + sub_link) return if __name__ == '__main__': continue_script = True while continue_script is True: main() print("\nProcess Complete!") if not confirm("Would you like to perform an additional task?"): continue_script = False print("Exiting")
Leave a Reply