commit 68da185e5e39eb505e8d32201192c6f846f8fa1f Author: Correl Roush Date: Wed Apr 27 21:24:53 2011 -0400 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6308b04 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# Compiled python files +*.py[oc] diff --git a/trss.py b/trss.py new file mode 100644 index 0000000..895811a --- /dev/null +++ b/trss.py @@ -0,0 +1,199 @@ +import os +import sys +import csv +from ConfigParser import SafeConfigParser +import feedparser +import urllib2 + +_config_file = os.path.expanduser('~/.trss/config') +_feed_file = os.path.expanduser('~/.trss/feeds') +config = SafeConfigParser() + +class TorrentFeed: + def __init__(self, url, name=None, lastguid=None): + self.url = url + self.name = name + self.lastguid = lastguid + self._parsed = None + def parse(self): + if not self._parsed: + self._parsed = feedparser.parse(self.url) + if self._parsed.feed.has_key('title'): + self.name = self._parsed.feed.title + return self._parsed + def set_last_index(self, index=0): + parsed = self.parse() + self.lastguid = parsed.entries[index].guid + + def download(self, folder): + parsed = self.parse() + downloads = [] + for entry in parsed.entries: + if entry.guid == self.lastguid: + break + downloads.append(entry) + downloads.reverse() + for download in downloads: + # Download, break on failure + if download.enclosures: + enclosure = download.enclosures[0] + print 'Downloading {0} to {1}'.format(download.title, folder) + remote = urllib2.urlopen(enclosure.href) + filename = os.path.basename(enclosure.href) + if filename.endswith('.torrent'): + # Strip the extension, it'll be re-applied later + filename = filename[:filename.rfind('.torrent')] + local_file = '{0}.torrent'.format(filename) + attempt = 0 + while os.path.exists(os.path.join(folder, local_file)): + attempt += 1 + local_file = '{0} ({1}).torrent'.format(filename, attempt) + local = open(os.path.join(folder, local_file), 'wb') + local.write(remote.read()) + local.close() + remote.close() + self.lastguid = download.guid + +def read_cfg(): + config.read(_config_file) + +def write_cfg(): + try: + if not os.path.exists(os.path.dirname(_config_file)): + os.makedirs(os.path.dirname(_config_file)) + with open(_config_file, 'w') as config_file: + config.write(config_file) + except: + print 'Could not write config file' + +def read_feeds(): + try: + feed_file = csv.reader(open(_feed_file, 'rb')) + for url, name, lastguid in feed_file: + feed = TorrentFeed(name, url, lastguid) + feeds.append(feed) + except: + pass + return feeds + +def write_feeds(): + try: + if not os.path.exists(os.path.dirname(_feed_file)): + os.makedirs(os.path.dirname(_feed_file)) + feed_file = csv.writer(open(_feed_file, 'wb')) + for feed in feeds: + feed_file.writerow((feed.name, feed.url, feed.lastguid)) + except: + raise + print 'Could not write feeds file' + +def add_rss(url, recent=0): + existing = [f for f in feeds if f.url == url] + if existing: + feed = existing[0] + feeds.remove(feed) + else: + feed = TorrentFeed(url) + feed.set_last_index(recent) + feeds.append(feed) + return feed + +def usage(): + print """Usage: + {0} add [] + Adds an RSS feed to follow + + rss-url: Full URL to the RSS feed + recent-items: (Optional) number of recent items to queue + for downloading + {0} remove + Remove an RSS feed + + index: Numeric index of the feed to remove as + reported by the list command + {0} list + Displays a list of followed feeds + + {0} download + Fetch all feeds and download new items + """.format(sys.argv[0]) + +feeds = [] + +if __name__ == '__main__': + read_cfg() + read_feeds() + + arguments = sys.argv[1:] + try: + command = arguments.pop(0).lower() + except: + usage() + exit(1) + + if command == 'add': + try: + url = arguments.pop(0) + except: + print 'You must specify an RSS url to add' + exit(1) + try: + recent = int(arguments.pop(0)) + except: + recent = 0 + feed = add_rss(url, recent) + print "Added feed '{0}'".format(feed.name) + write_feeds() + elif command == 'remove': + try: + index = int(arguments.pop(0)) + feed = feeds.pop(index) + print "Removed feed '{0}'".format(feed.name) + except: + print 'Invalid feed index' + exit(1) + write_feeds() + elif command == 'list': + for index, feed in enumerate(feeds): + print '{0}: {1} - {2}'.format(index, feed.name, feed.url) + elif command == 'download': + try: + folder = config.get('trss', 'download_folder') + except: + print 'Download folder is not set, using current directory' + folder = os.getcwd() + for feed in feeds: + print 'FEED', feed.name + feed.download(folder) + write_feeds() + elif command == 'set': + try: + key = arguments.pop(0) + except: + for section in config.sections(): + for key in config.options(section): + print "{0}.{1}: {2}".format(section, key, config.get(section, key)) + exit() + try: + value = arguments.pop(0) + except: + value = None + + try: + section, key = key.split('.') + except: + section = 'trss' + + if value is None: + try: + print "{0}.{1}: {2}".format(section, key, config.get(section, key)) + except: + print "Value {0}.{1} does not exist".format(section, key) + else: + if not config.has_section(section): + config.add_section(section) + config.set(section, key, value) + write_cfg() + else: + usage() + exit(1)