Merge branch 'master' of https://github.com/TheoPib/rtv into TheoPib-master

This commit is contained in:
Michael Lazar
2015-09-19 01:22:47 -07:00
11 changed files with 137 additions and 131 deletions

View File

@@ -2,6 +2,7 @@
RTV Changelog RTV Changelog
============= =============
.. _1.5: http://github.com/michael-lazar/rtv/releases/tag/v1.5
.. _1.4.2: http://github.com/michael-lazar/rtv/releases/tag/v1.4.2 .. _1.4.2: http://github.com/michael-lazar/rtv/releases/tag/v1.4.2
.. _1.4.1: http://github.com/michael-lazar/rtv/releases/tag/v1.4.1 .. _1.4.1: http://github.com/michael-lazar/rtv/releases/tag/v1.4.1
.. _1.4: http://github.com/michael-lazar/rtv/releases/tag/v1.4 .. _1.4: http://github.com/michael-lazar/rtv/releases/tag/v1.4
@@ -10,6 +11,19 @@ RTV Changelog
.. _1.2.1: http://github.com/michael-lazar/rtv/releases/tag/v1.2.1 .. _1.2.1: http://github.com/michael-lazar/rtv/releases/tag/v1.2.1
.. _1.2: http://github.com/michael-lazar/rtv/releases/tag/v1.2 .. _1.2: http://github.com/michael-lazar/rtv/releases/tag/v1.2
-----------------
1.5_ (2015-08-26)
-----------------
Features
* New page to view and open subscribed subreddits with `s`.
* Sorting method can now be toggled with the `1` - `5` keys.
* Links to x-posts are now opened inside of RTV.
Bugfixes
* Added */r/* to subreddit names in the subreddit view.
------------------- -------------------
1.4.2_ (2015-08-01) 1.4.2_ (2015-08-01)
------------------- -------------------

View File

@@ -46,6 +46,13 @@ The installation will place a script in the system path
$ rtv $ rtv
$ rtv --help $ rtv --help
If you're having issues running RTV with Python 2, run RTV as module :
.. code-block:: bash
$ cd /path/to/rtv
$ python2 -m rtv
===== =====
Usage Usage
===== =====
@@ -63,7 +70,7 @@ Basic Commands
:``j``/``k`` or ``▲``/``▼``: Move the cursor up/down :``j``/``k`` or ``▲``/``▼``: Move the cursor up/down
:``m``/``n`` or ``PgUp``/``PgDn``: Jump to the previous/next page :``m``/``n`` or ``PgUp``/``PgDn``: Jump to the previous/next page
:``o`` or ``ENTER``: Open the selected item as a webpage :``1-5``: Toggle post order (*hot*, *top*, *rising*, *new*, *controversial*)
:``r`` or ``F5``: Refresh page content :``r`` or ``F5``: Refresh page content
:``u``: Log in or switch accounts :``u``: Log in or switch accounts
:``?``: Show the help screen :``?``: Show the help screen
@@ -84,7 +91,8 @@ Once you are logged in your username will appear in the top-right corner of the
:``c``: Compose a new post or comment :``c``: Compose a new post or comment
:``e``: Edit an existing post or comment :``e``: Edit an existing post or comment
:``d``: Delete an existing post or comment :``d``: Delete an existing post or comment
:``s``: Open/close subscribed subreddits list :``i``: Display new messages prompt
:``s``: View a list of subscribed subreddits
-------------- --------------
Subreddit Mode Subreddit Mode
@@ -93,6 +101,7 @@ Subreddit Mode
In subreddit mode you can browse through the top submissions on either the front page or a specific subreddit. In subreddit mode you can browse through the top submissions on either the front page or a specific subreddit.
:``l`` or ``►``: Enter the selected submission :``l`` or ``►``: Enter the selected submission
:``o`` or ``ENTER``: Open the submission link with your web browser
:``/``: Open a prompt to switch subreddits :``/``: Open a prompt to switch subreddits
:``f``: Open a prompt to search the current subreddit :``f``: Open a prompt to search the current subreddit
@@ -111,6 +120,7 @@ Submission Mode
In submission mode you can view the self text for a submission and browse comments. In submission mode you can view the self text for a submission and browse comments.
:``h`` or ``◄``: Return to the subreddit :``h`` or ``◄``: Return to the subreddit
:``o`` or ``ENTER``: Open the comment permalink with your web browser
:``SPACE``: Fold the selected comment, or load additional comments :``SPACE``: Fold the selected comment, or load additional comments
============= =============
@@ -149,19 +159,14 @@ Config File
----------- -----------
RTV will read a configuration placed at ``~/.config/rtv/rtv.cfg`` (or ``$XDG_CONFIG_HOME``). RTV will read a configuration placed at ``~/.config/rtv/rtv.cfg`` (or ``$XDG_CONFIG_HOME``).
Each line in the file will replace the corresponding default argument in the launch script. Each line in the files will replace the corresponding default argument in the launch script.
This can be used to avoid having to re-enter login credentials every time the program is launched. This can be used to avoid having to re-enter login credentials every time the program is launched.
The OAuth section contains a boolean to trigger auto-login (defaults to False).
When authenticated, two additional fields are written : **access_token** and **refresh_token**.
Those are basically like username and password : they are used to authenticate you on Reddit servers.
Example initial config: Example initial config:
.. code-block:: ini **rtv.cfg**
[oauth] .. code-block:: ini
auto_login=False
[rtv] [rtv]
# Log file location # Log file location
@@ -177,6 +182,24 @@ Example initial config:
# This may be necessary for compatibility with some terminal browsers # This may be necessary for compatibility with some terminal browsers
# ascii=True # ascii=True
-----
OAuth
-----
OAuth is an authentication standard, that replaces authentication with login and password.
RTV implements OAuth. It stores OAuth configuration at ``~/.config/rtv/oauth.cfg``(or ``$XDG_CONFIG_HOME``).
**The OAuth configuration file must be writable, and is created automatically if it doesn't exist.**
It contains a boolean to trigger auto-login (defaults to false).
When authenticated, an additional field is written : **refresh_token**.
This acts as a replacement to username and password : it is used to authenticate you on Reddit servers.
Example **oauth.cfg**:
.. code-block:: ini
[oauth]
auto_login=false
========= =========
Changelog Changelog

View File

@@ -22,7 +22,13 @@ from tornado import ioloop
__all__ = [] __all__ = []
def get_config_fp(): def load_rtv_config():
"""
Attempt to load saved settings for things like the username and password.
"""
config = configparser.ConfigParser()
HOME = os.path.expanduser('~') HOME = os.path.expanduser('~')
XDG_CONFIG_HOME = os.getenv('XDG_CONFIG_HOME', XDG_CONFIG_HOME = os.getenv('XDG_CONFIG_HOME',
os.path.join(HOME, '.config')) os.path.join(HOME, '.config'))
@@ -35,29 +41,8 @@ def get_config_fp():
# get the first existing config file # get the first existing config file
for config_path in config_paths: for config_path in config_paths:
if os.path.exists(config_path): if os.path.exists(config_path):
break
return config_path
def open_config():
"""
Search for a configuration file at the location ~/.rtv and attempt to load
saved settings for things like the username and password.
"""
config = configparser.ConfigParser()
config_path = get_config_fp()
config.read(config_path) config.read(config_path)
break
return config
def load_rtv_config():
"""
Attempt to load saved settings for things like the username and password.
"""
config = open_config()
defaults = {} defaults = {}
if config.has_section('rtv'): if config.has_section('rtv'):
@@ -73,14 +58,26 @@ def load_oauth_config():
Attempt to load saved OAuth settings Attempt to load saved OAuth settings
""" """
config = open_config() config = configparser.ConfigParser()
HOME = os.path.expanduser('~')
XDG_CONFIG_HOME = os.getenv('XDG_CONFIG_HOME',
os.path.join(HOME, '.config'))
if os.path.exists(os.path.join(XDG_CONFIG_HOME, 'rtv')):
config_path = os.path.join(XDG_CONFIG_HOME, 'rtv', 'oauth.cfg')
else:
config_path = os.path.join(HOME, '.rtv-oauth')
config.read(config_path)
if config.has_section('oauth'): if config.has_section('oauth'):
defaults = dict(config.items('oauth')) defaults = dict(config.items('oauth'))
else: else:
# Populate OAuth section # Populate OAuth section
config['oauth'] = {'auto_login': False} config.add_section('oauth')
with open(get_config_fp(), 'w') as cfg: config.set('oauth', 'auto_login', 'false')
with open(config_path, 'w') as cfg:
config.write(cfg) config.write(cfg)
defaults = dict(config.items('oauth')) defaults = dict(config.items('oauth'))
@@ -106,7 +103,6 @@ def command_line():
oauth_group = parser.add_argument_group('OAuth data (optional)', OAUTH) oauth_group = parser.add_argument_group('OAuth data (optional)', OAUTH)
oauth_group.add_argument('--auto-login', dest='auto_login', help='OAuth auto-login setting') oauth_group.add_argument('--auto-login', dest='auto_login', help='OAuth auto-login setting')
oauth_group.add_argument('--auth-token', dest='access_token', help='OAuth authorization token')
oauth_group.add_argument('--refresh-token', dest='refresh_token', help='OAuth refresh token') oauth_group.add_argument('--refresh-token', dest='refresh_token', help='OAuth refresh token')
args = parser.parse_args() args = parser.parse_args()
@@ -154,7 +150,7 @@ def main():
reddit.config.decode_html_entities = False reddit.config.decode_html_entities = False
with curses_session() as stdscr: with curses_session() as stdscr:
oauth = OAuthTool(reddit, stdscr, LoadScreen(stdscr)) oauth = OAuthTool(reddit, stdscr, LoadScreen(stdscr))
if args.auto_login == 'True': # Ew! if args.auto_login == 'true': # Ew!
oauth.authorize() oauth.authorize()
if args.link: if args.link:
page = SubmissionPage(stdscr, reddit, oauth, url=args.link) page = SubmissionPage(stdscr, reddit, oauth, url=args.link)

View File

@@ -1 +1 @@
__version__ = '1.4.2' __version__ = '1.5'

View File

@@ -18,8 +18,8 @@ given, the program will display a secure prompt to enter a password.
""" """
OAUTH = """\ OAUTH = """\
Authentication is now done by OAuth, since PRAW will stop supporting login with Authentication is now done by OAuth, since PRAW will drop
username and password soon. password authentication soon.
""" """
CONTROLS = """ CONTROLS = """
@@ -47,6 +47,7 @@ Authenticated Commands
`c` : Compose a new post or comment `c` : Compose a new post or comment
`e` : Edit an existing post or comment `e` : Edit an existing post or comment
`d` : Delete an existing post or comment `d` : Delete an existing post or comment
`i` : Display new messages prompt
`s` : Open/close subscribed subreddits list `s` : Open/close subscribed subreddits list
Subreddit Mode Subreddit Mode

View File

@@ -1,4 +1,3 @@
from six.moves import configparser
import curses import curses
import logging import logging
import os import os
@@ -7,17 +6,17 @@ import uuid
import webbrowser import webbrowser
import praw import praw
from six.moves import configparser
from . import config from . import config
from .curses_helpers import show_notification, prompt_input from .curses_helpers import show_notification, prompt_input
from tornado import ioloop, web from tornado import gen, ioloop, web, httpserver
from concurrent.futures import ThreadPoolExecutor
__all__ = ['token_validity', 'OAuthTool'] __all__ = ['token_validity', 'OAuthTool']
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
token_validity = 3540
oauth_state = None oauth_state = None
oauth_code = None oauth_code = None
oauth_error = None oauth_error = None
@@ -29,6 +28,9 @@ class HomeHandler(web.RequestHandler):
class AuthHandler(web.RequestHandler): class AuthHandler(web.RequestHandler):
def initialize(self):
self.compact = os.environ.get('BROWSER') in ['w3m', 'links', 'elinks', 'lynx']
def get(self): def get(self):
global oauth_state global oauth_state
global oauth_code global oauth_code
@@ -40,6 +42,8 @@ class AuthHandler(web.RequestHandler):
self.render('auth.html', state=oauth_state, code=oauth_code, error=oauth_error) self.render('auth.html', state=oauth_state, code=oauth_code, error=oauth_error)
# Stop IOLoop if using BackgroundBrowser (or GUI browser)
if not self.compact:
ioloop.IOLoop.current().stop() ioloop.IOLoop.current().stop()
class OAuthTool(object): class OAuthTool(object):
@@ -59,34 +63,30 @@ class OAuthTool(object):
self.redirect_uri = redirect_uri or config.oauth_redirect_uri self.redirect_uri = redirect_uri or config.oauth_redirect_uri
self.scope = scope or config.oauth_scope.split('-') self.scope = scope or config.oauth_scope.split('-')
self.access_info = {} self.access_info = {}
self.token_expiration = 0 # Terminal web browser
self.compact = os.environ.get('BROWSER') in ['w3m', 'links', 'elinks', 'lynx']
# Initialize Tornado webapp and listen on port 65000 # Initialize Tornado webapp
self.callback_app = web.Application([ self.callback_app = web.Application([
(r'/', HomeHandler), (r'/', HomeHandler),
(r'/auth', AuthHandler), (r'/auth', AuthHandler),
], template_path='rtv/templates') ], template_path='rtv/templates')
self.callback_app.listen(65000)
self.http_server = None
def get_config_fp(self): def get_config_fp(self):
HOME = os.path.expanduser('~') HOME = os.path.expanduser('~')
XDG_CONFIG_HOME = os.getenv('XDG_CONFIG_HOME', XDG_CONFIG_HOME = os.getenv('XDG_CONFIG_HOME',
os.path.join(HOME, '.config')) os.path.join(HOME, '.config'))
config_paths = [ if os.path.exists(os.path.join(XDG_CONFIG_HOME, 'rtv')):
os.path.join(XDG_CONFIG_HOME, 'rtv', 'rtv.cfg'), file_path = os.path.join(XDG_CONFIG_HOME, 'rtv', 'oauth.cfg')
os.path.join(HOME, '.rtv') else:
] file_path = os.path.join(HOME, '.rtv-oauth')
# get the first existing config file return file_path
for config_path in config_paths:
if os.path.exists(config_path):
break
return config_path
def open_config(self, update=False): def open_config(self, update=False):
if self.config_fp is None: if self.config_fp is None:
@@ -100,45 +100,47 @@ class OAuthTool(object):
with open(self.config_fp, 'w') as cfg: with open(self.config_fp, 'w') as cfg:
self.config.write(cfg) self.config.write(cfg)
def set_token_expiration(self): def clear_oauth_data(self):
self.token_expiration = time.time() + token_validity self.open_config(update=True)
if self.config.has_section('oauth') and self.config.has_option('oauth', 'refresh_token'):
def token_expired(self): self.config.remove_option('oauth', 'refresh_token')
return time.time() > self.token_expiration
def refresh(self, force=False):
if self.token_expired() or force:
try:
with self.loader(message='Refreshing token'):
new_access_info = self.reddit.refresh_access_information(
self.config['oauth']['refresh_token'])
self.access_info = new_access_info
self.reddit.set_access_credentials(scope=set(self.access_info['scope']),
access_token=self.access_info['access_token'],
refresh_token=self.access_info['refresh_token'])
self.set_token_expiration()
except (praw.errors.OAuthAppRequired, praw.errors.OAuthInvalidToken,
praw.errors.HTTPException) as e:
show_notification(self.stdscr, ['Invalid OAuth data'])
else:
self.config['oauth']['access_token'] = self.access_info['access_token']
self.config['oauth']['refresh_token'] = self.access_info['refresh_token']
self.save_config() self.save_config()
@gen.coroutine
def open_terminal_browser(self, url):
with ThreadPoolExecutor(max_workers=1) as executor:
yield executor.submit(webbrowser.open_new_tab, url)
ioloop.IOLoop.current().stop()
def authorize(self): def authorize(self):
if self.compact and not '.compact' in self.reddit.config.API_PATHS['authorize']:
self.reddit.config.API_PATHS['authorize'] += '.compact'
self.reddit.set_oauth_app_info(self.client_id, self.reddit.set_oauth_app_info(self.client_id,
self.client_secret, self.client_secret,
self.redirect_uri) self.redirect_uri)
self.open_config(update=True) self.open_config(update=True)
# If no previous OAuth data found, starting from scratch # If no previous OAuth data found, starting from scratch
if 'oauth' not in self.config or 'access_token' not in self.config['oauth']: if not self.config.has_section('oauth') or not self.config.has_option('oauth', 'refresh_token'):
if self.http_server is None:
self.http_server = httpserver.HTTPServer(self.callback_app)
self.http_server.listen(65000)
# Generate a random UUID # Generate a random UUID
hex_uuid = uuid.uuid4().hex hex_uuid = uuid.uuid4().hex
permission_ask_page_link = self.reddit.get_authorize_url(str(hex_uuid), permission_ask_page_link = self.reddit.get_authorize_url(str(hex_uuid),
scope=self.scope, refreshable=True) scope=self.scope, refreshable=True)
if self.compact:
show_notification(self.stdscr, ['Opening ' + os.environ.get('BROWSER')])
curses.endwin()
ioloop.IOLoop.current().add_callback(self.open_terminal_browser, permission_ask_page_link)
ioloop.IOLoop.current().start()
curses.doupdate()
else:
with self.loader(message='Waiting for authorization'): with self.loader(message='Waiting for authorization'):
webbrowser.open(permission_ask_page_link) webbrowser.open(permission_ask_page_link)
ioloop.IOLoop.current().start() ioloop.IOLoop.current().start()
@@ -169,21 +171,15 @@ class OAuthTool(object):
with self.loader(message='Logging in'): with self.loader(message='Logging in'):
# Get access information (tokens and scopes) # Get access information (tokens and scopes)
self.access_info = self.reddit.get_access_information(self.final_code) self.access_info = self.reddit.get_access_information(self.final_code)
self.reddit.set_access_credentials(
scope=set(self.access_info['scope']),
access_token=self.access_info['access_token'],
refresh_token=self.access_info['refresh_token'])
self.set_token_expiration()
except (praw.errors.OAuthAppRequired, praw.errors.OAuthInvalidToken) as e: except (praw.errors.OAuthAppRequired, praw.errors.OAuthInvalidToken) as e:
show_notification(self.stdscr, ['Invalid OAuth data']) show_notification(self.stdscr, ['Invalid OAuth data'])
else: else:
if 'oauth' not in self.config: if not self.config.has_section('oauth'):
self.config['oauth'] = {} self.config.add_section('oauth')
self.config['oauth']['access_token'] = self.access_info['access_token'] self.config.set('oauth', 'refresh_token', self.access_info['refresh_token'])
self.config['oauth']['refresh_token'] = self.access_info['refresh_token']
self.save_config() self.save_config()
# Otherwise, fetch new access token # Otherwise, fetch new access token
else: else:
self.refresh(force=True) with self.loader(message='Logging in'):
self.reddit.refresh_access_information(self.config.get('oauth', 'refresh_token'))

View File

@@ -314,9 +314,6 @@ class BasePage(object):
@BaseController.register('a') @BaseController.register('a')
def upvote(self): def upvote(self):
# Refresh access token if expired
self.oauth.refresh()
data = self.content.get(self.nav.absolute_index) data = self.content.get(self.nav.absolute_index)
try: try:
if 'likes' not in data: if 'likes' not in data:
@@ -332,9 +329,6 @@ class BasePage(object):
@BaseController.register('z') @BaseController.register('z')
def downvote(self): def downvote(self):
# Refresh access token if expired
self.oauth.refresh()
data = self.content.get(self.nav.absolute_index) data = self.content.get(self.nav.absolute_index)
try: try:
if 'likes' not in data: if 'likes' not in data:
@@ -358,6 +352,7 @@ class BasePage(object):
if self.reddit.is_oauth_session(): if self.reddit.is_oauth_session():
self.reddit.clear_authentication() self.reddit.clear_authentication()
self.oauth.clear_oauth_data()
return return
self.oauth.authorize() self.oauth.authorize()
@@ -372,9 +367,6 @@ class BasePage(object):
show_notification(self.stdscr, ['Not logged in']) show_notification(self.stdscr, ['Not logged in'])
return return
# Refresh access token if expired
self.oauth.refresh()
data = self.content.get(self.nav.absolute_index) data = self.content.get(self.nav.absolute_index)
if data.get('author') != self.reddit.user.name: if data.get('author') != self.reddit.user.name:
curses.flash() curses.flash()
@@ -403,9 +395,6 @@ class BasePage(object):
show_notification(self.stdscr, ['Not logged in']) show_notification(self.stdscr, ['Not logged in'])
return return
# Refresh access token if expired
self.oauth.refresh()
data = self.content.get(self.nav.absolute_index) data = self.content.get(self.nav.absolute_index)
if data.get('author') != self.reddit.user.name: if data.get('author') != self.reddit.user.name:
curses.flash() curses.flash()
@@ -440,9 +429,6 @@ class BasePage(object):
Checks the inbox for unread messages and displays a notification. Checks the inbox for unread messages and displays a notification.
""" """
# Refresh access token if expired
self.oauth.refresh()
inbox = len(list(self.reddit.get_unread(limit=1))) inbox = len(list(self.reddit.get_unread(limit=1)))
try: try:
if inbox > 0: if inbox > 0:

View File

@@ -93,9 +93,6 @@ class SubmissionPage(BasePage):
show_notification(self.stdscr, ['Not logged in']) show_notification(self.stdscr, ['Not logged in'])
return return
# Refresh access token if expired
self.oauth.refresh()
data = self.content.get(self.nav.absolute_index) data = self.content.get(self.nav.absolute_index)
if data['type'] == 'Submission': if data['type'] == 'Submission':
content = data['text'] content = data['text']
@@ -131,9 +128,6 @@ class SubmissionPage(BasePage):
def delete_comment(self): def delete_comment(self):
"Delete a comment as long as it is not the current submission" "Delete a comment as long as it is not the current submission"
# Refresh access token if expired
self.oauth.refresh()
if self.nav.absolute_index != -1: if self.nav.absolute_index != -1:
self.delete() self.delete()
else: else:

View File

@@ -8,7 +8,7 @@ import requests
from .exceptions import SubredditError, AccountError from .exceptions import SubredditError, AccountError
from .page import BasePage, Navigator, BaseController from .page import BasePage, Navigator, BaseController
from .submission import SubmissionPage from .submission import SubmissionPage
from .subscriptions import SubscriptionPage from .subscription import SubscriptionPage
from .content import SubredditContent from .content import SubredditContent
from .helpers import open_browser, open_editor, strip_subreddit_url from .helpers import open_browser, open_editor, strip_subreddit_url
from .docs import SUBMISSION_FILE from .docs import SUBMISSION_FILE
@@ -54,9 +54,6 @@ class SubredditPage(BasePage):
def refresh_content(self, name=None, order=None): def refresh_content(self, name=None, order=None):
"Re-download all submissions and reset the page index" "Re-download all submissions and reset the page index"
# Refresh access token if expired
self.oauth.refresh()
name = name or self.content.name name = name or self.content.name
order = order or self.content.order order = order or self.content.order
@@ -136,9 +133,6 @@ class SubredditPage(BasePage):
show_notification(self.stdscr, ['Not logged in']) show_notification(self.stdscr, ['Not logged in'])
return return
# Refresh access token if expired
self.oauth.refresh()
# Strips the subreddit to just the name # Strips the subreddit to just the name
# Make sure it is a valid subreddit for submission # Make sure it is a valid subreddit for submission
subreddit = self.reddit.get_subreddit(self.content.name) subreddit = self.reddit.get_subreddit(self.content.name)
@@ -180,9 +174,6 @@ class SubredditPage(BasePage):
show_notification(self.stdscr, ['Not logged in']) show_notification(self.stdscr, ['Not logged in'])
return return
# Refresh access token if expired
self.oauth.refresh()
# Open subscriptions page # Open subscriptions page
page = SubscriptionPage(self.stdscr, self.reddit, self.oauth) page = SubscriptionPage(self.stdscr, self.reddit, self.oauth)
page.loop() page.loop()

View File

@@ -38,9 +38,6 @@ class SubscriptionPage(BasePage):
def refresh_content(self): def refresh_content(self):
"Re-download all subscriptions and reset the page index" "Re-download all subscriptions and reset the page index"
# Refresh access token if expired
self.oauth.refresh()
self.content = SubscriptionContent.from_user(self.reddit, self.loader) self.content = SubscriptionContent.from_user(self.reddit, self.loader)
self.nav = Navigator(self.content.get) self.nav = Navigator(self.content.get)

View File

@@ -1,6 +1,14 @@
from setuptools import setup from setuptools import setup
from version import __version__ as version from version import __version__ as version
import sys
requirements = ['tornado', 'praw>=3.1.0', 'six', 'requests', 'kitchen']
# Python 2: add required concurrent.futures backport from Python 3.2
if sys.version_info.major <= 2:
requirements.append('futures')
setup( setup(
name='rtv', name='rtv',
version=version, version=version,
@@ -13,7 +21,7 @@ setup(
keywords='reddit terminal praw curses', keywords='reddit terminal praw curses',
packages=['rtv'], packages=['rtv'],
include_package_data=True, include_package_data=True,
install_requires=['tornado', 'praw>=3.1.0', 'six', 'requests', 'kitchen'], install_requires=requirements,
entry_points={'console_scripts': ['rtv=rtv.__main__:main']}, entry_points={'console_scripts': ['rtv=rtv.__main__:main']},
classifiers=[ classifiers=[
'Intended Audience :: End Users/Desktop', 'Intended Audience :: End Users/Desktop',