Docstring wordings, refactored /r/me handling, updated README.
This commit is contained in:
21
README.rst
21
README.rst
@@ -61,7 +61,8 @@ RTV currently supports browsing both subreddits and individual submissions. In e
|
|||||||
:``a``/``z``: Upvote/downvote the selected item
|
:``a``/``z``: Upvote/downvote the selected item
|
||||||
:``ENTER`` or ``o``: Open the selected item in the default web browser
|
:``ENTER`` or ``o``: Open the selected item in the default web browser
|
||||||
:``r``: Refresh the current page
|
:``r``: Refresh the current page
|
||||||
:``?``: Show the help message
|
:``u``: Login and logout of your user account
|
||||||
|
:``?``: Show the help screen
|
||||||
:``q``: Quit
|
:``q``: Quit
|
||||||
|
|
||||||
**Subreddit Mode**
|
**Subreddit Mode**
|
||||||
@@ -71,7 +72,7 @@ In subreddit mode you can browse through the top submissions on either the front
|
|||||||
:``►`` or ``l``: View comments for the selected submission
|
:``►`` or ``l``: View comments for the selected submission
|
||||||
:``/``: 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
|
||||||
:``p``: Post a Submission to the current subreddit
|
:``p``: Post a new submission to the current subreddit
|
||||||
|
|
||||||
The ``/`` prompt accepts subreddits in the following formats
|
The ``/`` prompt accepts subreddits in the following formats
|
||||||
|
|
||||||
@@ -79,7 +80,7 @@ The ``/`` prompt accepts subreddits in the following formats
|
|||||||
* ``/r/python/new``
|
* ``/r/python/new``
|
||||||
* ``/r/python+linux`` supports multireddits
|
* ``/r/python+linux`` supports multireddits
|
||||||
* ``/r/front`` will redirect to the front page
|
* ``/r/front`` will redirect to the front page
|
||||||
* ``/r/me`` will show you your submissions on all subs
|
* ``/r/me`` will display your submissions
|
||||||
|
|
||||||
**Submission Mode**
|
**Submission Mode**
|
||||||
|
|
||||||
@@ -87,7 +88,7 @@ In submission mode you can view the self text for a submission and browse commen
|
|||||||
|
|
||||||
:``◄`` or ``h``: Return to subreddit mode
|
:``◄`` or ``h``: Return to subreddit mode
|
||||||
:``►`` or ``l``: Fold the selected comment, or load additional comments
|
:``►`` or ``l``: Fold the selected comment, or load additional comments
|
||||||
:``c``: Comment/reply on the selected item
|
:``c``: Post a new comment on the selected item
|
||||||
|
|
||||||
-------------
|
-------------
|
||||||
Configuration
|
Configuration
|
||||||
@@ -104,10 +105,20 @@ Example config:
|
|||||||
[rtv]
|
[rtv]
|
||||||
username=MyUsername
|
username=MyUsername
|
||||||
password=MySecretPassword
|
password=MySecretPassword
|
||||||
|
|
||||||
|
# Log file location
|
||||||
|
log=/tmp/rtv.log
|
||||||
|
|
||||||
# Default subreddit
|
# Default subreddit
|
||||||
subreddit=CollegeBasketball
|
subreddit=CollegeBasketball
|
||||||
|
|
||||||
|
# Default submission link - will be opened every time the program starts
|
||||||
|
# link=http://www.reddit.com/r/CollegeBasketball/comments/31irjq
|
||||||
|
|
||||||
|
# Enable unicode characters (experimental)
|
||||||
|
# This is known to be unstable with east asian wide character sets
|
||||||
|
# unicode=true
|
||||||
|
|
||||||
RTV allows users to compose comments and replys using their preferred text editor (**vi**, **nano**, **gedit**, etc).
|
RTV allows users to compose comments and replys using their preferred text editor (**vi**, **nano**, **gedit**, etc).
|
||||||
Set the environment variable ``RTV_EDITOR`` to specify which editor the program should use.
|
Set the environment variable ``RTV_EDITOR`` to specify which editor the program should use.
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,9 @@ def load_config():
|
|||||||
if config.has_section('rtv'):
|
if config.has_section('rtv'):
|
||||||
defaults = dict(config.items('rtv'))
|
defaults = dict(config.items('rtv'))
|
||||||
|
|
||||||
|
if 'unicode' in defaults:
|
||||||
|
defaults['unicode'] = config.getboolean('rtv', 'unicode')
|
||||||
|
|
||||||
return defaults
|
return defaults
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import textwrap
|
|||||||
import praw
|
import praw
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from .exceptions import SubmissionError, SubredditError
|
from .exceptions import SubmissionError, SubredditError, AccountError
|
||||||
from .helpers import humanize_timestamp, wrap_text, strip_subreddit_url
|
from .helpers import humanize_timestamp, wrap_text, strip_subreddit_url
|
||||||
|
|
||||||
__all__ = ['SubredditContent', 'SubmissionContent']
|
__all__ = ['SubredditContent', 'SubmissionContent']
|
||||||
@@ -114,18 +114,12 @@ class BaseContent(object):
|
|||||||
|
|
||||||
|
|
||||||
class SubmissionContent(BaseContent):
|
class SubmissionContent(BaseContent):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Grab a submission from PRAW and lazily store comments to an internal
|
Grab a submission from PRAW and lazily store comments to an internal
|
||||||
list for repeat access.
|
list for repeat access.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(self, submission, loader, indent_size=2, max_indent_level=4):
|
||||||
self,
|
|
||||||
submission,
|
|
||||||
loader,
|
|
||||||
indent_size=2,
|
|
||||||
max_indent_level=4):
|
|
||||||
|
|
||||||
self.indent_size = indent_size
|
self.indent_size = indent_size
|
||||||
self.max_indent_level = max_indent_level
|
self.max_indent_level = max_indent_level
|
||||||
@@ -138,13 +132,7 @@ class SubmissionContent(BaseContent):
|
|||||||
self._comment_data = [self.strip_praw_comment(c) for c in comments]
|
self._comment_data = [self.strip_praw_comment(c) for c in comments]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_url(
|
def from_url(cls, reddit, url, loader, indent_size=2, max_indent_level=4):
|
||||||
cls,
|
|
||||||
reddit,
|
|
||||||
url,
|
|
||||||
loader,
|
|
||||||
indent_size=2,
|
|
||||||
max_indent_level=4):
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with loader():
|
with loader():
|
||||||
@@ -165,10 +153,9 @@ class SubmissionContent(BaseContent):
|
|||||||
|
|
||||||
elif index == -1:
|
elif index == -1:
|
||||||
data = self._submission_data
|
data = self._submission_data
|
||||||
data['split_title'] = textwrap.wrap(data['title'],
|
data['split_title'] = textwrap.wrap(data['title'], width=n_cols -2)
|
||||||
width=n_cols -2)
|
|
||||||
data['split_text'] = wrap_text(data['text'], width=n_cols - 2)
|
data['split_text'] = wrap_text(data['text'], width=n_cols - 2)
|
||||||
data['n_rows'] = len(data['split_title']) + len(data['split_text']) + 5
|
data['n_rows'] = len(data['split_title'] + data['split_text']) + 5
|
||||||
data['offset'] = 0
|
data['offset'] = 0
|
||||||
|
|
||||||
else:
|
else:
|
||||||
@@ -233,9 +220,8 @@ class SubmissionContent(BaseContent):
|
|||||||
|
|
||||||
|
|
||||||
class SubredditContent(BaseContent):
|
class SubredditContent(BaseContent):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Grabs a subreddit from PRAW and lazily stores submissions to an internal
|
Grab a subreddit from PRAW and lazily stores submissions to an internal
|
||||||
list for repeat access.
|
list for repeat access.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -251,7 +237,7 @@ class SubredditContent(BaseContent):
|
|||||||
# there is is no other way to check things like multireddits that
|
# there is is no other way to check things like multireddits that
|
||||||
# don't have a real corresponding subreddit object.
|
# don't have a real corresponding subreddit object.
|
||||||
try:
|
try:
|
||||||
content.get(0)
|
self.get(0)
|
||||||
except (praw.errors.APIException, requests.HTTPError,
|
except (praw.errors.APIException, requests.HTTPError,
|
||||||
praw.errors.RedirectException):
|
praw.errors.RedirectException):
|
||||||
raise SubredditError(display_name)
|
raise SubredditError(display_name)
|
||||||
@@ -259,8 +245,6 @@ class SubredditContent(BaseContent):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def from_name(cls, reddit, name, loader, order='hot', query=None):
|
def from_name(cls, reddit, name, loader, order='hot', query=None):
|
||||||
|
|
||||||
name = name if name else 'front'
|
|
||||||
|
|
||||||
if order not in ['hot', 'top', 'rising', 'new', 'controversial']:
|
if order not in ['hot', 'top', 'rising', 'new', 'controversial']:
|
||||||
raise SubredditError(display_name)
|
raise SubredditError(display_name)
|
||||||
|
|
||||||
@@ -268,7 +252,7 @@ class SubredditContent(BaseContent):
|
|||||||
if name.startswith('r/'):
|
if name.startswith('r/'):
|
||||||
name = name[2:]
|
name = name[2:]
|
||||||
|
|
||||||
# Grab the display type e.g. "python/new"
|
# Grab the display order e.g. "python/new"
|
||||||
if '/' in name:
|
if '/' in name:
|
||||||
name, order = name.split('/')
|
name, order = name.split('/')
|
||||||
|
|
||||||
@@ -276,57 +260,50 @@ class SubredditContent(BaseContent):
|
|||||||
if order != 'hot':
|
if order != 'hot':
|
||||||
display_name += '/{}'.format(order)
|
display_name += '/{}'.format(order)
|
||||||
|
|
||||||
if name == 'front':
|
if name == 'me':
|
||||||
dispatch = {
|
if not self.reddit.is_logged_in():
|
||||||
'hot': reddit.get_front_page,
|
raise AccountError
|
||||||
'top': reddit.get_top,
|
else:
|
||||||
'rising': reddit.get_rising,
|
submissions = reddit.user.get_submitted(sort=order)
|
||||||
'new': reddit.get_new,
|
|
||||||
'controversial': reddit.get_controversial
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
subreddit = reddit.get_subreddit(name)
|
|
||||||
dispatch = {
|
|
||||||
'hot': subreddit.get_hot,
|
|
||||||
'top': subreddit.get_top,
|
|
||||||
'rising': subreddit.get_rising,
|
|
||||||
'new': subreddit.get_new,
|
|
||||||
'controversial': subreddit.get_controversial
|
|
||||||
}
|
|
||||||
|
|
||||||
if query:
|
elif query:
|
||||||
if name == 'front':
|
if name == 'front':
|
||||||
submissions = reddit.search(query, subreddit=None, sort=order)
|
submissions = reddit.search(query, subreddit=None, sort=order)
|
||||||
else:
|
else:
|
||||||
submissions = reddit.search(query, subreddit=name, sort=order)
|
submissions = reddit.search(query, subreddit=name, sort=order)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
if name == 'front':
|
||||||
|
dispatch = {
|
||||||
|
'hot': reddit.get_front_page,
|
||||||
|
'top': reddit.get_top,
|
||||||
|
'rising': reddit.get_rising,
|
||||||
|
'new': reddit.get_new,
|
||||||
|
'controversial': reddit.get_controversial,
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
subreddit = reddit.get_subreddit(name)
|
||||||
|
dispatch = {
|
||||||
|
'hot': subreddit.get_hot,
|
||||||
|
'top': subreddit.get_top,
|
||||||
|
'rising': subreddit.get_rising,
|
||||||
|
'new': subreddit.get_new,
|
||||||
|
'controversial': subreddit.get_controversial,
|
||||||
|
}
|
||||||
submissions = dispatch[order](limit=None)
|
submissions = dispatch[order](limit=None)
|
||||||
|
|
||||||
return cls(display_name, submissions, loader)
|
return cls(display_name, submissions, loader)
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_redditor(cls, reddit, loader, order='new'):
|
|
||||||
submissions = reddit.user.get_submitted(sort=order)
|
|
||||||
display_name = '/r/me'
|
|
||||||
content = cls(display_name, submissions, loader)
|
|
||||||
try:
|
|
||||||
content.get(0)
|
|
||||||
except (praw.errors.APIException, requests.HTTPError,
|
|
||||||
praw.errors.RedirectException):
|
|
||||||
raise SubredditError(display_name)
|
|
||||||
return content
|
|
||||||
|
|
||||||
def get(self, index, n_cols=70):
|
def get(self, index, n_cols=70):
|
||||||
"""
|
"""
|
||||||
Grab the `i`th submission, with the title field formatted to fit inside
|
Grab the `i`th submission, with the title field formatted to fit inside
|
||||||
of a window of width `n`
|
of a window of width `n_cols`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if index < 0:
|
if index < 0:
|
||||||
raise IndexError
|
raise IndexError
|
||||||
|
|
||||||
while index >= len(self._submission_data):
|
while index >= len(self._submission_data):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with self._loader():
|
with self._loader():
|
||||||
submission = next(self._submissions)
|
submission = next(self._submissions)
|
||||||
@@ -342,4 +319,4 @@ class SubredditContent(BaseContent):
|
|||||||
data['n_rows'] = len(data['split_title']) + 3
|
data['n_rows'] = len(data['split_title']) + 3
|
||||||
data['offset'] = 0
|
data['offset'] = 0
|
||||||
|
|
||||||
return data
|
return data
|
||||||
@@ -66,8 +66,11 @@ def show_help(stdscr):
|
|||||||
"""
|
"""
|
||||||
Overlay a message box with the help screen.
|
Overlay a message box with the help screen.
|
||||||
"""
|
"""
|
||||||
show_notification(stdscr, HELP.split("\n"))
|
|
||||||
|
|
||||||
|
curses.endwin()
|
||||||
|
print(HELP)
|
||||||
|
raw_input('Press Enter to continue')
|
||||||
|
curses.doupdate()
|
||||||
|
|
||||||
class LoadScreen(object):
|
class LoadScreen(object):
|
||||||
|
|
||||||
|
|||||||
18
rtv/docs.py
18
rtv/docs.py
@@ -2,7 +2,9 @@ from .__version__ import __version__
|
|||||||
|
|
||||||
__all__ = ['AGENT', 'SUMMARY', 'AUTH', 'CONTROLS', 'HELP']
|
__all__ = ['AGENT', 'SUMMARY', 'AUTH', 'CONTROLS', 'HELP']
|
||||||
|
|
||||||
AGENT = "desktop:https://github.com/michael-lazar/rtv:{} (by /u/civilization_phaze_3)".format(__version__)
|
AGENT = """\
|
||||||
|
desktop:https://github.com/michael-lazar/rtv:{} (by /u/civilization_phaze_3)\
|
||||||
|
""".format(__version__)
|
||||||
|
|
||||||
SUMMARY = """
|
SUMMARY = """
|
||||||
Reddit Terminal Viewer is a lightweight browser for www.reddit.com built into a
|
Reddit Terminal Viewer is a lightweight browser for www.reddit.com built into a
|
||||||
@@ -28,22 +30,22 @@ HELP = """
|
|||||||
Global Commands
|
Global Commands
|
||||||
`UP/DOWN` or `j/k` : Scroll to the prev/next item
|
`UP/DOWN` or `j/k` : Scroll to the prev/next item
|
||||||
`a/z` : Upvote/downvote the selected item
|
`a/z` : Upvote/downvote the selected item
|
||||||
`r` : Refresh the current page
|
|
||||||
`q` : Quit the program
|
|
||||||
`ENTER` or `o` : Open the selected item in the default web browser
|
`ENTER` or `o` : Open the selected item in the default web browser
|
||||||
`u` : Log in
|
`r` : Refresh the current page
|
||||||
|
`u` : Login/logout of your user account
|
||||||
`?` : Show this help message
|
`?` : Show this help message
|
||||||
|
`q` : Quit the program
|
||||||
|
|
||||||
Subreddit Mode
|
Subreddit Mode
|
||||||
`RIGHT` or `l` : View comments for the selected submission
|
`RIGHT` or `l` : View comments for the selected submission
|
||||||
`/` : 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
|
||||||
`p` : Post a Submission to the current subreddit
|
`p` : Post a new submission to the current subreddit
|
||||||
|
|
||||||
Submission Mode
|
Submission Mode
|
||||||
`LEFT` or `h` : Return to subreddit mode
|
`LEFT` or `h` : Return to subreddit mode
|
||||||
`RIGHT` or `l` : Fold the selected comment, or load additional comments
|
`RIGHT` or `l` : Fold the selected comment, or load additional comments
|
||||||
`c` : Comment/reply on the selected item
|
`c` : Post a new comment on the selected item
|
||||||
"""
|
"""
|
||||||
|
|
||||||
COMMENT_FILE = """
|
COMMENT_FILE = """
|
||||||
@@ -59,7 +61,7 @@ SUBMISSION_FILE = """
|
|||||||
# and an empty field aborts the submission.
|
# and an empty field aborts the submission.
|
||||||
#
|
#
|
||||||
# The first line will be interpreted as the title
|
# The first line will be interpreted as the title
|
||||||
# Following lines will be interpreted as the content
|
# The following lines will be interpreted as the content
|
||||||
#
|
#
|
||||||
# Posting to /r/{name}
|
# Posting to /r/{name}
|
||||||
"""
|
"""
|
||||||
@@ -1,23 +1,31 @@
|
|||||||
class SubmissionError(Exception):
|
class EscapeInterrupt(Exception):
|
||||||
"""Submission could not be loaded"""
|
"Signal that the ESC key has been pressed"
|
||||||
|
|
||||||
|
|
||||||
|
class RTVError(Exception):
|
||||||
|
"Base RTV error class"
|
||||||
|
|
||||||
|
|
||||||
|
class AccountError(RTVError):
|
||||||
|
"Could not access user account"
|
||||||
|
|
||||||
|
|
||||||
|
class SubmissionError(RTVError):
|
||||||
|
"Submission could not be loaded"
|
||||||
|
|
||||||
def __init__(self, url):
|
def __init__(self, url):
|
||||||
self.url = url
|
self.url = url
|
||||||
|
|
||||||
|
|
||||||
class SubredditError(Exception):
|
class SubredditError(RTVError):
|
||||||
"""Subreddit could not be reached"""
|
"Subreddit could not be reached"
|
||||||
|
|
||||||
def __init__(self, name):
|
def __init__(self, name):
|
||||||
self.name = name
|
self.name = name
|
||||||
|
|
||||||
|
|
||||||
class ProgramError(Exception):
|
class ProgramError(RTVError):
|
||||||
"""Problem executing an external program"""
|
"Problem executing an external program"
|
||||||
|
|
||||||
def __init__(self, name):
|
def __init__(self, name):
|
||||||
self.name = name
|
self.name = name
|
||||||
|
|
||||||
|
|
||||||
class EscapeInterrupt(Exception):
|
|
||||||
"""Signal that the ESC key has been pressed"""
|
|
||||||
|
|||||||
22
rtv/page.py
22
rtv/page.py
@@ -12,7 +12,6 @@ __all__ = ['Navigator']
|
|||||||
|
|
||||||
|
|
||||||
class Navigator(object):
|
class Navigator(object):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Handles math behind cursor movement and screen paging.
|
Handles math behind cursor movement and screen paging.
|
||||||
"""
|
"""
|
||||||
@@ -87,6 +86,7 @@ class Navigator(object):
|
|||||||
|
|
||||||
def flip(self, n_windows):
|
def flip(self, n_windows):
|
||||||
"Flip the orientation of the page"
|
"Flip the orientation of the page"
|
||||||
|
|
||||||
self.page_index += (self.step * n_windows)
|
self.page_index += (self.step * n_windows)
|
||||||
self.cursor_index = n_windows
|
self.cursor_index = n_windows
|
||||||
self.inverted = not self.inverted
|
self.inverted = not self.inverted
|
||||||
@@ -103,7 +103,6 @@ class Navigator(object):
|
|||||||
|
|
||||||
|
|
||||||
class BaseController(object):
|
class BaseController(object):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Event handler for triggering functions with curses keypresses.
|
Event handler for triggering functions with curses keypresses.
|
||||||
|
|
||||||
@@ -153,7 +152,6 @@ class BaseController(object):
|
|||||||
|
|
||||||
|
|
||||||
class BasePage(object):
|
class BasePage(object):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Base terminal viewer incorperates a cursor to navigate content
|
Base terminal viewer incorperates a cursor to navigate content
|
||||||
"""
|
"""
|
||||||
@@ -192,6 +190,7 @@ class BasePage(object):
|
|||||||
|
|
||||||
def clear_input_queue(self):
|
def clear_input_queue(self):
|
||||||
"Clear excessive input caused by the scroll wheel or holding down a key"
|
"Clear excessive input caused by the scroll wheel or holding down a key"
|
||||||
|
|
||||||
self.stdscr.nodelay(1)
|
self.stdscr.nodelay(1)
|
||||||
while self.stdscr.getch() != -1:
|
while self.stdscr.getch() != -1:
|
||||||
continue
|
continue
|
||||||
@@ -210,7 +209,7 @@ class BasePage(object):
|
|||||||
data['object'].upvote()
|
data['object'].upvote()
|
||||||
data['likes'] = True
|
data['likes'] = True
|
||||||
except praw.errors.LoginOrScopeRequired:
|
except praw.errors.LoginOrScopeRequired:
|
||||||
show_notification(self.stdscr, ['Login to vote'])
|
show_notification(self.stdscr, ['Not logged in'])
|
||||||
|
|
||||||
@BaseController.register('z')
|
@BaseController.register('z')
|
||||||
def downvote(self):
|
def downvote(self):
|
||||||
@@ -225,13 +224,13 @@ class BasePage(object):
|
|||||||
data['object'].downvote()
|
data['object'].downvote()
|
||||||
data['likes'] = False
|
data['likes'] = False
|
||||||
except praw.errors.LoginOrScopeRequired:
|
except praw.errors.LoginOrScopeRequired:
|
||||||
show_notification(self.stdscr, ['Login to vote'])
|
show_notification(self.stdscr, ['Not logged in'])
|
||||||
|
|
||||||
@BaseController.register('u')
|
@BaseController.register('u')
|
||||||
def login(self):
|
def login(self):
|
||||||
"""
|
"""
|
||||||
Prompt to log into the user's account. Log out if the user is already
|
Prompt to log into the user's account, or log out of the current
|
||||||
logged in.
|
account.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.reddit.is_logged_in():
|
if self.reddit.is_logged_in():
|
||||||
@@ -252,9 +251,7 @@ class BasePage(object):
|
|||||||
show_notification(self.stdscr, ['Logged in'])
|
show_notification(self.stdscr, ['Logged in'])
|
||||||
|
|
||||||
def logout(self):
|
def logout(self):
|
||||||
"""
|
"Prompt to log out of the user's account."
|
||||||
Prompt to log out of the user's account.
|
|
||||||
"""
|
|
||||||
|
|
||||||
ch = self.prompt_input("Log out? (y/n):")
|
ch = self.prompt_input("Log out? (y/n):")
|
||||||
if ch == 'y':
|
if ch == 'y':
|
||||||
@@ -264,7 +261,8 @@ class BasePage(object):
|
|||||||
curses.flash()
|
curses.flash()
|
||||||
|
|
||||||
def prompt_input(self, prompt, hide=False):
|
def prompt_input(self, prompt, hide=False):
|
||||||
"""Prompt the user for input"""
|
"Prompt the user for input"
|
||||||
|
|
||||||
attr = curses.A_BOLD | Color.CYAN
|
attr = curses.A_BOLD | Color.CYAN
|
||||||
n_rows, n_cols = self.stdscr.getmaxyx()
|
n_rows, n_cols = self.stdscr.getmaxyx()
|
||||||
|
|
||||||
@@ -397,4 +395,4 @@ class BasePage(object):
|
|||||||
for row in range(n_rows):
|
for row in range(n_rows):
|
||||||
window.chgat(row, 0, 1, attribute)
|
window.chgat(row, 0, 1, attribute)
|
||||||
|
|
||||||
window.refresh()
|
window.refresh()
|
||||||
@@ -24,9 +24,9 @@ class SubmissionPage(BasePage):
|
|||||||
|
|
||||||
self.controller = SubmissionController(self)
|
self.controller = SubmissionController(self)
|
||||||
self.loader = LoadScreen(stdscr)
|
self.loader = LoadScreen(stdscr)
|
||||||
if url is not None:
|
if url:
|
||||||
content = SubmissionContent.from_url(reddit, url, self.loader)
|
content = SubmissionContent.from_url(reddit, url, self.loader)
|
||||||
elif submission is not None:
|
elif submission:
|
||||||
content = SubmissionContent(submission, self.loader)
|
content = SubmissionContent(submission, self.loader)
|
||||||
else:
|
else:
|
||||||
raise ValueError('Must specify url or submission')
|
raise ValueError('Must specify url or submission')
|
||||||
@@ -35,6 +35,8 @@ class SubmissionPage(BasePage):
|
|||||||
content, page_index=-1)
|
content, page_index=-1)
|
||||||
|
|
||||||
def loop(self):
|
def loop(self):
|
||||||
|
"Main control loop"
|
||||||
|
|
||||||
self.active = True
|
self.active = True
|
||||||
while self.active:
|
while self.active:
|
||||||
self.draw()
|
self.draw()
|
||||||
@@ -43,6 +45,8 @@ class SubmissionPage(BasePage):
|
|||||||
|
|
||||||
@SubmissionController.register(curses.KEY_RIGHT, 'l')
|
@SubmissionController.register(curses.KEY_RIGHT, 'l')
|
||||||
def toggle_comment(self):
|
def toggle_comment(self):
|
||||||
|
"Toggle the selected comment tree between visible and hidden"
|
||||||
|
|
||||||
current_index = self.nav.absolute_index
|
current_index = self.nav.absolute_index
|
||||||
self.content.toggle(current_index)
|
self.content.toggle(current_index)
|
||||||
if self.nav.inverted:
|
if self.nav.inverted:
|
||||||
@@ -53,20 +57,24 @@ class SubmissionPage(BasePage):
|
|||||||
|
|
||||||
@SubmissionController.register(curses.KEY_LEFT, 'h')
|
@SubmissionController.register(curses.KEY_LEFT, 'h')
|
||||||
def exit_submission(self):
|
def exit_submission(self):
|
||||||
|
"Close the submission and return to the subreddit page"
|
||||||
|
|
||||||
self.active = False
|
self.active = False
|
||||||
|
|
||||||
@SubmissionController.register(curses.KEY_F5, 'r')
|
@SubmissionController.register(curses.KEY_F5, 'r')
|
||||||
def refresh_content(self):
|
def refresh_content(self):
|
||||||
url = self.content.name
|
"Re-download comments reset the page index"
|
||||||
|
|
||||||
self.content = SubmissionContent.from_url(
|
self.content = SubmissionContent.from_url(
|
||||||
self.reddit,
|
self.reddit,
|
||||||
url,
|
self.content.name,
|
||||||
self.loader)
|
self.loader)
|
||||||
self.nav = Navigator(self.content.get, page_index=-1)
|
self.nav = Navigator(self.content.get, page_index=-1)
|
||||||
|
|
||||||
@SubmissionController.register(curses.KEY_ENTER, 10, 'o')
|
@SubmissionController.register(curses.KEY_ENTER, 10, 'o')
|
||||||
def open_link(self):
|
def open_link(self):
|
||||||
# Always open the page for the submission
|
"Open the current submission page with the webbrowser"
|
||||||
|
|
||||||
# May want to expand at some point to open comment permalinks
|
# May want to expand at some point to open comment permalinks
|
||||||
url = self.content.get(-1)['permalink']
|
url = self.content.get(-1)['permalink']
|
||||||
open_browser(url)
|
open_browser(url)
|
||||||
@@ -74,11 +82,12 @@ class SubmissionPage(BasePage):
|
|||||||
@SubmissionController.register('c')
|
@SubmissionController.register('c')
|
||||||
def add_comment(self):
|
def add_comment(self):
|
||||||
"""
|
"""
|
||||||
Add a comment on the submission if a header is selected.
|
Add a top-level comment if the submission is selected, or reply to the
|
||||||
Reply to a comment if the comment is selected.
|
selected comment.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not self.reddit.is_logged_in():
|
if not self.reddit.is_logged_in():
|
||||||
show_notification(self.stdscr, ["Login to reply"])
|
show_notification(self.stdscr, ['Not logged in'])
|
||||||
return
|
return
|
||||||
|
|
||||||
data = self.content.get(self.nav.absolute_index)
|
data = self.content.get(self.nav.absolute_index)
|
||||||
@@ -100,19 +109,19 @@ class SubmissionPage(BasePage):
|
|||||||
curses.endwin()
|
curses.endwin()
|
||||||
comment_text = open_editor(comment_info)
|
comment_text = open_editor(comment_info)
|
||||||
curses.doupdate()
|
curses.doupdate()
|
||||||
|
|
||||||
if not comment_text:
|
if not comment_text:
|
||||||
curses.flash()
|
curses.flash()
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if data['type'] == 'Submission':
|
if data['type'] == 'Submission':
|
||||||
data['object'].add_comment(comment_text)
|
data['object'].add_comment(comment_text)
|
||||||
else:
|
else:
|
||||||
data['object'].reply(comment_text)
|
data['object'].reply(comment_text)
|
||||||
except praw.errors.APIException as e:
|
except praw.errors.APIException:
|
||||||
show_notification(self.stdscr, [e.message])
|
curses.flash()
|
||||||
else:
|
else:
|
||||||
time.sleep(0.5)
|
time.sleep(2.0)
|
||||||
self.refresh_content()
|
self.refresh_content()
|
||||||
|
|
||||||
def draw_item(self, win, data, inverted=False):
|
def draw_item(self, win, data, inverted=False):
|
||||||
@@ -248,4 +257,4 @@ class SubmissionPage(BasePage):
|
|||||||
text, attr = GOLD, (curses.A_BOLD | Color.YELLOW)
|
text, attr = GOLD, (curses.A_BOLD | Color.YELLOW)
|
||||||
win.addnstr(text, n_cols - win.getyx()[1], attr)
|
win.addnstr(text, n_cols - win.getyx()[1], attr)
|
||||||
|
|
||||||
win.border()
|
win.border()
|
||||||
@@ -3,7 +3,7 @@ import time
|
|||||||
import requests
|
import requests
|
||||||
import praw
|
import praw
|
||||||
|
|
||||||
from .exceptions import SubredditError
|
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 .content import SubredditContent
|
from .content import SubredditContent
|
||||||
@@ -33,6 +33,8 @@ class SubredditPage(BasePage):
|
|||||||
super(SubredditPage, self).__init__(stdscr, reddit, content)
|
super(SubredditPage, self).__init__(stdscr, reddit, content)
|
||||||
|
|
||||||
def loop(self):
|
def loop(self):
|
||||||
|
"Main control loop"
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
self.draw()
|
self.draw()
|
||||||
cmd = self.stdscr.getch()
|
cmd = self.stdscr.getch()
|
||||||
@@ -40,13 +42,14 @@ class SubredditPage(BasePage):
|
|||||||
|
|
||||||
@SubredditController.register(curses.KEY_F5, 'r')
|
@SubredditController.register(curses.KEY_F5, 'r')
|
||||||
def refresh_content(self, name=None):
|
def refresh_content(self, name=None):
|
||||||
|
"Re-download all submissions and reset the page index"
|
||||||
|
|
||||||
name = name or self.content.name
|
name = name or self.content.name
|
||||||
if name == 'me' or name == '/r/me':
|
|
||||||
self.redditor_profile()
|
|
||||||
return
|
|
||||||
try:
|
try:
|
||||||
self.content = SubredditContent.from_name(
|
self.content = SubredditContent.from_name(
|
||||||
self.reddit, name, self.loader)
|
self.reddit, name, self.loader)
|
||||||
|
except AccountError:
|
||||||
|
show_notification(self.stdscr, ['Not logged in'])
|
||||||
except SubredditError:
|
except SubredditError:
|
||||||
show_notification(self.stdscr, ['Invalid subreddit'])
|
show_notification(self.stdscr, ['Invalid subreddit'])
|
||||||
except requests.HTTPError:
|
except requests.HTTPError:
|
||||||
@@ -56,36 +59,30 @@ class SubredditPage(BasePage):
|
|||||||
|
|
||||||
@SubredditController.register('f')
|
@SubredditController.register('f')
|
||||||
def search_subreddit(self, name=None):
|
def search_subreddit(self, name=None):
|
||||||
"""Open a prompt to search the subreddit"""
|
"Open a prompt to search the given subreddit"
|
||||||
|
|
||||||
name = name or self.content.name
|
name = name or self.content.name
|
||||||
prompt = 'Search this Subreddit: '
|
prompt = 'Search:'
|
||||||
query = self.prompt_input(prompt)
|
query = self.prompt_input(prompt)
|
||||||
if query is not None:
|
if query is None:
|
||||||
try:
|
return
|
||||||
self.nav.cursor_index = 0
|
|
||||||
self.content = SubredditContent.from_name(self.reddit, name,
|
try:
|
||||||
self.loader, query=query)
|
self.content = SubredditContent.from_name(
|
||||||
except IndexError: # if there are no submissions
|
self.reddit, name, self.loader, query=query)
|
||||||
show_notification(self.stdscr, ['No results found'])
|
except IndexError: # if there are no submissions
|
||||||
|
show_notification(self.stdscr, ['No results found'])
|
||||||
|
else:
|
||||||
|
self.nav = Navigator(self.content.get)
|
||||||
|
|
||||||
@SubredditController.register('/')
|
@SubredditController.register('/')
|
||||||
def prompt_subreddit(self):
|
def prompt_subreddit(self):
|
||||||
"""Open a prompt to type in a new subreddit"""
|
"Open a prompt to navigate to a different subreddit"
|
||||||
prompt = 'Enter Subreddit: /r/'
|
prompt = 'Enter Subreddit: /r/'
|
||||||
name = self.prompt_input(prompt)
|
name = self.prompt_input(prompt)
|
||||||
if name is not None:
|
if name is not None:
|
||||||
self.refresh_content(name=name)
|
self.refresh_content(name=name)
|
||||||
|
|
||||||
def redditor_profile(self):
|
|
||||||
if self.reddit.is_logged_in():
|
|
||||||
try:
|
|
||||||
self.content = SubredditContent.from_redditor(
|
|
||||||
self.reddit, self.loader)
|
|
||||||
except requests.HTTPError:
|
|
||||||
show_notification(self.stdscr, ['Could not reach subreddit'])
|
|
||||||
else:
|
|
||||||
show_notification(self.stdscr, ['Log in to view your submissions'])
|
|
||||||
|
|
||||||
@SubredditController.register(curses.KEY_RIGHT, 'l')
|
@SubredditController.register(curses.KEY_RIGHT, 'l')
|
||||||
def open_submission(self):
|
def open_submission(self):
|
||||||
"Select the current submission to view posts"
|
"Select the current submission to view posts"
|
||||||
@@ -110,19 +107,18 @@ class SubredditPage(BasePage):
|
|||||||
|
|
||||||
@SubredditController.register('p')
|
@SubredditController.register('p')
|
||||||
def post_submission(self):
|
def post_submission(self):
|
||||||
# Abort if user isn't logged in
|
"Post a new submission to the given subreddit"
|
||||||
|
|
||||||
if not self.reddit.is_logged_in():
|
if not self.reddit.is_logged_in():
|
||||||
show_notification(self.stdscr, ['Login to reply'])
|
show_notification(self.stdscr, ['Not logged in'])
|
||||||
return
|
return
|
||||||
|
|
||||||
subreddit = self.reddit.get_subreddit(self.content.name)
|
|
||||||
|
|
||||||
# Make sure it is a valid subreddit for submission
|
|
||||||
# Strips the subreddit to just the name
|
# Strips the subreddit to just the name
|
||||||
|
# Make sure it is a valid subreddit for submission
|
||||||
|
subreddit = self.reddit.get_subreddit(self.content.name)
|
||||||
sub = str(subreddit).split('/')[2]
|
sub = str(subreddit).split('/')[2]
|
||||||
if '+' in sub or sub == 'all' or sub == 'front':
|
if '+' in sub or sub in ('all', 'front', 'me'):
|
||||||
message = 'Can\'t post to /r/{0}'.format(sub)
|
show_notification(self.stdscr, ['Invalid subreddit'])
|
||||||
show_notification(self.stdscr, [message])
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# Open the submission window
|
# Open the submission window
|
||||||
@@ -131,19 +127,22 @@ class SubredditPage(BasePage):
|
|||||||
submission_text = open_editor(submission_info)
|
submission_text = open_editor(submission_info)
|
||||||
curses.doupdate()
|
curses.doupdate()
|
||||||
|
|
||||||
# Abort if there is no content
|
# Validate the submission content
|
||||||
if not submission_text:
|
if not submission_text:
|
||||||
curses.flash()
|
curses.flash()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if '\n' not in submission_text:
|
||||||
|
show_notification(self.stdscr, ['No content'])
|
||||||
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
title, content = submission_text.split('\n', 1)
|
title, content = submission_text.split('\n', 1)
|
||||||
self.reddit.submit(sub, title, text=content)
|
self.reddit.submit(sub, title, text=content)
|
||||||
except praw.errors.APIException as e:
|
except praw.errors.APIException:
|
||||||
show_notification(self.stdscr, [e.message])
|
curses.flash()
|
||||||
except ValueError:
|
|
||||||
show_notification(self.stdscr, ['No post content! Post aborted.'])
|
|
||||||
else:
|
else:
|
||||||
time.sleep(0.5)
|
time.sleep(2.0)
|
||||||
self.refresh_content()
|
self.refresh_content()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -197,4 +196,4 @@ class SubredditPage(BasePage):
|
|||||||
text = clean(u' {subreddit}'.format(**data))
|
text = clean(u' {subreddit}'.format(**data))
|
||||||
win.addnstr(text, n_cols - win.getyx()[1], Color.YELLOW)
|
win.addnstr(text, n_cols - win.getyx()[1], Color.YELLOW)
|
||||||
text = clean(u' {flair}'.format(**data))
|
text = clean(u' {flair}'.format(**data))
|
||||||
win.addnstr(text, n_cols - win.getyx()[1], Color.RED)
|
win.addnstr(text, n_cols - win.getyx()[1], Color.RED)
|
||||||
Reference in New Issue
Block a user