List

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:

  1. backup – saves a list of subreddit subscriptions to a file
  2. restore – subscribes to subreddits listed in backup file, unsubscribing from current subreddits
  3. clear – unsubscribes from all subreddits in an account
  4. 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

  Posts

1 2
December 21st, 2018

Home Assistant: Heated Blanket Usage Limiter

Introduction I live in an older house with poor insulation, and I turn my thermostat down to 62 at night. [...]
December 19th, 2018

Home Assistant: Tracking Recent Arrivals + Having Alexa Welcome Them

Introduction I remember walking out of a movie theater in 2008 having just watched Iron Man, and thinking "Damn, I [...]
December 19th, 2018

Home Assistant: Automating Alexa Volume As A Media Player

Latest Version This guide is an update to my previous post, Automating Alexa Volume With Home Assistant and Spotify. At the [...]
January 9th, 2018

Home Assistant: Automating Alexa Volume With Home Assistant and Spotify

Updated Version Available! Automating volume control was simplified with the release of a custom component allowing for Alexa devices to [...]
October 6th, 2016

Subreddit Backup and Restore (Python Script)

Summary I'm an avid reddit user, constantly searching for new subreddits to subscribe to in my areas of my interests. Having [...]
October 7th, 2015

Saved Lead Hider – LinkedIn Sales Navigator Userscript

Introduction LinkedIn Sales Navigator is a useful tool that combines LinkedIn's network data, relevant news sources, and your accounts, leads, and [...]
June 20th, 2015

Boombox Airplay Conversion

Introduction Years ago, I purchased a Sony RDH-GTK1i boombox that has a 30-pin Apple connector, USB, RCA and radio functionality. At [...]
June 19th, 2015

HTPC – The Back-End

Introduction As explained on the Project Page, two of the most important components to an HTPC are the "invisible" background [...]
June 19th, 2015

Introduction to HTPCs

Introduction As mentioned on the Project Page, this, is an ongoing project for improving my HTPC solution. Its combination of [...]
June 19th, 2015

R1FLE Project (vgInteractive MK II)

Introduction This project is the slow growing progression of a project I started for the original Xbox, specifically for Halo 2. [...]