Merge branch 'master' into woorst-imgur_api
This commit is contained in:
@@ -11,6 +11,18 @@ import warnings
|
||||
import six
|
||||
import requests
|
||||
|
||||
# Need to check for curses comparability before performing the rtv imports
|
||||
try:
|
||||
import curses
|
||||
except ImportError:
|
||||
if sys.platform == 'win32':
|
||||
sys.exit('Fatal Error: This program is not compatible with Windows '
|
||||
'Operating Systems.\nPlease try installing on either Linux '
|
||||
'or Mac OS')
|
||||
else:
|
||||
sys.exit('Fatal Error: Your python distribution appears to be missing '
|
||||
'_curses.so.\nWas it compiled without support for curses?')
|
||||
|
||||
from . import docs
|
||||
from . import packages
|
||||
from .packages import praw
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
__version__ = '1.15.1'
|
||||
__version__ = '1.16.0'
|
||||
|
||||
@@ -159,7 +159,7 @@ class Content(object):
|
||||
else:
|
||||
# Saved comments don't have a nested level and are missing a couple
|
||||
# of fields like ``submission``. As a result, we can only load a
|
||||
# subset of fields to avoid triggering a seperate api call to load
|
||||
# subset of fields to avoid triggering a separate api call to load
|
||||
# the full comment.
|
||||
author = getattr(comment, 'author', '[deleted]')
|
||||
stickied = getattr(comment, 'stickied', False)
|
||||
@@ -167,10 +167,11 @@ class Content(object):
|
||||
|
||||
data['type'] = 'SavedComment'
|
||||
data['level'] = None
|
||||
data['title'] = comment.body
|
||||
data['comments'] = ''
|
||||
data['title'] = '[Comment] {0}'.format(comment.body)
|
||||
data['comments'] = None
|
||||
data['url_full'] = comment._fast_permalink
|
||||
data['url'] = comment._fast_permalink
|
||||
data['permalink'] = comment._fast_permalink
|
||||
data['nsfw'] = comment.over_18
|
||||
data['subreddit'] = six.text_type(comment.subreddit)
|
||||
data['url_type'] = 'selfpost'
|
||||
@@ -565,7 +566,7 @@ class SubredditContent(Content):
|
||||
raise exceptions.AccountError('Not logged in')
|
||||
else:
|
||||
order = order or 'new'
|
||||
submissions = reddit.user.get_submitted(sort=order, limit=None)
|
||||
submissions = reddit.user.get_overview(sort=order, limit=None)
|
||||
|
||||
elif resource_root == 'u' and resource == 'saved':
|
||||
if not reddit.is_oauth_session():
|
||||
@@ -578,7 +579,7 @@ class SubredditContent(Content):
|
||||
order = order or 'new'
|
||||
period = period or 'all'
|
||||
redditor = reddit.get_redditor(resource)
|
||||
submissions = redditor.get_submitted(
|
||||
submissions = redditor.get_overview(
|
||||
sort=order, time=period, limit=None)
|
||||
|
||||
elif resource == 'front':
|
||||
|
||||
@@ -30,6 +30,8 @@ https://github.com/michael-lazar/rtv
|
||||
m : Move up one page
|
||||
gg : Jump to the first post
|
||||
G : Jump to the last post
|
||||
J : Jump to the next sibling comment
|
||||
K : Jump to the parent comment
|
||||
1 : Sort by hot
|
||||
2 : Sort by top
|
||||
3 : Sort by rising
|
||||
|
||||
@@ -48,4 +48,8 @@ class TemporaryFileError(RTVError):
|
||||
|
||||
|
||||
class MailcapEntryNotFound(RTVError):
|
||||
"A valid mailcap entry could not be coerced from the given url"
|
||||
"A valid mailcap entry could not be coerced from the given url"
|
||||
|
||||
|
||||
class InvalidRefreshToken(RTVError):
|
||||
"The refresh token is corrupt and cannot be used to login"
|
||||
|
||||
@@ -273,6 +273,36 @@ class VidmeMIMEParser(BaseMIMEParser):
|
||||
return url, None
|
||||
|
||||
|
||||
class LiveleakMIMEParser(BaseMIMEParser):
|
||||
"""
|
||||
https://www.liveleak.com/view?i=12c_3456789
|
||||
<video>
|
||||
<source src="https://cdn.liveleak.com/..mp4" res="HD" type="video/mp4">
|
||||
<source src="https://cdn.liveleak.com/..mp4" res="SD" type="video/mp4">
|
||||
</video>
|
||||
Sometimes only one video source is available
|
||||
"""
|
||||
pattern = re.compile(r'https?://((www|m)\.)?liveleak\.com/view\?i=\w+$')
|
||||
|
||||
@staticmethod
|
||||
def get_mimetype(url):
|
||||
page = requests.get(url)
|
||||
soup = BeautifulSoup(page.content, 'html.parser')
|
||||
|
||||
urls = []
|
||||
videos = soup.find_all('video')
|
||||
for vid in videos:
|
||||
source = vid.find('source', attr={'res': 'HD'}) \
|
||||
or vid.find('source')
|
||||
if source:
|
||||
urls.append((source.get('src'), source.get('type')))
|
||||
# TODO: Handle pages with multiple videos
|
||||
# TODO: Handle pages with youtube embeds
|
||||
if urls:
|
||||
return urls[0]
|
||||
else:
|
||||
return url, None
|
||||
|
||||
# Parsers should be listed in the order they will be checked
|
||||
parsers = [
|
||||
StreamableMIMEParser,
|
||||
@@ -282,5 +312,6 @@ parsers = [
|
||||
ImgurApiMIMEParser,
|
||||
RedditUploadsMIMEParser,
|
||||
YoutubeMIMEParser,
|
||||
LiveleakMIMEParser,
|
||||
GifvMIMEParser,
|
||||
BaseMIMEParser]
|
||||
|
||||
25
rtv/oauth.py
25
rtv/oauth.py
@@ -15,6 +15,8 @@ from six.moves.BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
|
||||
|
||||
from . import docs
|
||||
from .config import TEMPLATES
|
||||
from .exceptions import InvalidRefreshToken
|
||||
from .packages.praw.errors import HTTPException, OAuthException
|
||||
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
@@ -131,8 +133,25 @@ class OAuthHelper(object):
|
||||
# If we already have a token, request new access credentials
|
||||
if self.config.refresh_token:
|
||||
with self.term.loader('Logging in'):
|
||||
self.reddit.refresh_access_information(
|
||||
self.config.refresh_token)
|
||||
try:
|
||||
self.reddit.refresh_access_information(
|
||||
self.config.refresh_token)
|
||||
except (HTTPException, OAuthException) as e:
|
||||
# Reddit didn't accept the refresh-token
|
||||
# This appears to throw a generic 400 error instead of the
|
||||
# more specific invalid_token message that it used to send
|
||||
if isinstance(e, HTTPException):
|
||||
if e._raw.status_code != 400:
|
||||
# No special handling if the error is something
|
||||
# temporary like a 5XX.
|
||||
raise e
|
||||
|
||||
# Otherwise we know the token is bad, so we can remove it.
|
||||
_logger.exception(e)
|
||||
self.clear_oauth_data()
|
||||
raise InvalidRefreshToken(
|
||||
' Invalid user credentials!\n'
|
||||
'The cached refresh token has been removed')
|
||||
return
|
||||
|
||||
state = uuid.uuid4().hex
|
||||
@@ -207,4 +226,4 @@ class OAuthHelper(object):
|
||||
|
||||
def clear_oauth_data(self):
|
||||
self.reddit.clear_authentication()
|
||||
self.config.delete_refresh_token()
|
||||
self.config.delete_refresh_token()
|
||||
|
||||
@@ -10,7 +10,7 @@ from __future__ import absolute_import
|
||||
import sys
|
||||
|
||||
|
||||
__praw_hash__ = '3bc535e4778047b78d9aeb2e3a5b108f96a091b2'
|
||||
__praw_hash__ = 'ad0dbcf49d5937ffd39e13e45ebcb404b00c582a'
|
||||
__praw_bundled__ = True
|
||||
|
||||
|
||||
|
||||
@@ -113,7 +113,7 @@ class RedditContentObject(object):
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
"""Set the `name` attribute to `value."""
|
||||
if value and name == 'subreddit':
|
||||
if value and name == 'subreddit' and isinstance(value, six.string_types):
|
||||
value = Subreddit(self.reddit_session, value, fetch=False)
|
||||
elif value and name in REDDITOR_KEYS:
|
||||
if isinstance(value, bool):
|
||||
|
||||
@@ -390,14 +390,7 @@ class Page(object):
|
||||
self._draw_content()
|
||||
self._draw_footer()
|
||||
self._add_cursor()
|
||||
# Note: There used to be a call to stdscr.touchwin() here. However, a
|
||||
# bug was discovered in tmux when $TERM was set to `xterm-256color`,
|
||||
# where only part of the screen got redrawn when scrolling. The correct
|
||||
# solution is to use `screen-256color` (which gets set automatically by
|
||||
# tmux) but many people override ther $TERM in their tmux.conf or
|
||||
# .bashrc file. Using clearok() instead seems to fix the problem, at
|
||||
# the expense of slightly more expensive screen refreshes.
|
||||
self.term.stdscr.clearok(True)
|
||||
self.term.clear_screen()
|
||||
self.term.stdscr.refresh()
|
||||
|
||||
def _draw_header(self):
|
||||
|
||||
@@ -181,6 +181,52 @@ class SubmissionPage(Page):
|
||||
else:
|
||||
self.term.flash()
|
||||
|
||||
@PageController.register(Command('SUBMISSION_GOTO_PARENT'))
|
||||
def move_parent_up(self):
|
||||
"""
|
||||
Move the cursor up to the comment's parent. If the comment is
|
||||
top-level, jump to the previous top-level comment.
|
||||
"""
|
||||
|
||||
cursor = self.nav.absolute_index
|
||||
if cursor > 0:
|
||||
level = max(self.content.get(cursor)['level'], 1)
|
||||
while self.content.get(cursor - 1)['level'] >= level:
|
||||
self._move_cursor(-1)
|
||||
cursor -= 1
|
||||
self._move_cursor(-1)
|
||||
else:
|
||||
self.term.flash()
|
||||
|
||||
self.clear_input_queue()
|
||||
|
||||
@PageController.register(Command('SUBMISSION_GOTO_SIBLING'))
|
||||
def move_sibling_next(self):
|
||||
"""
|
||||
Jump to the next comment that's at the same level as the selected
|
||||
comment and shares the same parent.
|
||||
"""
|
||||
|
||||
cursor = self.nav.absolute_index
|
||||
if cursor >= 0:
|
||||
level = self.content.get(cursor)['level']
|
||||
try:
|
||||
move = 1
|
||||
while self.content.get(cursor + move)['level'] > level:
|
||||
move += 1
|
||||
except IndexError:
|
||||
self.term.flash()
|
||||
else:
|
||||
if self.content.get(cursor + move)['level'] == level:
|
||||
for _ in range(move):
|
||||
self._move_cursor(1)
|
||||
else:
|
||||
self.term.flash()
|
||||
else:
|
||||
self.term.flash()
|
||||
|
||||
self.clear_input_queue()
|
||||
|
||||
def _draw_item(self, win, data, inverted):
|
||||
|
||||
if data['type'] == 'MoreComments':
|
||||
|
||||
@@ -260,9 +260,11 @@ class SubredditPage(Page):
|
||||
text, attr = self.term.get_arrow(data['likes'])
|
||||
self.term.add_line(win, text, attr=attr)
|
||||
self.term.add_line(win, ' {created} '.format(**data))
|
||||
text, attr = '-', curses.A_BOLD
|
||||
self.term.add_line(win, text, attr=attr)
|
||||
self.term.add_line(win, ' {comments} '.format(**data))
|
||||
|
||||
if data['comments'] is not None:
|
||||
text, attr = '-', curses.A_BOLD
|
||||
self.term.add_line(win, text, attr=attr)
|
||||
self.term.add_line(win, ' {comments} '.format(**data))
|
||||
|
||||
if data['saved']:
|
||||
text, attr = '[saved]', Color.GREEN
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
# - The first entry with a matching MIME type will be executed, * is a wildcard
|
||||
# - %s will be replaced with the image or video url
|
||||
# - Add ``test=test -n "$DISPLAY"`` if your command opens a new window
|
||||
# - Add ``needstermial`` for commands that use the terminal
|
||||
# - Add ``needsterminal`` for commands that use the terminal
|
||||
# - Add ``copiousoutput`` for commands that dump text to stdout
|
||||
|
||||
###############################################################################
|
||||
|
||||
@@ -133,6 +133,8 @@ SUBMISSION_POST = c
|
||||
SUBMISSION_EXIT = h, <KEY_LEFT>
|
||||
SUBMISSION_OPEN_IN_PAGER = l, <KEY_RIGHT>
|
||||
SUBMISSION_OPEN_IN_URLVIEWER = b
|
||||
SUBMISSION_GOTO_PARENT = K
|
||||
SUBMISSION_GOTO_SIBLING = J
|
||||
|
||||
; Subreddit page
|
||||
SUBREDDIT_SEARCH = f
|
||||
|
||||
@@ -59,6 +59,7 @@ class Terminal(object):
|
||||
self.loader = LoadScreen(self)
|
||||
self._display = None
|
||||
self._mailcap_dict = mailcap.getcaps()
|
||||
self._term = os.environ['TERM']
|
||||
|
||||
# Hack to allow setting the Imgur OAuth cred in the config file
|
||||
mime_parsers.ImgurApiMIMEParser.client_id = config['imgur_client_id']
|
||||
@@ -772,3 +773,30 @@ class Terminal(object):
|
||||
|
||||
out = '\n'.join(stack)
|
||||
return out
|
||||
|
||||
def clear_screen(self):
|
||||
"""
|
||||
In the beginning this always called touchwin(). However, a bug
|
||||
was discovered in tmux when TERM was set to `xterm-256color`, where
|
||||
only part of the screen got redrawn when scrolling. tmux automatically
|
||||
sets TERM to `screen-256color`, but many people choose to override
|
||||
this in their tmux.conf or .bashrc file which can cause issues.
|
||||
Using clearok() instead seems to fix the problem, with the trade off
|
||||
of slightly more expensive screen refreshes.
|
||||
|
||||
Update: It was discovered that using clearok() introduced a
|
||||
separate bug for urxvt users in which their screen flashed when
|
||||
scrolling. Heuristics were added to make it work with as many
|
||||
configurations as possible. It's still not perfect
|
||||
(e.g. urxvt + xterm-256color) will screen flash, but it should
|
||||
work in all cases if the user sets their TERM correctly.
|
||||
|
||||
Reference:
|
||||
https://github.com/michael-lazar/rtv/issues/343
|
||||
https://github.com/michael-lazar/rtv/issues/323
|
||||
"""
|
||||
|
||||
if self._term != 'xterm-256color':
|
||||
self.stdscr.touchwin()
|
||||
else:
|
||||
self.stdscr.clearok(True)
|
||||
|
||||
Reference in New Issue
Block a user