commit 26fe54f20c2c6dcdbc3d2e2c0cb6b449b7218f54
Author: deepend
Date: Fri Mar 8 16:51:35 2024 +0000
first commit
diff --git a/.coveragerc b/.coveragerc
new file mode 100644
index 0000000..19e2d07
--- /dev/null
+++ b/.coveragerc
@@ -0,0 +1,5 @@
+[run]
+source = tvr
+omit =
+ */__main__.py
+ */packages/praw/*
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..91b671f
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1 @@
+tests/cassettes/* binary
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1035dd7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,14 @@
+.*
+!.travis.yml
+!.pylintrc
+!.gitignore
+!.gitattributes
+!.coveragerc
+*~
+*.pyc
+*.log
+build
+dist
+rtv.egg-info
+tests/refresh-token
+venv/
diff --git a/.pylintrc b/.pylintrc
new file mode 100644
index 0000000..7c635bb
--- /dev/null
+++ b/.pylintrc
@@ -0,0 +1,378 @@
+[MASTER]
+
+# Specify a configuration file.
+#rcfile=
+
+# Python code to execute, usually for sys.path manipulation such as
+# pygtk.require().
+#init-hook=
+
+# Add files or directories to the blacklist. They should be base names, not
+# paths.
+ignore=praw
+
+# Pickle collected data for later comparisons.
+persistent=yes
+
+# List of plugins (as comma separated values of python modules names) to load,
+# usually to register additional checkers.
+load-plugins=
+
+# Use multiple processes to speed up Pylint.
+jobs=1
+
+# Allow loading of arbitrary C extensions. Extensions are imported into the
+# active Python interpreter and may run arbitrary code.
+unsafe-load-any-extension=no
+
+# A comma-separated list of package or module names from where C extensions may
+# be loaded. Extensions are loading into the active Python interpreter and may
+# run arbitrary code
+extension-pkg-whitelist=
+
+# Allow optimization of some AST trees. This will activate a peephole AST
+# optimizer, which will apply various small optimizations. For instance, it can
+# be used to obtain the result of joining multiple strings with the addition
+# operator. Joining a lot of strings can lead to a maximum recursion error in
+# Pylint and this flag can prevent that. It has one side effect, the resulting
+# AST will be different than the one from reality.
+optimize-ast=no
+
+
+[MESSAGES CONTROL]
+
+# Only show warnings with the listed confidence levels. Leave empty to show
+# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED
+confidence=
+
+# Enable the message, report, category or checker with the given id(s). You can
+# either give multiple identifier separated by comma (,) or put this option
+# multiple time. See also the "--disable" option for examples.
+#enable=
+
+# Disable the message, report, category or checker with the given id(s). You
+# can either give multiple identifiers separated by comma (,) or put this
+# option multiple times (only on the command line, not in the configuration
+# file where it should appear only once).You can also use "--disable=all" to
+# disable everything first and then reenable specific checks. For example, if
+# you want to run only the similarities checker, you can use "--disable=all
+# --enable=similarities". If you want to run only the classes checker, but have
+# no Warning level messages displayed, use"--disable=all --enable=classes
+# --disable=W"
+disable=execfile-builtin,zip-builtin-not-iterating,range-builtin-not-iterating,hex-method,old-division,file-builtin,long-builtin,input-builtin,no-absolute-import,invalid-name,delslice-method,suppressed-message,coerce-builtin,buffer-builtin,import-star-module-level,round-builtin,old-ne-operator,apply-builtin,missing-final-newline,basestring-builtin,xrange-builtin,getslice-method,filter-builtin-not-iterating,map-builtin-not-iterating,raw_input-builtin,indexing-exception,dict-iter-method,metaclass-assignment,setslice-method,next-method-called,intern-builtin,using-cmp-argument,missing-docstring,oct-method,backtick,print-statement,reload-builtin,long-suffix,old-raise-syntax,unicode-builtin,nonzero-method,old-octal-literal,cmp-method,useless-suppression,dict-view-method,parameter-unpacking,unpacking-in-except,coerce-method,unichr-builtin,raising-string,cmp-builtin,reduce-builtin,standarderror-builtin,no-else-return,too-many-locals,too-many-statements,too-few-public-methods,too-many-public-methods,too-many-instance-attributes
+
+
+[REPORTS]
+
+# Set the output format. Available formats are text, parseable, colorized, msvs
+# (visual studio) and html. You can also give a reporter class, eg
+# mypackage.mymodule.MyReporterClass.
+output-format=text
+
+# Put messages in a separate file for each module / package specified on the
+# command line instead of printing them on stdout. Reports (if any) will be
+# written in a file name "pylint_global.[txt|html]".
+files-output=no
+
+# Tells whether to display a full report or only the messages
+reports=yes
+
+# Python expression which should return a note less than 10 (10 is the highest
+# note). You have access to the variables errors warning, statement which
+# respectively contain the number of errors / warnings messages and the total
+# number of statements analyzed. This is used by the global evaluation report
+# (RP0004).
+evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
+
+# Template used to display messages. This is a python new-style format string
+# used to format the message information. See doc for all details
+#msg-template=
+
+
+[SIMILARITIES]
+
+# Minimum lines number of a similarity.
+min-similarity-lines=5
+
+# Ignore comments when computing similarities.
+ignore-comments=yes
+
+# Ignore docstrings when computing similarities.
+ignore-docstrings=yes
+
+# Ignore imports when computing similarities.
+ignore-imports=no
+
+
+[MISCELLANEOUS]
+
+# List of note tags to take in consideration, separated by a comma.
+notes=FIXME,XXX,TODO
+
+
+[VARIABLES]
+
+# Tells whether we should check for unused import in __init__ files.
+init-import=no
+
+# A regular expression matching the name of dummy variables (i.e. expectedly
+# not used).
+dummy-variables-rgx=_$|dummy
+
+# List of additional names supposed to be defined in builtins. Remember that
+# you should avoid to define new builtins when possible.
+additional-builtins=
+
+# List of strings which can identify a callback function by name. A callback
+# name must start or end with one of those strings.
+callbacks=cb_,_cb
+
+
+[LOGGING]
+
+# Logging modules to check that the string format arguments are in logging
+# function parameter format
+logging-modules=logging
+
+
+[SPELLING]
+
+# Spelling dictionary name. Available dictionaries: none. To make it working
+# install python-enchant package.
+spelling-dict=
+
+# List of comma separated words that should not be checked.
+spelling-ignore-words=
+
+# A path to a file that contains private dictionary; one word per line.
+spelling-private-dict-file=
+
+# Tells whether to store unknown words to indicated private dictionary in
+# --spelling-private-dict-file option instead of raising a message.
+spelling-store-unknown-words=no
+
+
+[TYPECHECK]
+
+# Tells whether missing members accessed in mixin class should be ignored. A
+# mixin class is detected if its name ends with "mixin" (case insensitive).
+ignore-mixin-members=yes
+
+# List of module names for which member attributes should not be checked
+# (useful for modules/projects where namespaces are manipulated during runtime
+# and thus existing member attributes cannot be deduced by static analysis. It
+# supports qualified module names, as well as Unix pattern matching.
+ignored-modules=
+
+# List of classes names for which member attributes should not be checked
+# (useful for classes with attributes dynamically set). This supports can work
+# with qualified names.
+ignored-classes=SQLObject
+
+# List of members which are set dynamically and missed by pylint inference
+# system, and so shouldn't trigger E1101 when accessed. Python regular
+# expressions are accepted.
+generated-members=REQUEST,acl_users,aq_parent
+
+
+[FORMAT]
+
+# Maximum number of characters on a single line.
+max-line-length=100
+
+# Regexp for a line that is allowed to be longer than the limit.
+ignore-long-lines=^\s*(# )??$
+
+# Allow the body of an if to be on the same line as the test if there is no
+# else.
+single-line-if-stmt=no
+
+# List of optional constructs for which whitespace checking is disabled. `dict-
+# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}.
+# `trailing-comma` allows a space between comma and closing bracket: (a, ).
+# `empty-line` allows space-only lines.
+no-space-check=trailing-comma,dict-separator
+
+# Maximum number of lines in a module
+max-module-lines=1000
+
+# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
+# tab).
+indent-string=' '
+
+# Number of spaces of indent required inside a hanging or continued line.
+indent-after-paren=4
+
+# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
+expected-line-ending-format=
+
+
+[BASIC]
+
+# List of builtins function names that should not be used, separated by a comma
+bad-functions=map,filter
+
+# Good variable names which should always be accepted, separated by a comma
+good-names=i,j,k,ex,Run,_
+
+# Bad variable names which should always be refused, separated by a comma
+bad-names=foo,bar,baz,toto,tutu,tata
+
+# Colon-delimited sets of names that determine each other's naming style when
+# the name regexes allow several styles.
+name-group=
+
+# Include a hint for the correct naming format with invalid-name
+include-naming-hint=no
+
+# Regular expression matching correct module names
+module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
+
+# Naming hint for module names
+module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
+
+# Regular expression matching correct variable names
+variable-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Naming hint for variable names
+variable-name-hint=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression matching correct class names
+class-rgx=[A-Z_][a-zA-Z0-9]+$
+
+# Naming hint for class names
+class-name-hint=[A-Z_][a-zA-Z0-9]+$
+
+# Regular expression matching correct attribute names
+attr-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Naming hint for attribute names
+attr-name-hint=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression matching correct constant names
+const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$
+
+# Naming hint for constant names
+const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$
+
+# Regular expression matching correct class attribute names
+class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
+
+# Naming hint for class attribute names
+class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
+
+# Regular expression matching correct argument names
+argument-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Naming hint for argument names
+argument-name-hint=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression matching correct function names
+function-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Naming hint for function names
+function-name-hint=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression matching correct method names
+method-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Naming hint for method names
+method-name-hint=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression matching correct inline iteration names
+inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
+
+# Naming hint for inline iteration names
+inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$
+
+# Regular expression which should only match function or class names that do
+# not require a docstring.
+no-docstring-rgx=__.*__
+
+# Minimum line length for functions/classes that require docstrings, shorter
+# ones are exempt.
+docstring-min-length=-1
+
+
+[ELIF]
+
+# Maximum number of nested blocks for function / method body
+max-nested-blocks=5
+
+
+[IMPORTS]
+
+# Deprecated modules which should not be used, separated by a comma
+deprecated-modules=stringprep,optparse
+
+# Create a graph of every (i.e. internal and external) dependencies in the
+# given file (report RP0402 must not be disabled)
+import-graph=
+
+# Create a graph of external dependencies in the given file (report RP0402 must
+# not be disabled)
+ext-import-graph=
+
+# Create a graph of internal dependencies in the given file (report RP0402 must
+# not be disabled)
+int-import-graph=
+
+
+[CLASSES]
+
+# List of method names used to declare (i.e. assign) instance attributes.
+defining-attr-methods=__init__,__new__,setUp
+
+# List of valid names for the first argument in a class method.
+valid-classmethod-first-arg=cls
+
+# List of valid names for the first argument in a metaclass class method.
+valid-metaclass-classmethod-first-arg=mcs
+
+# List of member names, which should be excluded from the protected access
+# warning.
+exclude-protected=_asdict,_fields,_replace,_source,_make
+
+
+[DESIGN]
+
+# Maximum number of arguments for function / method
+max-args=7
+
+# Argument names that match this expression will be ignored. Default to name
+# with leading underscore
+ignored-argument-names=_.*
+
+# Maximum number of locals for function / method body
+max-locals=15
+
+# Maximum number of return / yield for function / method body
+max-returns=6
+
+# Maximum number of branch for function / method body
+max-branches=12
+
+# Maximum number of statements in function / method body
+max-statements=50
+
+# Maximum number of parents for a class (see R0901).
+max-parents=7
+
+# Maximum number of attributes for a class (see R0902).
+max-attributes=7
+
+# Minimum number of public methods for a class (see R0903).
+min-public-methods=2
+
+# Maximum number of public methods for a class (see R0904).
+max-public-methods=20
+
+# Maximum number of boolean expressions in a if statement
+max-bool-expr=5
+
+
+[EXCEPTIONS]
+
+# Exceptions that will emit a warning when being caught. Defaults to
+# "Exception"
+overgeneral-exceptions=Exception
diff --git a/AUTHORS.rst b/AUTHORS.rst
new file mode 100644
index 0000000..a8c84db
--- /dev/null
+++ b/AUTHORS.rst
@@ -0,0 +1,68 @@
+================
+TTRV Contributors
+================
+* `deepend `_
+
+
+================
+RTV Contributors
+================
+
+Thanks to the following people for their contributions to this project.
+
+* `Michael Lazar `_
+* `Tobin Brown `_
+* `woorst `_
+* `Théo Piboubès `_
+* `Yusuke Sakamoto `_
+* `Johnathan Jenkins `_
+* `tyjak `_
+* `Edridge D'Souza `_
+* `Josue Ortega `_
+* `mekhami `_
+* `Nemanja Nedeljković `_
+* `obosob `_
+* `codesoap `_
+* `Toby Hughes `_
+* `Noah Morrison `_
+* `Mardigon Toler `_
+* `5225225 `_
+* `Shawn Hind `_
+* `Antoine Nguyen `_
+* `JuanPablo `_
+* `Pablo Arias `_
+* `Robert Greener `_
+* `mac1202 `_
+* `Iqbal Singh `_
+* `Lorenz Leitner `_
+* `Markus Pettersson `_
+* `Reshef Elisha `_
+* `Ryan Reno `_
+* `Sam Tebbs `_
+* `Justin Partain `_
+* `afloofloo `_
+* `0xflotus `_
+* `Caleb Perkins `_
+* `Charles Saracco `_
+* `Corey McCandless `_
+* `Crestwave `_
+* `Danilo G. Baio `_
+* `Donovan Glover `_
+* `Fabio Alessandro Locati `_
+* `Gabriel Le Breton `_
+* `Hans Roman `_
+* `micronn `_
+* `Ivan Klishch `_
+* `Joe MacDonald `_
+* `Marc Abramowitz `_
+* `Matt `_
+* `Matthew Smith `_
+* `Michael Kwon `_
+* `Michael Wei `_
+* `Ram-Z `_
+* `Vivek Anand `_
+* `Wieland Hoffmann `_
+* `Adam Talsma `_
+* `geheimnisse `_
+* `Alexander Terry `_
+* `peterpans01 `_
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
new file mode 100644
index 0000000..f71ef6a
--- /dev/null
+++ b/CHANGELOG.rst
@@ -0,0 +1,783 @@
+=============
+TTVR Changelog
+=============
+
+.. _1.27.0: https://github.com/tildeclub/ttrv/releases/tag/v1.27.0
+.. _1.26.0: https://github.com/tildeclub/ttrv/releases/tag/v1.26.0
+.. _1.25.1: https://github.com/tildeclub/ttrv/releases/tag/v1.25.1
+.. _1.25.0: https://github.com/tildeclub/ttrv/releases/tag/v1.25.0
+.. _1.24.0: https://github.com/tildeclub/ttrv/releases/tag/v1.24.0
+.. _1.23.0: https://github.com/tildeclub/ttrv/releases/tag/v1.23.0
+.. _1.22.1: https://github.com/tildeclub/ttrv/releases/tag/v1.22.1
+.. _1.22.0: https://github.com/tildeclub/ttrv/releases/tag/v1.22.0
+.. _1.21.0: https://github.com/tildeclub/ttrv/releases/tag/v1.21.0
+.. _1.20.0: https://github.com/tildeclub/ttrv/releases/tag/v1.20.0
+.. _1.19.0: https://github.com/tildeclub/ttrv/releases/tag/v1.19.0
+.. _1.18.0: https://github.com/tildeclub/ttrv/releases/tag/v1.18.0
+.. _1.17.1: https://github.com/tildeclub/ttrv/releases/tag/v1.17.1
+.. _1.17.0: https://github.com/tildeclub/ttrv/releases/tag/v1.17.0
+.. _1.16.0: https://github.com/tildeclub/ttrv/releases/tag/v1.16.0
+.. _1.15.1: https://github.com/tildeclub/ttrv/releases/tag/v1.15.1
+.. _1.15.0: https://github.com/tildeclub/ttrv/releases/tag/v1.15.0
+.. _1.14.1: https://github.com/tildeclub/ttrv/releases/tag/v1.14.1
+.. _1.13.0: https://github.com/tildeclub/ttrv/releases/tag/v1.13.0
+.. _1.12.1: https://github.com/tildeclub/ttrv/releases/tag/v1.12.1
+.. _1.12.0: https://github.com/tildeclub/ttrv/releases/tag/v1.12.0
+.. _1.11.0: https://github.com/tildeclub/ttrv/releases/tag/v1.11.0
+.. _1.10.0: https://github.com/tildeclub/ttrv/releases/tag/v1.10.0
+.. _1.9.1: https://github.com/tildeclub/ttrv/releases/tag/v1.9.1
+.. _1.9.0: https://github.com/tildeclub/ttrv/releases/tag/v1.9.0
+.. _1.8.1: https://github.com/tildeclub/ttrv/releases/tag/v1.8.1
+.. _1.8.0: https://github.com/tildeclub/ttrv/releases/tag/v1.8.0
+.. _1.7.0: https://github.com/tildeclub/ttrv/releases/tag/v1.7.0
+.. _1.6.1: https://github.com/tildeclub/ttrv/releases/tag/v1.6.1
+.. _1.6: https://github.com/tildeclub/ttrv/releases/tag/v1.6
+.. _1.5: https://github.com/tildeclub/ttrv/releases/tag/v1.5
+.. _1.4.2: https://github.com/tildeclub/ttrv/releases/tag/v1.4.2
+.. _1.4.1: https://github.com/tildeclub/ttrv/releases/tag/v1.4.1
+.. _1.4: https://github.com/tildeclub/ttrv/releases/tag/v1.4
+.. _1.3: https://github.com/tildeclub/ttrv/releases/tag/v1.3
+.. _1.2.2: https://github.com/tildeclub/ttrv/releases/tag/v1.2.2
+.. _1.2.1: https://github.com/tildeclub/ttrv/releases/tag/v1.2.1
+.. _1.2: https://github.com/tildeclub/ttrv/releases/tag/v1.2
+
+--------------------
+1.27.0_ (2019-06-02)
+--------------------
+
+This is the final release of RTV. See here for more information:
+
+https://github.com/michael-lazar/rtv/issues/696
+
+Features
+
+* Added a configuration option to toggle whether to open web browser links in a
+ new tab or a new window.
+
+Documentation
+
+* Improved the mailcap example for the ``feh`` command.
+* Fixed the the descriptions for the ``j`` & ``k`` keys (they were swapped).
+
+--------------------
+1.26.0_ (2019-03-03)
+--------------------
+
+Features
+
+* Added a brand new inbox page for viewing private messages and comment replies.
+ The inbox is accessible with the ``i`` key. Supported actions include viewing
+ message chains and replying to messages, marking messages as read/unread, and
+ opening the context of a comment.
+* Added the ability to compose new private messages with the ``C`` key.
+* Updated the inline help ``?`` document to contain a more comprehensive list
+ of commands.
+* Opening a link from the command line is now faster at startup because the
+ default subreddit will not be loaded beforehand.
+* Added a new ``--debug-info`` command to display useful system information.
+
+Bugfixes
+
+* Fixed opening comments with the prompt ``/`` from the subscription window.
+* The subscription and multireddit ``s``/``S`` keys now work from all pages.
+* Relative time strings are now correctly pluralized.
+* Fixed an unclosed file handler when opening the web browser.
+* Fixed the application not starting if the user has an empty front page.
+
+Configuration Changes
+
+* Renamed the following keybindings to better represent their usage:
+
+ * ``SORT_HOT`` -> ``SORT_1``
+ * ``SORT_TOP`` -> ``SORT_2``
+ * ``SORT_RISING`` -> ``SORT_3``
+ * ``SORT_NEW`` -> ``SORT_4``
+ * ``SORT_CONTROVERSIAL`` -> ``SORT_5``
+ * ``SORT_GILDED`` -> ``SORT_6``
+ * ``SUBREDDIT_OPEN_SUBSCRIPTIONS`` -> ``SUBSCRIPTIONS``
+ * ``SUBREDDIT_OPEN_MULTIREDDITS`` -> ``MULTIREDDITS``
+
+
+* Added new keybindings to support the inbox page:
+
+ * ``SORT_7``
+ * ``PRIVATE_MESSAGE``
+ * ``INBOX_VIEW_CONTEXT``
+ * ``INBOX_OPEN_SUBMISSION``
+ * ``INBOX_REPLY``
+ * ``INBOX_MARK_READ``
+ * ``INBOX_EXIT``
+
+* Added new theme elements to support the inbox page:
+
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+
+--------------------
+1.25.1_ (2019-02-13)
+--------------------
+
+Bugfixes
+
+* Fixed a bug that was causing newlines to be stripped when posting comments
+ and submissions.
+
+
+--------------------
+1.25.0_ (2019-02-03)
+--------------------
+
+Features
+
+* You can now open HTML links that are embedded inside of comments and
+ submissions by pressing the ``ENTER`` key and selecting a link from the list.
+ This also works when copying links to the clipboard using ``Y``.
+* Added the ``--no-autologin`` command line argument to disable automatically
+ logging in at startup.
+* Added the ``max_pager_cols`` configuration option to limit the text width
+ when sending text to the system ``PAGER``.
+* Additional filtering options have been added when viewing user pages.
+* The gilded flair now displays the number of times a submission has been
+ gilded.
+* Submissions/comments now display the time that they were most recently edited.
+
+Bugfixes
+
+* Fixed the MIME parser for gfycat, and gfycat videos are now downloaded as mp4.
+* Fixed formatting when composing posts with leading whitespace.
+* Fixed crash when attempting to display a long terminal title.
+
+Documentation
+
+* RTV has been moved to the Arch Community Repository and installation
+ instructions for Arch have been updated accordingly.
+
+
+--------------------
+1.24.0_ (2018-08-12)
+--------------------
+
+Features
+
+* Python 3.7 is now officially supported.
+* Lines that start with the hash symbol (#) are no longer ignored when
+ composing posts in your editor. This allows # to be used with Reddit's
+ markdown parser to denote headers.
+* Added a new *dark colorblind* theme.
+* Added support for the ``$RTV_PAGER`` environment variable, which can be
+ used to set a unique PAGER for rtv.
+* Added the ability to sort submissions by **guilded**.
+
+Bugfixes
+
+* Fixed a crash when setting the ``$BROWSER`` with python 3.7.
+* Improved the error message when attempting to vote on an archived post.
+* Cleaned up several outdated MIME parsers. Removed the vidme, twitch,
+ oddshot, and imgtc parsers. Fixed the liveleak and reddit video parsers.
+
+
+--------------------
+1.23.0_ (2018-06-24)
+--------------------
+
+Features
+
+* Submissions can now be marked as *[hidden]* using the ``space`` key. Hidden
+ submissions will be removed from the feed when the page is reloaded.
+* New MIME parsers have been added for vimeo.com and streamja.com.
+* Added support for opening links with **qutebrowser**.
+
+Bugfixes
+
+* Fixed unhandled OAuth server log messages being dumped to stdout.
+* Fixed the application crashing when performing rate-limited requests.
+* Fixed crash when displaying posts that contain null byte characters.
+
+Documentation
+
+* Added README badge for *saythanks.io*.
+* Updated the mailcap template to support *v.redd.it* links.
+
+
+--------------------
+1.22.1_ (2018-03-11)
+--------------------
+
+I forgot to check in a commit before publishing the 1.22.0 release (whoops!)
+
+Bugfixes
+
+* Updated the ``__version__.py`` file to report the current version.
+* Added the missing v1.22.0 entry to the CHANGELOG.
+
+--------------------
+1.22.0_ (2018-03-07)
+--------------------
+
+Features
+
+* Added the ``--no-flash`` option to disable terminal flashing.
+
+Bugfixes
+
+* Fixed automatically exiting on launch when trying to open an invalid
+ subreddit with the ``-s`` flag.
+* Fixed error handling for HTTP request timeouts when checking for new
+ messages in the inbox.
+* Fixed a typo in the sample theme config.
+
+Documentation
+
+* Added the FreeBSD package to the README.
+
+--------------------
+1.21.0_ (2017-12-30)
+--------------------
+
+Features
+
+* Full support for customizable themes has been added. For more information,
+ see the new section on themes in the README, and the ``THEMES.md`` file.
+
+Bugfixes
+
+* Fixed incorrect URL strings being sent to the **opera** web browser.
+* Fixed timeout messages for the **surf** and **vimb** web browsers.
+* Switched to using ``XDG_DATA_HOME`` to store the rtv browser history and
+ credentials file.
+
+--------------------
+1.20.0_ (2017-12-05)
+--------------------
+
+Features
+
+* Text piped to the ``$PAGER`` will now wrap on word / sentence breaks.
+* New MIME parsers have been added for liveleak.com and worldstarhiphop.com.
+
+Bugfixes
+
+* Fixed a regression where text from the web browser's stdout/stderr was
+ being sent to the terminal window.
+* Fixed crashing on startup when the terminal doesn't support colors.
+* Fixed broken text formatting when running inside of emacs ``term``.
+
+Codebase
+
+* Dropped support for python 3.3 because it's no longer supported upstream
+ by **pytest**. The application will still install through pip but will no
+ longer be tested.
+* Added a text logo to the README.
+
+--------------------
+1.19.0_ (2017-10-24)
+--------------------
+
+Features
+
+* Greatly improved loading times by using smarter rate limiting and page caching.
+* The logout prompt is now visible as a popup notification.
+* New MIME parsers have been added for gifs.com, giphy.com, imgtc.com,
+ imgflip.com, livememe.com, makeameme.org and flickr.com
+* Improved mailcap examples for parsing video links with mpv.
+
+Bugfixes
+
+* Patched a backwards-incompatible Reddit API change with the comment
+ permalink now being returned in the response JSON.
+* Fixed crashing when a comment contained exotic unicode characters like emojis.
+* Removed the option to select custom sorting ranges for controversial and
+ top comments.
+* Fixed MIME parsing for single image Imgur galleries.
+
+Codebase
+
+* Preliminary refactoring for the upcoming theme support.
+* Created some utility scripts for project maintenance.
+* Created a release checklist document.
+* Updated the README gif images and document layout.
+
+--------------------
+1.18.0_ (2017-09-06)
+--------------------
+
+Features
+
+* The ``rtv -l`` flag has been deprecated and replaced with a positional
+ argument, in order to match the syntax of other command line web browsers.
+* NSFW content is now filtered according to the user's reddit profile
+ settings.
+* ``$RTV_BROWSER`` has been added as a way to set the preferred web browser.
+* Sorting options for **relevance** and **comments** are now displayed on
+ the search results page.
+* An **[S]** badge is now displayed next to the submission author.
+* The gfycat MIME parser has been expanded to support more URLs.
+* New MIME parsers have been added for oddshot.tv, clips.twitch.tv,
+ clippituser.tv, and Reddit's beta hosted videos.
+
+Bugfixes
+
+* Users can now use the prompt to navigate to "/comments/..." pages from
+ inside of a submission.
+* Users can now navigate to multireddits using the "/u/me/" prefix.
+* Fixed the ``$BROWSER`` behavior on macOS to support the **chrome**,
+ **firefox**, **safari**, and **default** keywords.
+
+Codebase
+
+* Travis CI tests have been moved to the trusty environment.
+* Added more detailed logging of the environment and settings at startup.
+
+--------------------
+1.17.1_ (2017-08-06)
+--------------------
+
+Bugfixes
+
+* ``J``/``K`` commands are now restricted to the submission page.
+
+--------------------
+1.17.0_ (2017-08-03)
+--------------------
+
+Features
+
+* Added the ``J`` command to jump to the next sibling comment.
+* Added the ``K`` command to jump to the parent comment.
+* Search results can now be sorted, and the title bar has been updated
+ to display the current search query.
+* Imgur URLs are now resolved via the Imgur API.
+ This enables the loading of large albums with over 10 images.
+ An ``imgur_client_id`` option has been added to the RTV configuration.
+* A MIME parser has been added for www.liveleak.com.
+* RTV now respects the ``$VISUAL`` environment variable.
+
+Bugfixes
+
+* Fixed a screen refresh bug on urxvt terminals.
+* New key bindings will now attempt to fallback to their default key if not
+ defined in the user's configuration file.
+
+Documentation
+
+* Added additional mailcap examples for framebuffer videos and iTerm2.
+* Python version information is now captured in the log at startup.
+
+
+--------------------
+1.16.0_ (2017-06-08)
+--------------------
+
+Features
+
+* Added the ability to copy links to the OS clipboad with ``y`` and ``Y``.
+* Both submissions and comments can now be viewed on **/user/** pages.
+* A MIME parser has been added for www.streamable.com.
+* A MIME parser has been added for www.vidme.com.
+* Submission URLs can now be opened while viewing the comments page.
+
+Bugfixes
+
+* More graceful handling for the invalid LOCALE error on MacOS.
+* A fatal error is now raised when trying to run on Windows without curses.
+* Fixed an error when trying to view saved comments.
+* Invalid refresh-tokens are now automatically deleted.
+* Users who are signed up for Reddit's beta profiles can now launch RTV.
+
+--------------------
+1.15.1_ (2017-04-09)
+--------------------
+Codebase
+
+* Removed the mailcap-fix dependency for python versions >= 3.6.0.
+* Enabled installing test dependencies with ``pip install rtv[test]``.
+
+--------------------
+1.15.0_ (2017-03-30)
+--------------------
+Features
+
+* Added the ability to open comment threads using the submission's
+ permalink. E.g. **/comments/30rwj2**
+
+Bugfixes
+
+* Updated ``requests`` requirement to fix a bug in version 2.3.0.
+* Fixed an edge case where comment trees were unfolding out of order.
+
+Codebase
+
+* Removed dependency on the PyPI ``praw`` package. A version of PRAW 3
+ is now bundled with rtv. This should make installation easier because
+ users are no longer required to maintain a legacy version of praw in
+ their python dependencies.
+* Removed ``update-checker`` dependency.
+
+--------------------
+1.14.1_ (2017-01-12)
+--------------------
+Features
+
+* The order-by option menu now triggers after a single '2' or '5' keystroke
+ instead of needing to double press.
+
+Bugfixes
+
+* Mailcap now handles multi-part shell commands correctly, e.g. "emacs -nw"
+* OS X no longer relies on $DISPLAY to check if there is a display available.
+* Added error handling for terminals that don't support hiding the cursor.
+* Fixed a bug on tmux that prevented scrolling when $TERM was set to
+ "xterm-256color" instead of screen.
+
+Documentation
+
+* Added section to FAQ about garbled characters output by curses.
+
+--------------------
+1.13.0_ (2016-10-17)
+--------------------
+Features
+
+* Pressing `2` or `5` twice now opens a menu to select the time frame.
+* Added the `hide_username` config option.
+* Added the `max_comment_cols` config option.
+
+Bugfixes
+
+* Fixed the terminal title from displaying b'' in py3.
+* Flipped j and k in the documentation.
+* Fixed bug when selecting post order for the front page.
+* Added more descriptive error messages for invalid subreddits.
+
+--------------------
+1.12.1_ (2016-09-27)
+--------------------
+Bugfixes
+
+* Fixed security vulnerability where malicious URLs could inject python code.
+* No longer hangs when using mpv on long videos.
+* Now falls back to ascii mode when the system locale is not utf-8.
+
+--------------------
+1.12.0_ (2016-08-25)
+--------------------
+Features
+
+* Added a help banner with common key bindings.
+* Added `gg` and `G` bindings to jump to the top and bottom the the page.
+* Updated help screen now opens with the system PAGER.
+* The `/` prompt now works from inside of submissions.
+* Added an Instagram parser to extract images and videos from urls.
+
+Bugixes
+
+* Shortened reddit links (https://redd.it/) will now work with ``-s``.
+
+Codebase
+
+* Removed the Tornado dependency from the project.
+* Added a requirements.txt file.
+* Fixed a bunch of tests where cassettes were not being generated.
+* Added compatability for pytest-xdist.
+
+
+--------------------
+1.11.0_ (2016-08-02)
+--------------------
+Features
+
+* Added the ability to open image and video urls with the user's mailcap file.
+* New ``--enable-media`` and ``copy-mailcap`` commands to support mailcap.
+* New command `w` to save submissions and comments.
+* New command `p` to toggle between the front page and the last visited subreddit.
+* New command `S` to view subscribed multireddits.
+* Extended ``/`` prompt to work with users, multireddits, and domains.
+* New page ``/u/saved`` to view saved submissions.
+* You can now specify the sort period by appending **-(period)**,
+ E.g. **/r/python/top-week**.
+
+Bugfixes
+
+* Terminal title is now only set when $DISPLAY is present.
+* Urlview now works on the submission as well as comments.
+* Fixed text encoding when using urlview.
+* Removed `futures` dependency from the python 3 wheel.
+* Unhandled resource warnings on exit are now ignored.
+
+Documentation
+
+* Various README updates.
+* Updated asciinema demo video.
+* Added script to update the AUTHORS.rst file.
+
+--------------------
+1.10.0_ (2016-07-11)
+--------------------
+Features
+
+* New command, `b` extracts urls from comments using urlviewer.
+* Comment files will no longer be destroyed if RTV encounters an error while posting.
+* The terminal title now displays the subreddit name/url.
+
+Bugfixes
+
+* Fixed crash when entering empty or invalid subreddit name.
+* Fixed crash when opening x-posts linked to subreddits.
+* Fixed a bug where the terminal title wasn't getting set.
+* **/r/me** is now displayed as *My Submissions* in the header.
+
+-------------------
+1.9.1_ (2016-06-13)
+-------------------
+Features
+
+* Better support for */r/random*.
+* Added a ``monochrome`` config setting to disable all color.
+* Improved cursor positioning when expanding/hiding comments.
+* Show ``(not enough space)`` when comments are too large.
+
+Bugfixes
+
+* Fixed permissions when copying the config file.
+* Fixed bug where submission indicies were duplicated when paging.
+* Specify praw v3.4.0 to avoid installing praw 4.
+
+Documentation
+
+* Added section to the readme on Arch Linux installation.
+* Updated a few argument descriptions.
+* Added a proper ascii logo.
+
+-------------------
+1.9.0_ (2016-04-05)
+-------------------
+Features
+
+* You can now open long posts/comments with the $PAGER by pressing `l`.
+* Changed a couple of visual separators.
+
+Documentation
+
+* Added testing instructions to the FAQ.
+
+-------------------
+1.8.1_ (2016-03-01)
+-------------------
+Features
+
+* All keys are now rebindable through the config.
+* New bindings - ctrl-d and ctrl-u for page up / page down.
+* Added tag for stickied posts and comments.
+* Added bullet between timestamp and comment count.
+
+Bugfixes
+
+* Links starting with np.reddit.com no longer return `Forbidden`.
+
+Documentation
+
+* Updated README.
+
+-------------------
+1.8.0_ (2015-12-20)
+-------------------
+Features
+
+* A banner on the top of the page now displays the selected page sort order.
+* Hidden scores now show up as "- pts".
+* Oauth settings are now accesible through the config file.
+* New argument `--config` specifies the config file to use.
+* New argument `--copy-config` generates a default config file.
+
+Documentation
+
+* Added a keyboard reference from keyboardlayouteditor.com
+* Added a link to an asciinema demo video
+
+-------------------
+1.7.0_ (2015-12-08)
+-------------------
+
+**Note**
+This version comes with a large change in the internal structure of the project,
+but does not break backwards compatibility. This includes adding a new test
+suite that will hopefully improve the stability of future releases.
+
+Continuous Integration additions
+
+* Travis-CI https://travis-ci.org/michael-lazar/rtv
+* Coveralls https://coveralls.io/github/michael-lazar/rtv
+* Gitter (chat) https://gitter.im/michael-lazar/rtv
+* Added a tox config for local testing
+* Added a pylint config for static code and style analysis
+* The project now uses VCR.py to record HTTP interactions for testing.
+
+Features
+
+* Added a wider utilization of the loading screen for functions that make
+ reddit API calls.
+* In-progress loading screens can now be cancelled by pressing the `Esc` key.
+
+Bugfixes
+
+* OSX users should now be able to login using OAuth.
+* Comments now return the correct nested level when loading "More Comments".
+* Several unicode fixes, the project is now much more consistent in the way
+ that unicode is handled.
+* Several undocumented bug fixes as a result of the code restructure.
+
+
+-------------------
+1.6.1_ (2015-10-19)
+-------------------
+Bugfixes
+
+* Fixed authentication checking for */r/me*.
+* Added force quit option with the `Q` key.
+* Removed option to sort subscriptions.
+* Fixed crash with pressing `i` when not logged in.
+* Removed futures requirement from the python 3 distribution.
+
+Documentation
+
+* Updated screenshot in README.
+* Added section to the FAQ on installation.
+
+-----------------
+1.6_ (2015-10-14)
+-----------------
+Features
+
+* Switched all authentication to OAuth.
+* Can now list the version with `rtv --version`.
+* Added a man page.
+* Added confirmation prompt when quitting.
+* Submissions now display the index in front of their title.
+
+Bugfixes
+
+* Streamlined error logging.
+
+Documentation
+
+* Added missing docs for the `i` key.
+* New documentation for OAuth.
+* New FAQ section.
+
+-----------------
+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)
+-------------------
+Features
+
+* Pressing the `o` key now opens selfposts directly inside of rtv.
+
+Bugfixes
+
+* Fixed invalid subreddits from throwing unexpected errors.
+
+-------------------
+1.4.1_ (2015-07-11)
+-------------------
+Features
+
+* Added the ability to check for unread messages with the `i` key.
+* Upped required PRAW version to 3.
+
+Bugfixes
+
+* Fixed crash caused by downvoting.
+* Missing flairs now display properly.
+* Fixed ResourceWarning on Python 3.2+.
+
+-----------------
+1.4_ (2015-05-16)
+-----------------
+Features
+
+* Unicode support has been vastly improved and is now turned on by default.
+ Ascii only mode can be toggled with the `--ascii` command line flag.
+* Added pageup and pagedown with the `m` and `n` keys.
+* Support for terminal based webbrowsers such as links and w3m.
+* Browsing history is now persistent and stored in `$XDG_CACHE_HOME`.
+
+Bugfixes
+
+* Several improvements for handling unicode.
+* Fixed crash caused by resizing the window and exiting a submission.
+
+-----------------
+1.3_ (2015-04-22)
+-----------------
+Features
+
+* Added edit `e` and delete `d` for comments and submissions.
+* Added *nsfw* tags.
+
+Bugfixes
+
+* Upvote/downvote icon now displays in the submission selfpost.
+* Loading large *MoreComment* blocks no longer hangs the program.
+* Improved logging and error handling with praw interactions.
+
+-------------------
+1.2.2_ (2015-04-07)
+-------------------
+Bugfixes
+
+* Fixed default subreddit not being set.
+
+Documentation
+
+* Added changelog and contributor links to the README.
+
+-------------------
+1.2.1_ (2015-04-06)
+-------------------
+Bugfixes
+
+* Fixed crashing on invalid subreddit names
+
+-----------------
+1.2_ (2015-04-06)
+-----------------
+Features
+
+* Added user login / logout with the `u` key.
+* Added subreddit searching with the `f` key.
+* Added submission posting with the `p` key.
+* Added viewing of user submissions with `/r/me`.
+* Program title now displays in the terminal window.
+* Gold symbols now display on guilded comments and posts.
+* Moved default config location to XDG_CONFIG_HOME.
+
+Bugfixes
+
+* Improved error handling for submission / comment posts.
+* Fixed handling of unicode flairs.
+* Improved displaying of the help message and selfposts on small terminal windows.
+* The author's name now correctly highlights in submissions
+* Corrected user agent formatting.
+* Various minor bugfixes.
+
+------------------
+1.1.1 (2015-03-30)
+------------------
+* Post comments using your text editor.
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
new file mode 100644
index 0000000..e10969b
--- /dev/null
+++ b/CONTRIBUTING.rst
@@ -0,0 +1,108 @@
+----------------------
+Contributor Guidelines
+----------------------
+
+Before you start
+================
+
+- Post an issue on the `tracker `_ describing the bug or feature you would like to add
+- If an issue already exists, leave a comment to let others know that you intend to work on it
+
+Considerations
+==============
+
+- One of the project's goals is to maintain compatibility with as many terminal emulators as possible.
+ Please be mindful of this when designing a new feature
+
+ - Is it compatible with both Linux and OS X?
+ - Is it compatible with both Python 2 and Python 3
+ - Will it work over ssh (without X11)?
+ - What about terminals that don't support color? Or in those with limited (8/256) colors?
+ - Will it work in tmux/screen?
+ - Will is fail gracefully if unicode is not supported?
+
+- If you're adding a new feature, try to include a few test cases.
+ See the section below on setting up your test environment
+- If you tried, but you can't get the tests running in your environment, it's ok
+- If you are unsure about anything, ask!
+
+Submitting a pull request
+=========================
+
+- Reference the issue # that the pull request is related to
+- Make sure you have merged in the latest changes from the ``master`` branch
+- After you submit, make sure that the Travis-CI build passes
+- Be prepared to have your code reviewed.
+ For non-trivial additions, it's normal for this process to take a few iterations
+
+Style guide
+===========
+
+- All code should follow `PEP 8 `_
+- Try to keep lines under 80 characters, but don't sacrifice readability to do it!
+
+ **Ugly**
+
+ .. code-block:: python
+
+ text = ''.join(
+ line for line in fp2 if not line.startswith('#'))
+
+ **Better**
+
+ .. code-block:: python
+
+ text = ''.join(line for line in fp2 if not line.startswith('#'))
+
+- Use the existing codebase as a reference when writing docstrings (adopted from the `Google Style Guide `_)
+- Add an encoding header ``# -*- coding: utf-8 -*-`` to all new files
+- **Please don't submit pull requests for style-only code changes**
+
+Running the tests
+=================
+
+This project uses `pytest `_ and `VCR.py `_
+
+VCR is a tool that records HTTP requests made during the test run and stores them in *tests/cassettes* for subsequent runs.
+This both speeds up the tests and helps to maintain consistency across runs.
+
+1. Install the test dependencies
+
+ .. code-block:: bash
+
+ $ pip install ttrv[test]
+
+2. Set your ``$PYTHONPATH`` to point to the directory of your ttrv repository.
+
+ .. code-block:: bash
+
+ $ export PYTHONPATH=~/code/ttrv/
+
+3. Run the tests using the existing cassettes
+
+ .. code-block:: bash
+
+ $ python -m pytest ~/code/ttrv/tests/
+ ================================ test session starts ================================
+ platform linux -- Python 3.4.0, pytest-2.9.2, py-1.4.31, pluggy-0.3.1
+ rootdir: ~/code/ttrv/, inifile:
+ plugins: xdist-1.14, cov-2.2.0
+ collected 113 items
+
+4. By default, the cassettes will act as read-only.
+ If you have written a new test and would like to record a cassette, you must provide your own refresh token.
+ The easiest thing to do is to use the token generated by ttrv when you log in.
+ This is usually stored as *~/.local/share/ttrv/refresh-token*.
+
+ .. code-block:: bash
+
+ $ python -m pytest ~/code/ttrv/tests/ --record-mode once --refresh-token ~/.local/share/ttrv/refresh-token
+ ================================ test session starts ================================
+ platform linux -- Python 3.4.0, pytest-2.9.2, py-1.4.31, pluggy-0.3.1
+ rootdir: ~/code/ttrv/, inifile:
+ plugins: xdist-1.14, cov-2.2.0
+ collected 113 items
+
+ Note that all sensitive information will automatically be stripped from the cassette when it's saved.
+
+5. Once you have generated a new cassette, go ahead and commit it to your branch along with your test case
diff --git a/CONTROLS.md b/CONTROLS.md
new file mode 100644
index 0000000..fd661e9
--- /dev/null
+++ b/CONTROLS.md
@@ -0,0 +1,103 @@
+# Controls
+
+## Basic Commands
+
+- j or ▼ - Move the cursor down
+- k or ▲ - Move the cursor up
+- l or ► - View the currently selected item
+- h or ◄ - Return to the previous view
+- m or PgUp - Move the cursor up one page
+- n or PgDn - Move the cursor down one page
+- gg - Jump to the top of the page
+- G - Jump to the bottom of the page
+- 1 to 7 - Sort submissions by category
+- r or F5 - Refresh the content on the current page
+- u - Login to your reddit account
+- q - Quit
+- Q - Force quit
+- y - Copy submission permalink to clipboard
+- Y - Copy submission link to clipboard
+- F2 - Cycle to the previous color theme
+- F3 - Cycle to the next color theme
+- ? - Show the help screen
+- / - Open a prompt to select a subreddit
+
+The / key opens a text prompt at the bottom of the screen. You can use
+this to type in the name of the subreddit that you want to open. The following text
+formats are recognized:
+
+- ``/python`` - Open a subreddit, shorthand
+- ``/r/python`` - Open a subreddit
+- ``/r/python/new`` - Open a subreddit, sorted by category
+- ``/r/python/controversial-year`` - Open a subreddit, sorted by category and time
+- ``/r/python+linux+commandline`` - Open multiple subreddits merged together
+- ``/comments/30rwj2`` - Open a submission, shorthand
+- ``/r/python/comments/30rwj2`` - Open a submission
+- ``/r/front`` - Open your front page
+- ``/u/me`` - View your submissions
+- ``/u/me/saved`` - View your saved content
+- ``/u/me/hidden`` - View your hidden content
+- ``/u/me/upvoted`` - View your upvoted content
+- ``/u/me/downvoted`` - View your downvoted content
+- ``/u/spez`` - View a user's submissions and comments
+- ``/u/spez/submitted`` - View a user's submissions
+- ``/u/spez/comments`` - View a user's comments
+- ``/u/multi-mod/m/android`` - Open a user's curated multireddit
+- ``/domain/python.org`` - Search for links for the given domain
+
+## Authenticated Commands
+
+Some actions require that you be logged in to your reddit account. You can login
+by pressing the u key. Once you are logged in, your username will
+appear in the top-right corner of the screen.
+
+- a - Upvote
+- z - Downvote
+- c - Compose a new submission or comment
+- C - Compose a new private message
+- e - Edit the selected submission or comment
+- d - Delete the selected submission or comment
+- i - View your inbox (see [inbox mode](#inbox-mode))
+- s - View your subscribed subreddits (see [subscription mode](#subscription-mode))
+- S - View your subscribed multireddits (see [subscription mode](#subscription-mode))
+- u - Logout of your reddit account
+- w - Save the selected submission or comment
+
+## Subreddit Mode
+
+The following actions can be performed when viewing a subreddit:
+
+- l or ► - View the comments for the selected submission (see [submission mode](#submission-mode))
+- o or ENTER - Open the selected submission link using your web browser or ``.mailcap`` config
+- SPACE - Mark the selected submission as *hidden*
+- p - Toggle between the currently viewed subreddit and ``/r/front``
+- f - Open a prompt to search the current subreddit for a text string
+
+## Submission Mode
+
+The following actions can be performed when viewing a submission:
+
+- h or ◄ - Close the submission and return to the previous page
+- l or ► - View the selected comment using the system's pager
+- o or ENTER - Open a link in the comment using your web browser or ``.mailcap`` config
+- SPACE - Fold or expand the selected comment and its children
+- b - Send the comment text to the system's urlviewer application
+- J - Move the cursor down the the next comment at the same indentation
+- K - Move the cursor up to the parent comment
+
+## Subscription Mode
+
+The following actions can be performed when viewing your subscriptions or multireddits:
+
+- h or ◄ - Close your subscriptions and return to the previous page
+- l or ► - Open the selected subreddit or multireddit
+
+## Inbox Mode
+
+The following actions can be performed when viewing your inbox:
+
+- h or ◄ - Close your inbox and return to the previous page
+- l or ► - View the context of the selected comment
+- o or Enter - Open the submission of the selected comment
+- c - Reply to the selected comment or message
+- w - Mark the selected comment or message as seen
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..c058a6a
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 michael-lazar
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..16bf6a9
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,8 @@
+include version.py
+include CHANGELOG.rst
+include AUTHORS.rst
+include README.md
+include LICENSE
+include ttrv.1
+include ttrv/templates/*
+include ttrv/themes/*
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..5d046b8
--- /dev/null
+++ b/README.md
@@ -0,0 +1,228 @@
+Tilde Terminal Reddit Viewer (TTRV)
+Forked from Original source/development at: RTV
+
+
+A text-based interface (TUI) to view and interact with Reddit from your terminal.
+
+
+
+
+
+
+
+
+
+## Table of Contents
+
+* [Demo](#demo)
+* [Installation](#installation)
+* [Usage](#usage)
+* [Settings](#settings)
+* [Themes](#themes)
+* [FAQ](#faq)
+* [Contributing](#contributing)
+* [License](#license)
+
+## Demo
+
+
+
+
+
+## Installation
+
+### PyPI package
+
+TTRV is available on [PyPI](https://pypi.python.org/pypi/ttrv/) and can be installed with pip:
+
+```bash
+$ pip install ttrv
+```
+
+### From source
+
+```bash
+$ git clone https://github.com/tildeclub/ttrv.git
+$ cd ttrv/
+$ python setup.py install
+```
+
+### Windows
+
+TTRV is not supported on Windows but you can enable Windows subsystem for Linux, download your preferred Linux distribution from Microsoft Store and access it from there.
+
+To open links on Edge, paste the line below to ``{HOME}/.bashrc``
+```
+export BROWSER='/mnt/c/Program Files (x86)/Microsoft/Edge/Application/msedge.exe'
+```
+
+## Usage
+
+To run the program, type:
+
+```bash
+$ ttrv --help
+```
+
+### Controls
+
+Move the cursor using either the arrow keys or *Vim* style movement:
+
+- Press ▲ and ▼ to scroll through submissions
+- Press ▶ to view the selected submission and ◀ to return
+- Press space-bar to expand/collapse comments
+- Press u to login (this requires a web browser for [OAuth](https://github.com/reddit-archive/reddit/wiki/oauth2))
+- Press ? to open the help screen
+
+Press / to open the navigation prompt, where you can type things like:
+
+- ``/front``
+- ``/r/commandprompt+linuxmasterrace``
+- ``/r/programming/controversial``
+- ``/u/me``
+- ``/u/multi-mod/m/art``
+- ``/domain/github.com``
+
+See [CONTROLS](CONTROLS.md) for the full list of commands.
+
+## Settings
+
+### Configuration File
+
+Configuration files are stored in the ``{HOME}/.config/ttrv/`` directory.
+
+Check out [ttrv.cfg](ttrv/templates/ttrv.cfg) for the full list of configurable options. You can clone this file into your home directory by running:
+
+```bash
+$ ttrv --copy-config
+```
+
+### Viewing Media Links
+
+You can use [mailcap](https://en.wikipedia.org/wiki/Media_type#Mailcap) to configure how TTRV will open different types of links.
+
+
+
+
+
+A mailcap file allows you to associate different MIME media types, like ``image/jpeg`` or ``video/mp4``, with shell commands. This feature is disabled by default because it takes a few extra steps to configure. To get started, copy the default mailcap template to your home directory.
+
+```bash
+$ ttrv --copy-mailcap
+```
+
+This template contains examples for common MIME types that work with popular reddit websites like *imgur*, *youtube*, and *gfycat*. Open the mailcap template and follow the [instructions](ttrv/templates/mailcap) listed inside.
+
+Once you've setup your mailcap file, enable it by launching ttrv with the ``ttrv --enable-media`` flag (or set it in your **ttrv.cfg**)
+
+### Environment Variables
+
+The default programs that TTRV interacts with can be configured through environment variables:
+
+
+
+ $TTRV_EDITOR
+ A program used to compose text submissions and comments, e.g. vim , emacs , gedit
+ If not specified, will fallback to $VISUAL and $EDITOR in that order.
+
+
+ $TTRV_BROWSER
+ A program used to open links to external websites, e.g. firefox , google-chrome , w3m , lynx
+ If not specified, will fallback to $BROWSER, or your system's default browser.
+
+
+ $TTRV_URLVIEWER
+ A tool used to extract hyperlinks from blocks of text, e.g. urlview , urlscan
+ If not specified, will fallback to urlview if it is installed.
+
+
+
+### Clipboard
+
+TTRV supports copying submission links to the OS clipboard. On macOS this is supported out of the box.
+On Linux systems you will need to install either [xsel](http://www.vergenet.net/~conrad/software/xsel/) or [xclip](https://sourceforge.net/projects/xclip/).
+
+## Themes
+
+Themes can be used to customize the look and feel of TTRV
+
+
+
+
+ Solarized Dark
+
+
+
+ Solarized Light
+
+
+
+
+
+ Papercolor
+
+
+
+ Molokai
+
+
+
+
+
+You can list all installed themes with the ``--list-themes`` command, and select one with ``--theme``. You can save your choice permanently in your [ttrv.cfg](ttrv/templates/ttrv.cfg) file. You can also use the F2 & F3 keys inside of TTRV to cycle through all available themes.
+
+For instructions on writing and installing your own themes, see [THEMES.md](THEMES.md).
+
+## FAQ
+
+
+ Why am I getting an error during installation/when launching ttrv?
+
+ > If your distro ships with an older version of python 2.7 or python-requests,
+ > you may experience SSL errors or other package incompatibilities. The
+ > easiest way to fix this is to install ttrv using python 3. If you
+ > don't already have pip3, see http://stackoverflow.com/a/6587528 for setup
+ > instructions. Then do
+ >
+ > ```bash
+ > $ sudo pip uninstall ttrv
+ > $ sudo pip3 install -U ttrv
+ > ```
+
+
+
+ Why do I see garbled text like M-b~@M-" or ^@ ?
+
+ > This type of text usually shows up when python is unable to render
+ > unicode properly.
+ >
+ > 1. Try starting TTRV in ascii-only mode with ``ttrv --ascii``
+ > 2. Make sure that the terminal/font that you're using supports unicode
+ > 3. Try [setting the LOCALE to utf-8](https://perlgeek.de/en/article/set-up-a-clean-utf8-environment)
+ > 4. Your python may have been built against the wrong curses library,
+ > see [here](stackoverflow.com/questions/19373027) and
+ > [here](https://bugs.python.org/issue4787) for more information
+
+
+
+ How do I run the code directly from the repository?
+
+ > This project is structured to be run as a python *module*. This means that
+ > you need to launch it using python's ``-m`` flag. See the example below, which
+ > assumes that you have cloned the repository into the directory **~/ttrv_project**.
+ >
+ > ```bash
+ > $ cd ~/ttrv_project
+ > $ python3 -m ttrv
+ > ```
+
+
+
+## Contributing
+All feedback and suggestions are welcome, just post an issue!
+
+Before writing any code, please read the [Contributor Guidelines](CONTRIBUTING.rst).
+
+## License
+This project is distributed under the [MIT](LICENSE) license.
+
diff --git a/THEMES.md b/THEMES.md
new file mode 100644
index 0000000..bb89d15
--- /dev/null
+++ b/THEMES.md
@@ -0,0 +1,215 @@
+# Themes
+
+## Installing Themes
+
+You can install custom themes by copying them into your **~/.config/ttrv/themes/**
+directory. The name of the theme will match the name of the file.
+
+```
+$ cp my-custom-theme.cfg ~/.config/ttrv/themes/
+$ ttrv --theme my-custom-theme
+```
+
+If you've created a cool theme and would like to share it with the community,
+please submit a pull request!
+
+## A quick primer on ANSI colors
+
+Color support on modern terminals can be split into 4 categories:
+
+1. No support for colors
+2. 8 system colors - Black, Red, Green, Yellow, Blue, Magenta,
+ Cyan, and White
+3. 16 system colors - Everything above + bright variations
+4. 256 extended colors - Everything above + 6x6x6 color palette + 24 greyscale colors
+
+
+
+The 256 terminal color codes, image from https://github.com/eikenb/terminal-colors
+
+
+The 16 system colors, along with the default foreground and background,
+can usually be customized through your terminal's profile settings. The
+6x6x6 color palette and grayscale colors are constant RGB values across
+all terminals. TTRV's default theme only uses the 8 primary system colors,
+which is why it matches the "look and feel" of the terminal that you're
+running it in.
+
+
+
+Setting the 16 system colors in iTerm preferences
+
+
+The curses library determines your terminal's color support by reading your
+environment's ``$TERM`` variable, and looking up your terminal's
+capabilities in the [terminfo](https://linux.die.net/man/5/terminfo)
+database. You can emulate this behavior by using the ``tput`` command:
+
+```
+bash$ export TERM=xterm
+bash$ tput colors
+8
+bash$ export TERM=xterm-256color
+bash$ tput colors
+256
+bash$ export TERM=vt220
+bash$ tput colors
+-1
+```
+
+In general you should not be setting your ``$TERM`` variable manually,
+it will be set automatically by you terminal. Often, problems with
+terminal colors can be traced back to somebody hardcoding
+``TERM=xterm-256color`` in their .bashrc file.
+
+## Understanding TTRV Themes
+
+Here's an example of what an TTRV theme file looks like:
+
+```
+[theme]
+; =
+Normal = default default normal
+Selected = default default normal
+SelectedCursor = default default reverse
+
+TitleBar = cyan - bold+reverse
+OrderBar = yellow - bold
+OrderBarHighlight = yellow - bold+reverse
+HelpBar = cyan - bold+reverse
+Prompt = cyan - bold+reverse
+NoticeInfo = - - bold
+NoticeLoading = - - bold
+NoticeError = - - bold
+NoticeSuccess = - - bold
+
+CursorBlock = - - -
+CursorBar1 = magenta - -
+CursorBar2 = cyan - -
+CursorBar3 = green - -
+CursorBar4 = yellow - -
+
+CommentAuthor = blue - bold
+CommentAuthorSelf = green - bold
+CommentCount = - - -
+CommentText = - - -
+Created = - - -
+Downvote = red - bold
+Gold = yellow - bold
+HiddenCommentExpand = - - bold
+HiddenCommentText = - - -
+MultiredditName = yellow - bold
+MultiredditText = - - -
+NeutralVote = - - bold
+NSFW = red - bold+reverse
+Saved = green - -
+Score = - - -
+Separator = - - bold
+Stickied = green - -
+SubscriptionName = yellow - bold
+SubscriptionText = - - -
+SubmissionAuthor = green - bold
+SubmissionFlair = red - -
+SubmissionSubreddit = yellow - -
+SubmissionText = - - -
+SubmissionTitle = - - bold
+Upvote = green - bold
+Link = blue - underline
+LinkSeen = magenta - underline
+UserFlair = yellow - bold
+```
+
+Every piece of text drawn on the screen is assigned to an ````,
+which has three properties:
+
+- ````: The text color
+- ````: The background color
+- ````: Additional text attributes, like bold or underlined
+
+### Colors
+
+The ```` and ```` properties can be set to any the following values:
+
+- ``default``, which means use the terminal's default foreground or background color.
+- The 16 system colors:
+
+
+ black dark_gray
+ red bright_red
+ green bright_green
+ yellow bright_yellow
+ blue bright_blue
+ magenta bright_magenta
+ cyan bright_cyan
+ light_gray white
+
+
+- ``ansi_{n}``, where n is between 0 and 255. These will map to their
+ corresponding ANSI colors (see the figure above).
+- Hex RGB codes, like ``#0F0F0F``, which will be converted to their nearest
+ ANSI color. This is generally not recommended because the conversion process
+ downscales the color resolution and the resulting colors will look "off".
+
+### Attributes
+
+The ```` property can be set to any of the following values:
+
+- ``normal``, ``bold``, ``underline``, or ``standout``.
+- ``reverse`` will swap the foreground and background colors.
+
+Attributes can be mixed together using the + symbol. For example,
+ ``bold+underline`` will make the text bold and underlined.
+
+### Modifiers
+
+TTRV themes use special "modifer" elements to define the default
+application style. This allows you to do things like set the default
+background color without needing to set ```` on every
+single element. The three modifier elements are:
+
+- ``Normal`` - The default modifier that applies to all text elements.
+- ``Selected`` - Applies to text elements that are highlighted on the page.
+- ``SelectedCursor`` - Like ``Selected``, but only applies to ``CursorBlock``
+ and ``CursorBar{n}`` elements.
+
+When an element is marked with a ``-`` token, it means inherit the
+attribute value from the relevant modifier. This is best explained
+through an example:
+
+```
+[theme]
+; =
+Normal = ansi_241 ansi_230 normal
+Selected = ansi_241 ansi_254 normal
+
+Link = ansi_33 - underline
+```
+
+
+
+The default solarized-light theme
+
+
+In the snippet above, the ``Link`` element has its background color set
+to the ``-`` token. This means that it will inherit it's background
+from either the ``Normal`` (light yellow, ansi_230) or the ``Selected`` (light grey, ansi_254)
+element, depending on if it's selected or not.
+
+Compare this with what happens when the ``Link`` background is hard-coded to ``ansi_230``:
+
+```
+[theme]
+; =
+Normal = ansi_241 ansi_230 normal
+Selected = ansi_241 ansi_254 normal
+
+Link = ansi_33 ansi_230 underline
+```
+
+
+
+The Link element hard-coded to ansi_230
+
+
+In this case, the ``Link`` background stays yellow (ansi_230) even when the link is
+selected by the cursor.
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..81c0835
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,10 @@
+beautifulsoup4==4.5.1
+decorator==4.0.10
+kitchen==1.2.4
+mailcap-fix==0.1.3
+requests==2.20.0
+six==1.10.0
+pytest==3.2.3
+vcrpy==1.10.5
+pylint==1.6.5
+pytest-xdist==1.22.5
\ No newline at end of file
diff --git a/resources/demo.gif b/resources/demo.gif
new file mode 100644
index 0000000..2e6d8ce
Binary files /dev/null and b/resources/demo.gif differ
diff --git a/resources/iterm_preferences.png b/resources/iterm_preferences.png
new file mode 100644
index 0000000..1e53585
Binary files /dev/null and b/resources/iterm_preferences.png differ
diff --git a/resources/keyboard.png b/resources/keyboard.png
new file mode 100644
index 0000000..b0932fa
Binary files /dev/null and b/resources/keyboard.png differ
diff --git a/resources/logo.png b/resources/logo.png
new file mode 100644
index 0000000..bbd2989
Binary files /dev/null and b/resources/logo.png differ
diff --git a/resources/logo_black.png b/resources/logo_black.png
new file mode 100644
index 0000000..8cdcd31
Binary files /dev/null and b/resources/logo_black.png differ
diff --git a/resources/mailcap.gif b/resources/mailcap.gif
new file mode 100644
index 0000000..dfce22b
Binary files /dev/null and b/resources/mailcap.gif differ
diff --git a/resources/retro_term.png b/resources/retro_term.png
new file mode 100644
index 0000000..5c1441d
Binary files /dev/null and b/resources/retro_term.png differ
diff --git a/resources/terminal_colors.png b/resources/terminal_colors.png
new file mode 100644
index 0000000..5d2ec8f
Binary files /dev/null and b/resources/terminal_colors.png differ
diff --git a/resources/theme_default.png b/resources/theme_default.png
new file mode 100644
index 0000000..988ffe2
Binary files /dev/null and b/resources/theme_default.png differ
diff --git a/resources/theme_modifiers.png b/resources/theme_modifiers.png
new file mode 100644
index 0000000..1f0d86a
Binary files /dev/null and b/resources/theme_modifiers.png differ
diff --git a/resources/theme_modifiers_2.png b/resources/theme_modifiers_2.png
new file mode 100644
index 0000000..8abd330
Binary files /dev/null and b/resources/theme_modifiers_2.png differ
diff --git a/resources/theme_molokai.png b/resources/theme_molokai.png
new file mode 100644
index 0000000..44f8b7d
Binary files /dev/null and b/resources/theme_molokai.png differ
diff --git a/resources/theme_monochrome.png b/resources/theme_monochrome.png
new file mode 100644
index 0000000..8320fbe
Binary files /dev/null and b/resources/theme_monochrome.png differ
diff --git a/resources/theme_papercolor.png b/resources/theme_papercolor.png
new file mode 100644
index 0000000..2f849cf
Binary files /dev/null and b/resources/theme_papercolor.png differ
diff --git a/resources/theme_solarized_dark.png b/resources/theme_solarized_dark.png
new file mode 100644
index 0000000..fad6123
Binary files /dev/null and b/resources/theme_solarized_dark.png differ
diff --git a/resources/theme_solarized_light.png b/resources/theme_solarized_light.png
new file mode 100644
index 0000000..439914b
Binary files /dev/null and b/resources/theme_solarized_light.png differ
diff --git a/resources/title_image.png b/resources/title_image.png
new file mode 100644
index 0000000..81c7987
Binary files /dev/null and b/resources/title_image.png differ
diff --git a/scripts/build_authors.py b/scripts/build_authors.py
new file mode 100755
index 0000000..333bce9
--- /dev/null
+++ b/scripts/build_authors.py
@@ -0,0 +1,59 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Scrape the project contributors list from Github and update AUTHORS.rst
+"""
+
+from __future__ import unicode_literals
+import os
+import time
+import logging
+
+import requests
+
+_filepath = os.path.dirname(os.path.relpath(__file__))
+
+FILENAME = os.path.abspath(os.path.join(_filepath, '..', 'AUTHORS.rst'))
+URL = "https://api.github.com/repos/tildeclub/ttrv/contributors?per_page=1000"
+HEADER = """\
+================
+TTRV Contributors
+================
+
+Thanks to the following people for their contributions to this project.
+
+"""
+
+
+def main():
+
+ logging.captureWarnings(True)
+
+ # Request the list of contributors
+ print('GET {}'.format(URL))
+ resp = requests.get(URL)
+ contributors = resp.json()
+
+ lines = []
+ for contributor in contributors:
+ time.sleep(1.0)
+
+ # Request each contributor individually to get the full name
+ print('GET {}'.format(contributor['url']))
+ resp = requests.get(contributor['url'])
+ user = resp.json()
+
+ name = user.get('name') or contributor['login']
+ url = user['html_url']
+ lines.append('* `{} <{}>`_'.format(name, url))
+
+ print('Writing to {}'.format(FILENAME))
+ text = HEADER + '\n'.join(lines)
+ text = text.encode('utf-8')
+ with open(FILENAME, 'wb') as fp:
+ fp.write(text)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/scripts/build_manpage.py b/scripts/build_manpage.py
new file mode 100755
index 0000000..0b21bb0
--- /dev/null
+++ b/scripts/build_manpage.py
@@ -0,0 +1,84 @@
+#!/usr/bin/env python
+
+"""
+Internal tool used to automatically generate an up-to-date version of the tvr
+man page. Currently this script should be manually ran after each version bump.
+In the future, it would be nice to have this functionality built into setup.py.
+
+Usage:
+ $ python scripts/build_manpage.py
+"""
+
+import os
+import sys
+from datetime import datetime
+
+_filepath = os.path.dirname(os.path.relpath(__file__))
+ROOT = os.path.abspath(os.path.join(_filepath, '..'))
+sys.path.insert(0, ROOT)
+
+import tvr
+from tvr import config
+
+
+def main():
+
+ parser = config.build_parser()
+ help_text = parser.format_help()
+ help_sections = help_text.split('\n\n')
+ del help_sections[1]
+
+ data = {}
+ print('Fetching version')
+ data['version'] = tvr.__version__
+ print('Fetching release date')
+ data['release_date'] = datetime.utcnow().strftime('%B %d, %Y')
+ print('Fetching synopsis')
+ synopsis = help_sections[0].replace('usage: ', '')
+ synopsis = ' '.join(line.strip() for line in synopsis.split('\n'))
+ data['synopsis'] = synopsis
+ print('Fetching description')
+ data['description'] = help_sections[1]
+ # Build the options section for each argument from the help section
+ # Example Before:
+ # -h, --help show this help message and exit
+ # Example After
+ # .TP
+ # \fB-h\fR, \fB--help\fR
+ # show this help message and exit
+ options = ''
+ lines = help_sections[2].split('\n')[1:] # positional arguments
+ lines.extend(help_sections[3].split('\n')[1:]) # optional arguments
+ lines = [line.strip() for line in lines]
+ arguments = []
+ for line in lines:
+ if line.startswith('-'):
+ arguments.append(line)
+ elif line.startswith('URL'):
+ # Special case for URL which is a positional argument
+ arguments.append(line)
+ else:
+ arguments[-1] = arguments[-1] + ' ' + line
+ for argument in arguments:
+ flag, description = (col.strip() for col in argument.split(' ', 1))
+ flag = ', '.join(r'\fB'+f+r'\fR' for f in flag.split(', '))
+ options += '\n'.join(('.TP', flag, description, '\n'))
+ data['options'] = options
+ print('Fetching license')
+ data['license'] = tvr.__license__
+ print('Fetching copyright')
+ data['copyright'] = tvr.__copyright__
+ # Escape dashes is all of the sections
+ data = {k: v.replace('-', r'\-') for k, v in data.items()}
+ print('Reading from %s/scripts/tvr.1.template' % ROOT)
+ with open(os.path.join(ROOT, 'scripts/tvr.1.template')) as fp:
+ template = fp.read()
+ print('Populating template')
+ out = template.format(**data)
+ print('Writing to %s/tvr.1' % ROOT)
+ with open(os.path.join(ROOT, 'tvr.1'), 'w') as fp:
+ fp.write(out)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/scripts/cassettes/demo_theme.yaml b/scripts/cassettes/demo_theme.yaml
new file mode 100644
index 0000000..79f4478
--- /dev/null
+++ b/scripts/cassettes/demo_theme.yaml
@@ -0,0 +1,6363 @@
+interactions:
+- request:
+ body: grant_type=refresh_token&redirect_uri=http%3A%2F%2F127.0.0.1%3A65000%2F&refresh_token=**********
+ headers:
+ Accept: ['*/*']
+ Accept-Encoding: ['gzip, deflate']
+ Authorization: ['**********']
+ Connection: [keep-alive]
+ Content-Length: ['122']
+ Content-Type: [application/x-www-form-urlencoded]
+ User-Agent: [TTRV Theme Demo PRAW/3.6.1 Python/3.6.1 b'Darwin-14.5.0-x86_64-i386-64bit']
+ method: POST
+ uri: https://api.reddit.com/api/v1/access_token/
+ response:
+ body: {string: '{"access_token": "5TsmI0CKTE9P80JWx5PNB8q2cQw", "token_type":
+ "bearer", "device_id": "None", "expires_in": 3600, "scope": "edit history
+ identity mysubreddits privatemessages read report save submit subscribe vote"}'}
+ headers:
+ Accept-Ranges: [bytes]
+ Connection: [keep-alive]
+ Content-Length: ['214']
+ Content-Type: [application/json; charset=UTF-8]
+ Date: ['Fri, 07 Jul 2017 05:52:23 GMT']
+ Server: [snooserv]
+ Set-Cookie: ['session_tracker=CYIJ6S5YULMlp5p0IA.0.1499406743221.Z0FBQUFBQlpYeUdYNDQwcjA1andsVVJrVjFwNGtKNzZiMHE5ODhpd3BUVjVicjhoRmRudDVBeXZqbm93ZUxuZ1JsTmlIdWp2ZXRERTZubjI3N0k5a2ZQeWpoYVFlaThJS05ibkZ3RlhZTmtVYVF2VXFhcnFyQl83RDk3MXJEZzJzX0xDM1BPRE14ZDg;
+ Domain=reddit.com; Max-Age=7199; Path=/; expires=Fri, 07-Jul-2017 07:52:23
+ GMT; secure', edgebucket=u1fMLgWA7OnvVsZM9S; Domain=reddit.com; Max-Age=63071999;
+ Path=/; secure]
+ Strict-Transport-Security: [max-age=15552000; includeSubDomains; preload]
+ Via: [1.1 varnish]
+ X-Cache: [MISS]
+ X-Cache-Hits: ['0']
+ X-Moose: [majestic]
+ X-Served-By: [cache-iad2131-IAD]
+ X-Timer: ['S1499406743.211475,VS0,VE19']
+ cache-control: ['max-age=0, must-revalidate']
+ set-cookie: ['session_tracker=CYIJ6S5YULMlp5p0IA.0.1499406743221.Z0FBQUFBQlpYeUdYNDQwcjA1andsVVJrVjFwNGtKNzZiMHE5ODhpd3BUVjVicjhoRmRudDVBeXZqbm93ZUxuZ1JsTmlIdWp2ZXRERTZubjI3N0k5a2ZQeWpoYVFlaThJS05ibkZ3RlhZTmtVYVF2VXFhcnFyQl83RDk3MXJEZzJzX0xDM1BPRE14ZDg;
+ Domain=reddit.com; Max-Age=7199; Path=/; expires=Fri, 07-Jul-2017 07:52:23
+ GMT; secure', edgebucket=u1fMLgWA7OnvVsZM9S; Domain=reddit.com; Max-Age=63071999;
+ Path=/; secure]
+ x-content-type-options: [nosniff]
+ x-frame-options: [SAMEORIGIN]
+ x-xss-protection: [1; mode=block]
+ status: {code: 200, message: OK}
+- request:
+ body: null
+ headers:
+ Accept: ['*/*']
+ Accept-Encoding: ['gzip, deflate']
+ Authorization: ['**********']
+ Connection: [keep-alive]
+ User-Agent: [TTRV Theme Demo PRAW/3.6.1 Python/3.6.1 b'Darwin-14.5.0-x86_64-i386-64bit']
+ method: GET
+ uri: https://oauth.reddit.com/api/v1/me.json
+ response:
+ body:
+ string: !!binary |
+ H4sIAJghX1kC/61WXW/jNhD8K4Gf0ySW8+HcW3rNtWmLw+XQokCLgqCklcWa4vIo0o596H/vLiVZ
+ VOI2PeBeAofL1Q5nZ4f8PFOtgMZq3AHM3pxUUrdwejKrQPrgoKWlz7MShUEvvJPFmha8C7ylrXEr
+ ZGOFViZZ1moDopbWglFmRYnbMSbLFtwGnHBg0XmKj7FVzGt9K1ZOGs+VhwigRRu0dEKZCAi3Bhz9
+ mjnQsJGmgBnt20inKJPXCzTeoRYZr8OTBacaMF6okqLZxfzvCLRlBIIhqQKEo7MEO5ZtMFcaxBZy
+ 4aVbwRRuCZUM2ovWtaJGXWLgwl8AbX4M2vwmQpPlHtxaHOc9EOBW9OiftYNjBEkY2XA3+9C2Vh44
+ AUphm4RZ+kSx3okCG4aQBIKlJpRAEVyrqIKxg8I6qJKlXGOx7irnu76z0wQZPFKgCobP2AeQurwC
+ BxxnCUqlk482TPuTddigaLCUejiwKDRhFiVt34lStY1qW5VTnxQm6RpWsthRa6Ur6gh4jBHn0hC1
+ sJmeGUJ/XGGRauwSNnwzcBTFniRV6Eg6tfc2JanwaqM81++ktXXUgDFucQ0NmhUKFgNEXSTKKWpp
+ DBAbpxPhtCEntkrljwnn8oZ1Y7EluSAdGCvWbg5ejmW77ECzzk2h/5SDImkVSThH6lFHcVTdRGEG
+ tkLjakWJRhSyqF8ypUyOT8KGth7XIjlHyvXNKSUpnYpVlSrGYNp/RRw5ar5XJINBq9N+W0n6E3Ss
+ ZMkhabhpJGl8Ghn9JKmHsh+0l0NFsGPRksZn6nWvoySlOYwdGrQR54EEgmxlQ5nCAZiNIoKfq5JJ
+ 72aK1C6J/mPnoG7MvoL53N6yiA46Ey7odPZ7Q2zB8yBOTCHa1cHUyXafJxlqA/l7zspOaD+4AzzR
+ HCQ0RTaB1fzSeqPKockh2Z92QlKbSOIMZKT/9fn6zztjvowDllAuw1ehfBkpL2iHftH7LzfBI3Lr
+ 7NkTl8nqc4NyIIek6CNkmHT1RB+RhjbOxpcBvRfa0No4Dsny6E7MSrwNaGc8vMqpw8zR4RO0LFTD
+ Le3c4c35eX7m69Dk7Vn3lYbsQp7RGJ3f3Gm7uXe48Y/z6wu8fbd7eCh/eLuvfs7W38Bfxe0y+/U6
+ fHgb9g9nlmRC3+9U9r8r2N8WvzwsNnp/vS7WD9+5D5eLd81P+vvfF3dPG1xU+2/f38+LH21bPA4V
+ iHOr5S7es/F6UU+RjVk4L4hZrfaSJ1zYWu5BLDilJor5clZ7vglM0Pq0u8uZp4j4GZ0kCpWTpbBM
+ Ot6HNwfzNzZzDXyD68jwLIIDTrW9xcSloU4T/KRMeo4IX7wCv+O0R59Aej7Y5IM6fvHfvhec5vA5
+ IzvK2XnSyZ60P+bZ8uL0ZLG8/JNiA2Z/JRb608315JxIYGWnuh4R8rtzvkwOb0NOYySO0BUFOhTN
+ rq5PT+gP1xy90e9sxxhVTCuPjetL8zzxukXTTqZgRa9G8j2rXDx3QmtUkv5kJPKXu2lrmZpxtwie
+ b8s+g46mKpX6IV8bRAK/qiiLXINl1O8emRjMlt7cBCbB1lE47qjpiTYU6d5q0xhtFv3q8AkkX6/Z
+ qA5mN7vP8N5/fKzK6q56/xhVpeiRWZHBCYc5RpBDPt+0Yi1dw1dbli0ZVXxdFPRGYZchJXRG5dF2
+ G5n8rj2JiUZ4U2gxK17trUHcSB9fAkN00NUxUTJmuqtlN0Xzy+wqW8yvLy/OGExsaDEC4LV+c9+u
+ mHCRLfsEekb1L7Sh9vDKHM69zPoTHGR1sOl/AH1Jnh66DQAA
+ headers:
+ Accept-Ranges: [bytes]
+ Connection: [keep-alive]
+ Content-Encoding: [gzip]
+ Content-Length: ['1347']
+ Content-Type: [application/json; charset=UTF-8]
+ Date: ['Fri, 07 Jul 2017 05:52:24 GMT']
+ Server: [snooserv]
+ Set-Cookie: ['session_tracker=EsHcNvrMDzIw7rl10k.0.1499406744791.Z0FBQUFBQlpYeUdZbllUSU1OcEdvaHd2V0tGOUlRUzVVNWRqVUtVeUFKSkxIV0t5Yk5sQWFyczJTSk9tWDRBbnJyd2t4alkxZzR5elZOMTBZeWp4UWRnUnctMTdOaE1pSDIyTHhsUGJCS1pvM2hlX0xUN0M0U0FKeGtEM3YwWFNVMFYyOC1yMk55YUs;
+ Domain=reddit.com; Max-Age=7199; Path=/; expires=Fri, 07-Jul-2017 07:52:24
+ GMT; secure', edgebucket=FCaLeYpsJ64Y4b61zH; Domain=reddit.com; Max-Age=63071999;
+ Path=/; secure]
+ Strict-Transport-Security: [max-age=15552000; includeSubDomains; preload]
+ Vary: [accept-encoding]
+ Via: [1.1 varnish]
+ X-Cache: [MISS]
+ X-Cache-Hits: ['0']
+ X-Moose: [majestic]
+ X-Served-By: [cache-iad2130-IAD]
+ X-Timer: ['S1499406745.741230,VS0,VE93']
+ cache-control: ['private, s-maxage=0, max-age=0, must-revalidate, max-age=0,
+ must-revalidate']
+ expires: ['-1']
+ set-cookie: ['session_tracker=EsHcNvrMDzIw7rl10k.0.1499406744791.Z0FBQUFBQlpYeUdZbllUSU1OcEdvaHd2V0tGOUlRUzVVNWRqVUtVeUFKSkxIV0t5Yk5sQWFyczJTSk9tWDRBbnJyd2t4alkxZzR5elZOMTBZeWp4UWRnUnctMTdOaE1pSDIyTHhsUGJCS1pvM2hlX0xUN0M0U0FKeGtEM3YwWFNVMFYyOC1yMk55YUs;
+ Domain=reddit.com; Max-Age=7199; Path=/; expires=Fri, 07-Jul-2017 07:52:24
+ GMT; secure', edgebucket=FCaLeYpsJ64Y4b61zH; Domain=reddit.com; Max-Age=63071999;
+ Path=/; secure]
+ x-content-type-options: [nosniff]
+ x-frame-options: [SAMEORIGIN]
+ x-ratelimit-remaining: ['599.0']
+ x-ratelimit-reset: ['456']
+ x-ratelimit-used: ['1']
+ x-xss-protection: [1; mode=block]
+ status: {code: 200, message: OK}
+- request:
+ body: null
+ headers:
+ Accept: ['*/*']
+ Accept-Encoding: ['gzip, deflate']
+ Authorization: ['**********']
+ Connection: [keep-alive]
+ Cookie: [edgebucket=FCaLeYpsJ64Y4b61zH; session_tracker=EsHcNvrMDzIw7rl10k.0.1499406744791.Z0FBQUFBQlpYeUdZbllUSU1OcEdvaHd2V0tGOUlRUzVVNWRqVUtVeUFKSkxIV0t5Yk5sQWFyczJTSk9tWDRBbnJyd2t4alkxZzR5elZOMTBZeWp4UWRnUnctMTdOaE1pSDIyTHhsUGJCS1pvM2hlX0xUN0M0U0FKeGtEM3YwWFNVMFYyOC1yMk55YUs]
+ User-Agent: [TTRV Theme Demo PRAW/3.6.1 Python/3.6.1 b'Darwin-14.5.0-x86_64-i386-64bit']
+ method: GET
+ uri: https://oauth.reddit.com/user/civilization_phaze_3/saved.json?sort=new&t=all&limit=1024
+ response:
+ body:
+ string: !!binary |
+ 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
+ KxpWx4bQcWt6eP3ly38BcMfWWAA+AAA=
+ headers:
+ Accept-Ranges: [bytes]
+ Connection: [keep-alive]
+ Content-Encoding: [gzip]
+ Content-Length: ['3785']
+ Content-Type: [application/json; charset=UTF-8]
+ Date: ['Fri, 07 Jul 2017 05:52:26 GMT']
+ Server: [snooserv]
+ Strict-Transport-Security: [max-age=15552000; includeSubDomains; preload]
+ Vary: [accept-encoding]
+ Via: [1.1 varnish]
+ X-Cache: [MISS]
+ X-Cache-Hits: ['0']
+ X-Moose: [majestic]
+ X-Served-By: [cache-iad2130-IAD]
+ X-Timer: ['S1499406747.598866,VS0,VE101']
+ cache-control: ['private, s-maxage=0, max-age=0, must-revalidate, max-age=0,
+ must-revalidate']
+ expires: ['-1']
+ set-cookie: ['loid=0000000000000lqnao.2.1425202840186.Z0FBQUFBQlpYeUdhdHFFRkJMVXZTQ1hoYUdjNGI4SHhGSG9NemY2bElIQUZqTjZCRFBjVkJQMnpQdHcxS2dyd1hjWWh2QllvQmg1ZWVrUlhYVG5mMW41S1FaNXJoTFRyc1hpUVpWWHNZU21TWmM5Tnd1QUJyTmcyM0dOaWFIc3VEYlU5X05WNF9wMW0;
+ Domain=reddit.com; Max-Age=63071999; Path=/; expires=Sun, 07-Jul-2019 05:52:26
+ GMT; secure', 'session_tracker=EsHcNvrMDzIw7rl10k.0.1499406746608.Z0FBQUFBQlpYeUdhYmFhMjY0QlJLRm1kLUlRWGl4RWZRVHZIcWZhcWZNSWlZeHhoLVp4eUc0S0MzcXk2QmxEWlJIWU9NbEtHUHJaN2pWQ2RuczlBMnoyOXp6WDJRX3JTY3pLdTU3cG5YdEpCcmx6XzdqVHRxZTJNRG1oY0h0LUdpdUQ4cFVJTXI3UzE;
+ Domain=reddit.com; Max-Age=7199; Path=/; expires=Fri, 07-Jul-2017 07:52:26
+ GMT; secure']
+ x-content-type-options: [nosniff]
+ x-frame-options: [SAMEORIGIN]
+ x-ratelimit-remaining: ['598.0']
+ x-ratelimit-reset: ['454']
+ x-ratelimit-used: ['2']
+ x-reddit-tracking: ['https://pixel.redditmedia.com/pixel/of_destiny.png?v=cb%2FABGh5ca5iuA7xnGcRuEy59xY8w0%2Bkq%2BKrywYDqnXs%2BwyInsDbRdQM5L4nNUXtvkgdj%2Flk1QyLdOuD6TXf3o4u%2BU2zNLtEKHri235%2Fcl0Dyn44KTucNQ%3D%3D']
+ x-ua-compatible: [IE=edge]
+ x-xss-protection: [1; mode=block]
+ status: {code: 200, message: OK}
+- request:
+ body: null
+ headers:
+ Accept: ['*/*']
+ Accept-Encoding: ['gzip, deflate']
+ Authorization: ['**********']
+ Connection: [keep-alive]
+ Cookie: [edgebucket=FCaLeYpsJ64Y4b61zH; session_tracker=EsHcNvrMDzIw7rl10k.0.1499406746608.Z0FBQUFBQlpYeUdhYmFhMjY0QlJLRm1kLUlRWGl4RWZRVHZIcWZhcWZNSWlZeHhoLVp4eUc0S0MzcXk2QmxEWlJIWU9NbEtHUHJaN2pWQ2RuczlBMnoyOXp6WDJRX3JTY3pLdTU3cG5YdEpCcmx6XzdqVHRxZTJNRG1oY0h0LUdpdUQ4cFVJTXI3UzE;
+ loid=0000000000000lqnao.2.1425202840186.Z0FBQUFBQlpYeUdhdHFFRkJMVXZTQ1hoYUdjNGI4SHhGSG9NemY2bElIQUZqTjZCRFBjVkJQMnpQdHcxS2dyd1hjWWh2QllvQmg1ZWVrUlhYVG5mMW41S1FaNXJoTFRyc1hpUVpWWHNZU21TWmM5Tnd1QUJyTmcyM0dOaWFIc3VEYlU5X05WNF9wMW0]
+ User-Agent: [TTRV Theme Demo PRAW/3.6.1 Python/3.6.1 b'Darwin-14.5.0-x86_64-i386-64bit']
+ method: GET
+ uri: https://oauth.reddit.com/subreddits/popular/.json?limit=1024
+ response:
+ body:
+ string: !!binary |
+ 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
+ cwckScydT3MBuw1cAgHRncOqEIJMBlJtwnCBXQe0/ZLR4ITF0ssSKFIZ2Z5pnGaR8x2ZciJkgA+Z
+ ozgzsiPprj3FQSp9DpIFWhc2jfnQczmjKDEfJ9eV+yFUeqEm7evurde+OqqVRySmb1M30CLJwwkh
+ cRfM0dneheU5ToclBoaOJi9mdsiCgtPwIBMyMgzr3LMjBjbT5hGmKl9kwBmJxzCFHs1yyQ/pQmpS
+ jwUxJn8CLlUOP9LBNvGaLitR+h1DKVks+QFZf0lKkJiiRkvKZNtserSuIKOesXjnXo9tOQuV1CYw
+ ASkUXhpu9sMoyGVrLJcc41yP/dBMuXgFi7lV6U+sO/0lohCQW9bqP1jqUotrTszM8tlK8msFLmd7
+ J4kM9iobYtU34WSe1/IrtssnZFCiU4WSKFBV6FsteOHl75Xat5qH0icH698PrCtnrpeizf1olXWa
+ N+paM50GMWHon5qqpXyqatal0h8LHeFK0eQ2PiFrudgJjJ911UYxm0EF+/S3AjVRpSyroB7y1R7/
+ Uwv/pxbWVwt/fLo6KNwv/yW3Kn3y5qyMepd9LesKvxyvk0m5pXmrhuL1yWvkyo9Tsz55y6qOFP1O
+ StUnbwV54SelOQm1VuetvGKED7GownRJ6Tl4KX9ltBkGKP+llBT9kpNQpzWR+H1WZZAvjIRkqqT/
+ mUajSgZUoM5yECO0S31g69czzajxcmdaUBtra5vlBlTgcQZ5SdNp26ch57jIF7Kjrq7Qfw3UMeyo
+ sYHKsKOubDQ2ltfWV9YzdlTT6vQcayrgL65vKvjf1ZqaHtFsuuknKGHCjk/RuTTzM+tN0PCque2/
+ 2vCao1DyTiXgDVa+fWMsyw2vW+7NVsDiOybLaLNrukJxbtbSqNr3X+7e7+y+XtxfP2lGg9vzweq2
+ /Ud0+6q98eX3i/beftAdBDf+putcxVG1E9lfiEEUv5It2hSxJ00I5KGVgrJHNcydaSsqp96oC6I4
+ X0fKDkv0SjwQuC9oIVxouTctIbJcVbRghMzzJUqHtkfzJDggiS4tywvMXP6ewSO/Q0p3xOSDeKOa
+ vEqgiRIY40sqTQc3SsoijkmeRPBp5THCtXMuvyHGhqZIgC0Fed666OK2M77hJgF77RMQB4JC3w2J
+ ddyHLt7sExAs1+YFaQbEWR2Hp5INe7xjACKuvpwqlPsP+UInKPWyxZm3yowUtPWxw1tRZ7AvkUsR
+ +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
+ //43jpyBCWmxvs5bbHGX9P/3v/8/xSCOg8C8GAA=
+ headers:
+ Accept-Ranges: [bytes]
+ Connection: [keep-alive]
+ Content-Encoding: [gzip]
+ Content-Length: ['326867']
+ Content-Type: [application/json; charset=UTF-8]
+ Date: ['Fri, 07 Jul 2017 05:52:30 GMT']
+ Server: [snooserv]
+ Strict-Transport-Security: [max-age=15552000; includeSubDomains; preload]
+ Vary: [accept-encoding]
+ Via: [1.1 varnish]
+ X-Cache: [MISS]
+ X-Cache-Hits: ['0']
+ X-Moose: [majestic]
+ X-Served-By: [cache-iad2130-IAD]
+ X-Timer: ['S1499406749.453473,VS0,VE562']
+ cache-control: ['private, s-maxage=0, max-age=0, must-revalidate, max-age=0,
+ must-revalidate']
+ expires: ['-1']
+ set-cookie: ['session_tracker=EsHcNvrMDzIw7rl10k.0.1499406749460.Z0FBQUFBQlpYeUdkbE4zWlVrOHZIRFh4S243cHUxTnE0cV9nYnFZRDFpUlVtZm8wOHhtTUJfU3g1MXVrMGk5REJsNEZiUUV6Rm5SbHJZeEduSGFJZDlLaDdKamlzb2xzZlJoVWhPVktSZmpoaHJBdktqbkhQNEVQQWN4dDRnLThKMlhiV2VkQnNRd2o;
+ Domain=reddit.com; Max-Age=7199; Path=/; expires=Fri, 07-Jul-2017 07:52:29
+ GMT; secure']
+ x-content-type-options: [nosniff]
+ x-frame-options: [SAMEORIGIN]
+ x-ratelimit-remaining: ['597.0']
+ x-ratelimit-reset: ['451']
+ x-ratelimit-used: ['3']
+ x-reddit-tracking: ['https://pixel.redditmedia.com/pixel/of_destiny.png?v=%2BHTJBcfQwGBW9vuIHyj0EuYdahp%2Bw70Ve2nDjnnNOm%2FD0tkJu2kDJs8mo74%2BG0uM8LmTnB6FjkpimE8WU5SKf%2Bk0mn4XZ%2BuE']
+ x-ua-compatible: [IE=edge]
+ x-xss-protection: [1; mode=block]
+ status: {code: 200, message: OK}
+- request:
+ body: null
+ headers:
+ Accept: ['*/*']
+ Accept-Encoding: ['gzip, deflate']
+ Authorization: ['**********']
+ Connection: [keep-alive]
+ Cookie: [edgebucket=FCaLeYpsJ64Y4b61zH; session_tracker=EsHcNvrMDzIw7rl10k.0.1499406749460.Z0FBQUFBQlpYeUdkbE4zWlVrOHZIRFh4S243cHUxTnE0cV9nYnFZRDFpUlVtZm8wOHhtTUJfU3g1MXVrMGk5REJsNEZiUUV6Rm5SbHJZeEduSGFJZDlLaDdKamlzb2xzZlJoVWhPVktSZmpoaHJBdktqbkhQNEVQQWN4dDRnLThKMlhiV2VkQnNRd2o;
+ loid=0000000000000lqnao.2.1425202840186.Z0FBQUFBQlpYeUdhdHFFRkJMVXZTQ1hoYUdjNGI4SHhGSG9NemY2bElIQUZqTjZCRFBjVkJQMnpQdHcxS2dyd1hjWWh2QllvQmg1ZWVrUlhYVG5mMW41S1FaNXJoTFRyc1hpUVpWWHNZU21TWmM5Tnd1QUJyTmcyM0dOaWFIc3VEYlU5X05WNF9wMW0]
+ User-Agent: [RTV Theme Demo PRAW/3.6.1 Python/3.6.1 b'Darwin-14.5.0-x86_64-i386-64bit']
+ method: GET
+ uri: https://oauth.reddit.com/api/multi/mine/.json
+ response:
+ body:
+ string: !!binary |
+ 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
+ headers:
+ Accept-Ranges: [bytes]
+ Connection: [keep-alive]
+ Content-Encoding: [gzip]
+ Content-Length: ['2325']
+ Content-Type: [application/json; charset=UTF-8]
+ Date: ['Fri, 07 Jul 2017 05:52:35 GMT']
+ Server: [snooserv]
+ Strict-Transport-Security: [max-age=15552000; includeSubDomains; preload]
+ Vary: [accept-encoding]
+ Via: [1.1 varnish]
+ X-Cache: [MISS]
+ X-Cache-Hits: ['0']
+ X-Moose: [majestic]
+ X-Served-By: [cache-iad2130-IAD]
+ X-Timer: ['S1499406755.999195,VS0,VE37']
+ cache-control: ['private, s-maxage=0, max-age=0, must-revalidate, max-age=0,
+ must-revalidate']
+ expires: ['-1']
+ set-cookie: ['session_tracker=EsHcNvrMDzIw7rl10k.0.1499406755003.Z0FBQUFBQlpYeUdqeVFOYlZ4VTFrQ21TYTlYdEtQU2l4b2o4eWlOUlBvc0EyVXN1Z2dIVXdpVG5qaDRGcnRoSUNQQ0N1Qjd5ZW9CUnMzVmdIUFFtWk5qVVNXS1pQT3dCUEx3dnlvMU0wRm9qTEhFVE1YQWMtN19XdUxKaUlJZXU2M3ROMGhsYzNnZEQ;
+ Domain=reddit.com; Max-Age=7199; Path=/; expires=Fri, 07-Jul-2017 07:52:35
+ GMT; secure']
+ x-content-type-options: [nosniff]
+ x-frame-options: [SAMEORIGIN]
+ x-ratelimit-remaining: ['596.0']
+ x-ratelimit-reset: ['445']
+ x-ratelimit-used: ['4']
+ x-reddit-tracking: ['https://pixel.redditmedia.com/pixel/of_destiny.png?v=UV07SwR81k%2Bg4cSqYfqerf0a881zIQIy2tSk0FtBMNBnze851TEZ54uBE6XhCrdYk7h3DwDzLqW1%2F7gbzloW4N9QeGVypPiL']
+ x-ua-compatible: [IE=edge]
+ x-xss-protection: [1; mode=block]
+ status: {code: 200, message: OK}
+- request:
+ body: null
+ headers:
+ Accept: ['*/*']
+ Accept-Encoding: ['gzip, deflate']
+ Authorization: ['**********']
+ Connection: [keep-alive]
+ Cookie: [edgebucket=NYHc412EI2rjmJup8w; session_tracker=Vb9Xwzzo4W6mNCWsGB.0.1499485205400.Z0FBQUFBQlpZRlFWbGNqTWFSN1dvUjdjaHlJbVJnSF9jbG01OENOTXp4VEFyMWVEaUNXS1I5YnZ6dG9JbVdORXVGYU82Yzh1bjVnZmlZUXFuUFlzOXR0VURadzFsS0NqZkZKSk9pRy1NMjdDZFI0V0NiOUNheHhFQVdZMmhxcDM2OThfUDYzQkZnQi0]
+ User-Agent: [RTV Theme Demo PRAW/3.6.1 Python/3.6.1 b'Darwin-14.5.0-x86_64-i386-64bit']
+ method: GET
+ uri: https://oauth.reddit.com/r/Python/comments/4dy7xr.json
+ response:
+ body:
+ string: !!binary |
+ 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
+ headers:
+ Accept-Ranges: [bytes]
+ Connection: [keep-alive]
+ Content-Encoding: [gzip]
+ Content-Length: ['12415']
+ Content-Type: [application/json; charset=UTF-8]
+ Date: ['Sat, 08 Jul 2017 03:40:07 GMT']
+ Server: [snooserv]
+ Strict-Transport-Security: [max-age=15552000; includeSubDomains; preload]
+ Vary: [accept-encoding]
+ Via: [1.1 varnish]
+ X-Cache: [MISS]
+ X-Cache-Hits: ['0']
+ X-Moose: [majestic]
+ X-Served-By: [cache-ord1729-ORD]
+ X-Timer: ['S1499485207.224590,VS0,VE353']
+ cache-control: ['private, s-maxage=0, max-age=0, must-revalidate, max-age=0,
+ must-revalidate']
+ expires: ['-1']
+ set-cookie: ['loid=0000000000000lqnao.2.1425202840186.Z0FBQUFBQlpZRlFYbnVld0xNenhUVzhkUkN5MFE4bXotdTF5ZU03LVp4VWs4Z1MtczRpb01CSVR5S1pzVTVuQU8xNmtVbjRFWjlSbzJ4WHJiNDFlZEkzUmVzTDBKT1NwQU1uaS15WDZMZDBIcEhtTnhHNEJzSHNrbks4M0NBWmNKUTZqNnJNY1BQVHM;
+ Domain=reddit.com; Max-Age=63071999; Path=/; expires=Mon, 08-Jul-2019 03:40:07
+ GMT; secure', 'session_tracker=Vb9Xwzzo4W6mNCWsGB.0.1499485207338.Z0FBQUFBQlpZRlFYaDZWd21uRU56b2ZfaWZKcHlkWTZ4TjM4eEdvSF84ZmlEX2lvVmVIeXlIRHZ0MVlYc1YyV1cxNXdYRW83UVZsSUxsbEtjaXM4TDJ3TWNzdWRwVEVmek0zZThrbkNfN01PUnpndHZDY0czN2ZIU3pSWUVOa1dlX0lpTk9rTTkyOGU;
+ Domain=reddit.com; Max-Age=7199; Path=/; expires=Sat, 08-Jul-2017 05:40:07
+ GMT; secure']
+ x-content-type-options: [nosniff]
+ x-frame-options: [SAMEORIGIN]
+ x-ratelimit-remaining: ['598.0']
+ x-ratelimit-reset: ['593']
+ x-ratelimit-used: ['2']
+ x-reddit-tracking: ['https://pixel.redditmedia.com/pixel/of_destiny.png?v=5aXaZpJwTnOLjmstpewhC5MFDVF0tg4VqadGPTZSF7ah3SIkhjsdsnIUc%2FkpLgeIB0fRFTJ6nj%2FfdAbAOnRLO3hsSCFClldz']
+ x-ua-compatible: [IE=edge]
+ x-xss-protection: [1; mode=block]
+ status: {code: 200, message: OK}
+version: 1
diff --git a/scripts/count_lines.sh b/scripts/count_lines.sh
new file mode 100755
index 0000000..87b2d7e
--- /dev/null
+++ b/scripts/count_lines.sh
@@ -0,0 +1,15 @@
+#!/usr/bin/env bash
+
+ROOT="$(dirname "$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )")"
+
+cd ${ROOT}
+echo -e "\nTests: "
+echo "$(wc -l tests/*.py)"
+echo -e "\nScripts: "
+echo "$(wc -l scripts/*)"
+echo -e "\nTemplates: "
+echo "$(wc -l ttrv/templates/*)"
+echo -e "\nCode: "
+echo "$(wc -l ttrv/*.py)"
+echo -e "\nCombined: "
+echo "$(cat tests/*.py scripts/* ttrv/templates/* ttrv/*.py | wc -l) total lines"
diff --git a/scripts/demo_theme.py b/scripts/demo_theme.py
new file mode 100755
index 0000000..cf16958
--- /dev/null
+++ b/scripts/demo_theme.py
@@ -0,0 +1,283 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+from __future__ import print_function
+
+import os
+import sys
+import time
+import curses
+import locale
+import threading
+from types import MethodType
+from collections import Counter
+
+from vcr import VCR
+from six.moves.urllib.parse import urlparse, parse_qs
+
+from ttrv.theme import Theme, ThemeList
+from ttrv.config import Config
+from ttrv.packages import praw
+from ttrv.oauth import OAuthHelper
+from ttrv.terminal import Terminal
+from ttrv.objects import curses_session
+from ttrv.subreddit_page import SubredditPage
+from ttrv.submission_page import SubmissionPage
+from ttrv.subscription_page import SubscriptionPage
+
+try:
+ from unittest import mock
+except ImportError:
+ import mock
+
+
+def initialize_vcr():
+
+ def auth_matcher(r1, r2):
+ return (r1.headers.get('authorization') ==
+ r2.headers.get('authorization'))
+
+ def uri_with_query_matcher(r1, r2):
+ p1, p2 = urlparse(r1.uri), urlparse(r2.uri)
+ return (p1[:3] == p2[:3] and
+ parse_qs(p1.query, True) == parse_qs(p2.query, True))
+
+ cassette_dir = os.path.join(os.path.dirname(__file__), 'cassettes')
+ if not os.path.exists(cassette_dir):
+ os.makedirs(cassette_dir)
+
+ filename = os.path.join(cassette_dir, 'demo_theme.yaml')
+ if os.path.exists(filename):
+ record_mode = 'none'
+ else:
+ record_mode = 'once'
+ vcr = VCR(
+ record_mode=record_mode,
+ filter_headers=[('Authorization', '**********')],
+ filter_post_data_parameters=[('refresh_token', '**********')],
+ match_on=['method', 'uri_with_query', 'auth', 'body'],
+ cassette_library_dir=cassette_dir)
+ vcr.register_matcher('auth', auth_matcher)
+ vcr.register_matcher('uri_with_query', uri_with_query_matcher)
+
+ return vcr
+
+
+# Patch the getch method so we can display multiple notifications or
+# other elements that require a keyboard input on the screen at the
+# same time without blocking the main thread.
+def notification_getch(self):
+ if self.pause_getch:
+ return -1
+ return 0
+
+
+def prompt_getch(self):
+ while self.pause_getch:
+ time.sleep(1)
+ return 0
+
+
+def draw_screen(stdscr, reddit, config, theme, oauth):
+
+ threads = []
+ max_y, max_x = stdscr.getmaxyx()
+ mid_x = int(max_x / 2)
+ tall_y, short_y = int(max_y / 3 * 2), int(max_y / 3)
+
+ stdscr.clear()
+ stdscr.refresh()
+
+ # ===================================================================
+ # Submission Page
+ # ===================================================================
+ win1 = stdscr.derwin(tall_y - 1, mid_x - 1, 0, 0)
+ term = Terminal(win1, config)
+ term.set_theme(theme)
+ oauth.term = term
+
+ url = 'https://www.reddit.com/r/Python/comments/4dy7xr'
+ with term.loader('Loading'):
+ page = SubmissionPage(reddit, term, config, oauth, url=url)
+
+ # Tweak the data in order to demonstrate the full range of settings
+ data = page.content.get(-1)
+ data['object'].link_flair_text = 'flair'
+ data['object'].gilded = 1
+ data['object'].over_18 = True
+ data['object'].saved = True
+ data.update(page.content.strip_praw_submission(data['object']))
+ data = page.content.get(0)
+ data['object'].author.name = 'kafoozalum'
+ data['object'].stickied = True
+ data['object'].author_flair_text = 'flair'
+ data['object'].likes = True
+ data.update(page.content.strip_praw_comment(data['object']))
+ data = page.content.get(1)
+ data['object'].saved = True
+ data['object'].likes = False
+ data['object'].score_hidden = True
+ data['object'].gilded = 1
+ data.update(page.content.strip_praw_comment(data['object']))
+ data = page.content.get(2)
+ data['object'].author.name = 'kafoozalum'
+ data['object'].body = data['object'].body[:100]
+ data.update(page.content.strip_praw_comment(data['object']))
+ page.content.toggle(9)
+ page.content.toggle(5)
+ page.draw()
+
+ # ===================================================================
+ # Subreddit Page
+ # ===================================================================
+ win2 = stdscr.derwin(tall_y - 1, mid_x - 1, 0, mid_x + 1)
+ term = Terminal(win2, config)
+ term.set_theme(theme)
+ oauth.term = term
+
+ with term.loader('Loading'):
+ page = SubredditPage(reddit, term, config, oauth, '/u/saved')
+
+ # Tweak the data in order to demonstrate the full range of settings
+ data = page.content.get(3)
+ data['object'].hide_score = True
+ data['object'].author = None
+ data['object'].saved = False
+ data.update(page.content.strip_praw_submission(data['object']))
+ page.content.order = 'rising'
+ page.nav.cursor_index = 1
+ page.draw()
+
+ term.pause_getch = True
+ term.getch = MethodType(notification_getch, term)
+ thread = threading.Thread(target=term.show_notification,
+ args=('Success',),
+ kwargs={'style': 'Success'})
+ thread.start()
+ threads.append((thread, term))
+
+ # ===================================================================
+ # Subscription Page
+ # ===================================================================
+ win3 = stdscr.derwin(short_y, mid_x - 1, tall_y, 0)
+ term = Terminal(win3, config)
+ term.set_theme(theme)
+ oauth.term = term
+
+ with term.loader('Loading'):
+ page = SubscriptionPage(reddit, term, config, oauth, 'popular')
+ page.nav.cursor_index = 1
+ page.draw()
+
+ term.pause_getch = True
+ term.getch = MethodType(notification_getch, term)
+ thread = threading.Thread(target=term.show_notification,
+ args=('Error',),
+ kwargs={'style': 'Error'})
+ thread.start()
+ threads.append((thread, term))
+
+ # ===================================================================
+ # Multireddit Page
+ # ===================================================================
+ win4 = stdscr.derwin(short_y, mid_x - 1, tall_y, mid_x + 1)
+ term = Terminal(win4, config)
+ term.set_theme(theme)
+ oauth.term = term
+
+ with term.loader('Loading'):
+ page = SubscriptionPage(reddit, term, config, oauth, 'multireddit')
+ page.nav.cursor_index = 1
+ page.draw()
+
+ term.pause_getch = True
+ term.getch = MethodType(notification_getch, term)
+ thread = threading.Thread(target=term.show_notification,
+ args=('Info',),
+ kwargs={'style': 'Info'})
+ thread.start()
+ threads.append((thread, term))
+
+ term = Terminal(win4, config)
+ term.set_theme(theme)
+ term.pause_getch = True
+ term.getch = MethodType(prompt_getch, term)
+ thread = threading.Thread(target=term.prompt_y_or_n, args=('Prompt: ',))
+ thread.start()
+ threads.append((thread, term))
+
+ time.sleep(0.5)
+ curses.curs_set(0)
+ return threads
+
+
+def main():
+
+ locale.setlocale(locale.LC_ALL, '')
+
+ if len(sys.argv) > 1:
+ theme = Theme.from_name(sys.argv[1])
+ else:
+ theme = Theme()
+
+ vcr = initialize_vcr()
+ with vcr.use_cassette('demo_theme.yaml') as cassette, \
+ curses_session() as stdscr:
+
+ config = Config()
+ if vcr.record_mode == 'once':
+ config.load_refresh_token()
+ else:
+ config.refresh_token = 'mock_refresh_token'
+
+ reddit = praw.Reddit(user_agent='TTRV Theme Demo',
+ decode_html_entities=False,
+ disable_update_check=True)
+ reddit.config.api_request_delay = 0
+
+ config.history.add('https://api.reddit.com/comments/6llvsl/_/djutc3s')
+ config.history.add('http://i.imgur.com/Z9iGKWv.gifv')
+ config.history.add('https://www.reddit.com/r/Python/comments/6302cj/rpython_official_job_board/')
+
+ term = Terminal(stdscr, config)
+ term.set_theme()
+ oauth = OAuthHelper(reddit, term, config)
+ oauth.authorize()
+
+ theme_list = ThemeList()
+
+ while True:
+ term = Terminal(stdscr, config)
+ term.set_theme(theme)
+ threads = draw_screen(stdscr, reddit, config, theme, oauth)
+
+ try:
+ ch = term.show_notification(theme.display_string)
+ except KeyboardInterrupt:
+ ch = Terminal.ESCAPE
+
+ for thread, term in threads:
+ term.pause_getch = False
+ thread.join()
+
+ if vcr.record_mode == 'once':
+ break
+ else:
+ cassette.play_counts = Counter()
+
+ theme_list.reload()
+
+ if ch == curses.KEY_RIGHT:
+ theme = theme_list.next(theme)
+ elif ch == curses.KEY_LEFT:
+ theme = theme_list.previous(theme)
+ elif ch == Terminal.ESCAPE:
+ break
+ else:
+ # Force the theme to reload
+ theme = theme_list.next(theme)
+ theme = theme_list.previous(theme)
+
+
+sys.exit(main())
+
diff --git a/scripts/initialize_session.py b/scripts/initialize_session.py
new file mode 100644
index 0000000..e041767
--- /dev/null
+++ b/scripts/initialize_session.py
@@ -0,0 +1,30 @@
+"""
+Initialize an authenticated instance of PRAW to interact with.
+
+$ python -i initialize_session.py
+"""
+from ttrv.docs import AGENT
+from ttrv.packages import praw
+from ttrv.content import RequestHeaderRateLimiter
+from ttrv.config import Config
+
+config = Config()
+config.load_refresh_token()
+
+reddit = praw.Reddit(
+ user_agent=AGENT.format(version='test_session'),
+ decode_html_entities=False,
+ disable_update_check=True,
+ timeout=10, # 10 second request timeout
+ handler=RequestHeaderRateLimiter())
+
+
+reddit.set_oauth_app_info(
+ config['oauth_client_id'],
+ config['oauth_client_secret'],
+ config['oauth_redirect_uri'])
+reddit.refresh_access_information(config.refresh_token)
+
+inbox = reddit.get_inbox()
+items = [next(inbox) for _ in range(20)]
+pass
diff --git a/scripts/inspect_webbrowser.py b/scripts/inspect_webbrowser.py
new file mode 100755
index 0000000..b1a3e0c
--- /dev/null
+++ b/scripts/inspect_webbrowser.py
@@ -0,0 +1,31 @@
+#!/usr/bin/env python
+
+"""
+Utility script used to examine the python webbrowser module with different OSs.
+"""
+
+import os
+
+os.environ['BROWSER'] = 'firefox'
+
+# If we want to override the $BROWSER variable that the python webbrowser
+# references, it needs to be done before the webbrowser module is imported
+# for the first time.
+TTRV_BROWSER, BROWSER = os.environ.get('TTRV_BROWSER'), os.environ.get('BROWSER')
+if TTRV_BROWSER:
+ os.environ['BROWSER'] = TTRV_BROWSER
+
+print('TTRV_BROWSER=%s' % TTRV_BROWSER)
+print('BROWSER=%s' % BROWSER)
+
+import webbrowser
+
+print('webbrowser._browsers:')
+for key, val in webbrowser._browsers.items():
+ print(' %s: %s' % (key, val))
+
+print('webbrowser._tryorder:')
+for name in webbrowser._tryorder:
+ print(' %s' % name)
+
+webbrowser.open_new_tab('https://www.python.org')
diff --git a/scripts/pip_clean.sh b/scripts/pip_clean.sh
new file mode 100755
index 0000000..4459a57
--- /dev/null
+++ b/scripts/pip_clean.sh
@@ -0,0 +1,9 @@
+#!/usr/bin/env bash
+# Removes any lingering build/release files from the project directory
+
+find . -type f -name '*.pyc' -delete
+find . -type f -name '*.pyo' -delete
+find . -type d -name '__pycache__' -exec rm -rv {} +
+find . -type d -name 'build' -exec rm -rv {} +
+find . -type d -name 'dist' -exec rm -rv {} +
+find . -type d -name '*.egg-info' -exec rm -rv {} +
diff --git a/scripts/ttrv.1.template b/scripts/ttrv.1.template
new file mode 100644
index 0000000..d9f8f25
--- /dev/null
+++ b/scripts/ttrv.1.template
@@ -0,0 +1,47 @@
+.TH "TTRV" "1" "{release_date}" "Version {version}" "Usage and Commands"
+.SH NAME
+TTRV - Reddit Terminal Viewer
+.SH SYNOPSIS
+{synopsis}
+.SH DESCRIPTION
+{description}
+.SH OPTIONS
+{options}
+.SH CONTROLS
+Move the cursor using the arrow keys or vim style movement.
+.br
+Press \fBup\fR and \fBdown\fR to scroll through submissions.
+.br
+Press \fBright\fR to view the selected submission and \fBleft\fR to return.
+.br
+Press \fB?\fR to open the help screen.
+.SH FILES
+.TP
+.BR $XDG_CONFIG_HOME/ttrv/ttrv.cfg
+The configuration file can be used to customize default program settings.
+.TP
+.BR $XDG_DATA_HOME/ttrv/refresh-token
+After you login to reddit, your most recent OAuth refresh token will be stored
+for future sessions.
+.TP
+.BR $XDG_DATA_HOME/ttrv/history.log
+This file stores URLs that have been recently opened in order to
+visually highlight them as "seen".
+.SH ENVIRONMENT
+.TP
+.BR TTRV_EDITOR
+Text editor to use when editing comments and submissions. Will fallback to
+\fI$EDITOR\fR.
+.TP
+.BR TTRV_URLVIEWER
+Url viewer to use to extract links from comments. Requires a compatible
+program to be installed.
+.TP
+.BR TTRV_BROWSER
+Web browser to use when opening links. Will fallback to \fI$BROWSER\fR.
+.SH AUTHOR
+Michael Lazar (2017).
+.SH BUGS
+Report bugs to \fIhttps://github.com/tildeclub/ttrv/issues\fR
+.SH LICENSE
+{license}
diff --git a/scripts/update_packages.py b/scripts/update_packages.py
new file mode 100755
index 0000000..54092c5
--- /dev/null
+++ b/scripts/update_packages.py
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Update the project's bundled dependencies by downloading the git repository and
+copying over the most recent commit.
+"""
+
+import os
+import shutil
+import subprocess
+import tempfile
+
+_filepath = os.path.dirname(os.path.relpath(__file__))
+ROOT = os.path.abspath(os.path.join(_filepath, '..'))
+
+PRAW_REPO = 'https://github.com/michael-lazar/praw3.git'
+
+
+def main():
+
+ tmpdir = tempfile.mkdtemp()
+ subprocess.check_call(['git', 'clone', PRAW_REPO, tmpdir])
+
+ # Update the commit hash reference
+ os.chdir(tmpdir)
+ p = subprocess.Popen(['git', 'rev-parse', 'HEAD'], stdout=subprocess.PIPE)
+ p.wait()
+ commit = p.stdout.read().strip()
+ print('Found commit %s' % commit)
+ regex = 's/^__praw_hash__ =.*$/__praw_hash__ = \'%s\'/g' % commit
+ packages_root = os.path.join(ROOT, 'ttrv', 'packages', '__init__.py')
+ print('Updating commit hash in %s' % packages_root)
+ subprocess.check_call(['sed', '-i', '', regex, packages_root])
+
+ # Overwrite the project files
+ src = os.path.join(tmpdir, 'praw')
+ dest = os.path.join(ROOT, 'ttrv', 'packages', 'praw')
+ print('Copying package files to %s' % dest)
+ shutil.rmtree(dest, ignore_errors=True)
+ shutil.copytree(src, dest)
+
+ # Cleanup
+ print('Removing directory %s' % tmpdir)
+ shutil.rmtree(tmpdir)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..0a8df87
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,2 @@
+[wheel]
+universal = 1
\ No newline at end of file
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..08541d5
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,86 @@
+import sys
+import codecs
+import setuptools
+
+from version import __version__ as version
+
+
+install_requires = [
+ 'beautifulsoup4',
+ 'decorator',
+ 'kitchen',
+ 'requests >=2.4.0', # https://github.com/tildeclub/ttrv/issues/325
+ 'six',
+]
+
+tests_require = [
+ 'coveralls',
+ 'pytest>=3.1.0', # Pinned for the ``pytest.param`` method
+ 'coverage',
+ 'mock',
+ 'pylint',
+ 'vcrpy',
+]
+
+extras_require = {
+ 'test': tests_require
+}
+
+# https://hynek.me/articles/conditional-python-dependencies/
+if int(setuptools.__version__.split(".", 1)[0]) < 18:
+ assert "bdist_wheel" not in sys.argv
+ if sys.version_info[0:2] < (3, 6):
+ install_requires.append("mailcap-fix")
+else:
+ # Building the bdist_wheel with conditional environment dependencies
+ # requires setuptools version > 18. For older setuptools versions this
+ # will raise an error.
+ extras_require.update({":python_version<'3.6'": ["mailcap-fix"]})
+
+
+def long_description():
+ with codecs.open('README.md', encoding='utf8') as f:
+ return f.read()
+
+
+setuptools.setup(
+ name='ttrv',
+ version=version,
+ description='Tilde Terminal Reddit Viewer',
+ long_description=long_description(),
+ long_description_content_type='text/markdown',
+ url='https://github.com/tildeclub/ttrv',
+ author='deepend (forked from RTV)',
+ author_email='deepend@tilde.club',
+ license='MIT',
+ keywords='reddit terminal praw curses',
+ packages=[
+ 'ttrv',
+ 'ttrv.packages',
+ 'ttrv.packages.praw'
+ ],
+ package_data={
+ 'ttrv': ['templates/*', 'themes/*'],
+ 'ttrv.packages.praw': ['praw.ini']
+ },
+ data_files=[("share/man/man1", ["ttrv.1"])],
+ install_requires=install_requires,
+ tests_require=tests_require,
+ extras_require=extras_require,
+ entry_points={'console_scripts': ['ttrv=ttrv.__main__:main']},
+ classifiers=[
+ 'Intended Audience :: End Users/Desktop',
+ 'Environment :: Console :: Curses',
+ 'Operating System :: MacOS :: MacOS X',
+ 'Operating System :: POSIX',
+ 'Natural Language :: English',
+ 'Programming Language :: Python :: 2.7',
+ 'Programming Language :: Python :: 3.4',
+ 'Programming Language :: Python :: 3.5',
+ 'Programming Language :: Python :: 3.6',
+ 'Programming Language :: Python :: 3.7',
+ 'Topic :: Terminals',
+ 'Topic :: Internet :: WWW/HTTP :: Dynamic Content :: Message Boards',
+ 'Topic :: Internet :: WWW/HTTP :: Dynamic Content :: News/Diary',
+ ],
+)
diff --git a/test b/test
new file mode 100644
index 0000000..9daeafb
--- /dev/null
+++ b/test
@@ -0,0 +1 @@
+test
diff --git a/ttrv.1 b/ttrv.1
new file mode 100644
index 0000000..555f3eb
--- /dev/null
+++ b/ttrv.1
@@ -0,0 +1,119 @@
+.TH "TTRV" "1" "June 03, 2019" "Version 1.27.0" "Usage and Commands"
+.SH NAME
+TTRV - Tilde Terminal Reddit Viewer
+.SH SYNOPSIS
+ttrv [URL] [\-s SUBREDDIT]
+.SH DESCRIPTION
+TTRV (Tilde Terminal Reddit Viewer) is a terminal interface to view and interact with reddit.
+.SH OPTIONS
+.TP
+\fBURL\fR
+[optional] Full URL of a submission to open
+
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+show this help message and exit
+
+.TP
+\fB\-s SUBREDDIT\fR
+Name of the subreddit that will be loaded on start
+
+.TP
+\fB\-\-log FILE\fR
+Log HTTP requests to the given file
+
+.TP
+\fB\-\-config FILE\fR
+Load configuration settings from the given file
+
+.TP
+\fB\-\-ascii\fR
+Enable ascii\-only mode
+
+.TP
+\fB\-\-monochrome\fR
+Disable color
+
+.TP
+\fB\-\-theme FILE\fR
+Color theme to use, see \-\-list\-themes for valid options
+
+.TP
+\fB\-\-list\-themes\fR
+List all of the available color themes
+
+.TP
+\fB\-\-non\-persistent\fR
+Forget the authenticated user when the program exits
+
+.TP
+\fB\-\-no\-autologin\fR
+Do not authenticate automatically on startup
+
+.TP
+\fB\-\-clear\-auth\fR
+Remove any saved user data before launching
+
+.TP
+\fB\-\-copy\-config\fR
+Copy the default configuration to {HOME}/.config/ttrv/ttrv.cfg
+
+.TP
+\fB\-\-copy\-mailcap\fR
+Copy an example mailcap configuration to {HOME}/.mailcap
+
+.TP
+\fB\-\-enable\-media\fR
+Open external links using programs defined in the mailcap config
+
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+show program's version number and exit
+
+.TP
+\fB\-\-no\-flash\fR
+Disable screen flashing
+
+.TP
+\fB\-\-debug\-info\fR
+Show system and environment information and exit
+
+
+.SH CONTROLS
+Move the cursor using the arrow keys or vim style movement.
+.br
+Press \fBup\fR and \fBdown\fR to scroll through submissions.
+.br
+Press \fBright\fR to view the selected submission and \fBleft\fR to return.
+.br
+Press \fB?\fR to open the help screen.
+.SH FILES
+.TP
+.BR $XDG_CONFIG_HOME/ttrv/ttrv.cfg
+The configuration file can be used to customize default program settings.
+.TP
+.BR $XDG_DATA_HOME/ttrv/refresh-token
+After you login to reddit, your most recent OAuth refresh token will be stored
+for future sessions.
+.TP
+.BR $XDG_DATA_HOME/ttrv/history.log
+This file stores URLs that have been recently opened in order to
+visually highlight them as "seen".
+.SH ENVIRONMENT
+.TP
+.BR TTRV_EDITOR
+Text editor to use when editing comments and submissions. Will fallback to
+\fI$EDITOR\fR.
+.TP
+.BR TTRV_URLVIEWER
+Url viewer to use to extract links from comments. Requires a compatible
+program to be installed.
+.TP
+.BR TTRV_BROWSER
+Web browser to use when opening links. Will fallback to \fI$BROWSER\fR.
+.SH AUTHOR
+deepend (2017).
+.SH BUGS
+Report bugs to \fIhttps://github.com/tildeclub/ttrv/issues\fR
+.SH LICENSE
+The MIT License (MIT)
diff --git a/ttrv.egg-info/PKG-INFO b/ttrv.egg-info/PKG-INFO
new file mode 100644
index 0000000..3d6c669
--- /dev/null
+++ b/ttrv.egg-info/PKG-INFO
@@ -0,0 +1,258 @@
+Metadata-Version: 2.1
+Name: ttrv
+Version: 1.27.3
+Summary: Tilde Terminal Reddit Viewer
+Home-page: https://github.com/tildeclub/ttrv
+Author: deepend (forked from RTV)
+Author-email: deepend@tilde.club
+License: MIT
+Keywords: reddit terminal praw curses
+Platform: UNKNOWN
+Classifier: Intended Audience :: End Users/Desktop
+Classifier: Environment :: Console :: Curses
+Classifier: Operating System :: MacOS :: MacOS X
+Classifier: Operating System :: POSIX
+Classifier: Natural Language :: English
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3.4
+Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Topic :: Terminals
+Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content :: Message Boards
+Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content :: News/Diary
+Description-Content-Type: text/markdown
+Provides-Extra: test
+License-File: LICENSE
+License-File: AUTHORS.rst
+
+Tilde Terminal Reddit Viewer (TTRV)
+Forked from Original source/development at: RTV
+
+
+A text-based interface (TUI) to view and interact with Reddit from your terminal.
+
+
+
+
+
+
+
+
+
+## Table of Contents
+
+* [Demo](#demo)
+* [Installation](#installation)
+* [Usage](#usage)
+* [Settings](#settings)
+* [Themes](#themes)
+* [FAQ](#faq)
+* [Contributing](#contributing)
+* [License](#license)
+
+## Demo
+
+
+
+
+
+## Installation
+
+### PyPI package
+
+TTRV is available on [PyPI](https://pypi.python.org/pypi/ttrv/) and can be installed with pip:
+
+```bash
+$ pip install ttrv
+```
+
+### From source
+
+```bash
+$ git clone https://github.com/tildeclub/ttrv.git
+$ cd ttrv/
+$ python setup.py install
+```
+
+### Windows
+
+TTRV is not supported on Windows but you can enable Windows subsystem for Linux, download your preferred Linux distribution from Microsoft Store and access it from there.
+
+To open links on Edge, paste the line below to ``{HOME}/.bashrc``
+```
+export BROWSER='/mnt/c/Program Files (x86)/Microsoft/Edge/Application/msedge.exe'
+```
+
+## Usage
+
+To run the program, type:
+
+```bash
+$ ttrv --help
+```
+
+### Controls
+
+Move the cursor using either the arrow keys or *Vim* style movement:
+
+- Press ▲ and ▼ to scroll through submissions
+- Press ▶ to view the selected submission and ◀ to return
+- Press space-bar to expand/collapse comments
+- Press u to login (this requires a web browser for [OAuth](https://github.com/reddit-archive/reddit/wiki/oauth2))
+- Press ? to open the help screen
+
+Press / to open the navigation prompt, where you can type things like:
+
+- ``/front``
+- ``/r/commandprompt+linuxmasterrace``
+- ``/r/programming/controversial``
+- ``/u/me``
+- ``/u/multi-mod/m/art``
+- ``/domain/github.com``
+
+See [CONTROLS](CONTROLS.md) for the full list of commands.
+
+## Settings
+
+### Configuration File
+
+Configuration files are stored in the ``{HOME}/.config/ttrv/`` directory.
+
+Check out [ttrv.cfg](ttrv/templates/ttrv.cfg) for the full list of configurable options. You can clone this file into your home directory by running:
+
+```bash
+$ ttrv --copy-config
+```
+
+### Viewing Media Links
+
+You can use [mailcap](https://en.wikipedia.org/wiki/Media_type#Mailcap) to configure how TTRV will open different types of links.
+
+
+
+
+
+A mailcap file allows you to associate different MIME media types, like ``image/jpeg`` or ``video/mp4``, with shell commands. This feature is disabled by default because it takes a few extra steps to configure. To get started, copy the default mailcap template to your home directory.
+
+```bash
+$ ttrv --copy-mailcap
+```
+
+This template contains examples for common MIME types that work with popular reddit websites like *imgur*, *youtube*, and *gfycat*. Open the mailcap template and follow the [instructions](ttrv/templates/mailcap) listed inside.
+
+Once you've setup your mailcap file, enable it by launching ttrv with the ``ttrv --enable-media`` flag (or set it in your **ttrv.cfg**)
+
+### Environment Variables
+
+The default programs that TTRV interacts with can be configured through environment variables:
+
+
+
+ $TTRV_EDITOR
+ A program used to compose text submissions and comments, e.g. vim , emacs , gedit
+ If not specified, will fallback to $VISUAL and $EDITOR in that order.
+
+
+ $TTRV_BROWSER
+ A program used to open links to external websites, e.g. firefox , google-chrome , w3m , lynx
+ If not specified, will fallback to $BROWSER, or your system's default browser.
+
+
+ $TTRV_URLVIEWER
+ A tool used to extract hyperlinks from blocks of text, e.g. urlview , urlscan
+ If not specified, will fallback to urlview if it is installed.
+
+
+
+### Clipboard
+
+TTRV supports copying submission links to the OS clipboard. On macOS this is supported out of the box.
+On Linux systems you will need to install either [xsel](http://www.vergenet.net/~conrad/software/xsel/) or [xclip](https://sourceforge.net/projects/xclip/).
+
+## Themes
+
+Themes can be used to customize the look and feel of TTRV
+
+
+
+
+ Solarized Dark
+
+
+
+ Solarized Light
+
+
+
+
+
+ Papercolor
+
+
+
+ Molokai
+
+
+
+
+
+You can list all installed themes with the ``--list-themes`` command, and select one with ``--theme``. You can save your choice permanently in your [ttrv.cfg](ttrv/templates/ttrv.cfg) file. You can also use the F2 & F3 keys inside of TTRV to cycle through all available themes.
+
+For instructions on writing and installing your own themes, see [THEMES.md](THEMES.md).
+
+## FAQ
+
+
+ Why am I getting an error during installation/when launching ttrv?
+
+ > If your distro ships with an older version of python 2.7 or python-requests,
+ > you may experience SSL errors or other package incompatibilities. The
+ > easiest way to fix this is to install ttrv using python 3. If you
+ > don't already have pip3, see http://stackoverflow.com/a/6587528 for setup
+ > instructions. Then do
+ >
+ > ```bash
+ > $ sudo pip uninstall ttrv
+ > $ sudo pip3 install -U ttrv
+ > ```
+
+
+
+ Why do I see garbled text like M-b~@M-" or ^@ ?
+
+ > This type of text usually shows up when python is unable to render
+ > unicode properly.
+ >
+ > 1. Try starting TTRV in ascii-only mode with ``ttrv --ascii``
+ > 2. Make sure that the terminal/font that you're using supports unicode
+ > 3. Try [setting the LOCALE to utf-8](https://perlgeek.de/en/article/set-up-a-clean-utf8-environment)
+ > 4. Your python may have been built against the wrong curses library,
+ > see [here](stackoverflow.com/questions/19373027) and
+ > [here](https://bugs.python.org/issue4787) for more information
+
+
+
+ How do I run the code directly from the repository?
+
+ > This project is structured to be run as a python *module*. This means that
+ > you need to launch it using python's ``-m`` flag. See the example below, which
+ > assumes that you have cloned the repository into the directory **~/ttrv_project**.
+ >
+ > ```bash
+ > $ cd ~/ttrv_project
+ > $ python3 -m ttrv
+ > ```
+
+
+
+## Contributing
+All feedback and suggestions are welcome, just post an issue!
+
+Before writing any code, please read the [Contributor Guidelines](CONTRIBUTING.rst).
+
+## License
+This project is distributed under the [MIT](LICENSE) license.
+
+
+
diff --git a/ttrv.egg-info/SOURCES.txt b/ttrv.egg-info/SOURCES.txt
new file mode 100644
index 0000000..787bdf7
--- /dev/null
+++ b/ttrv.egg-info/SOURCES.txt
@@ -0,0 +1,54 @@
+AUTHORS.rst
+CHANGELOG.rst
+LICENSE
+MANIFEST.in
+README.md
+setup.cfg
+setup.py
+ttrv.1
+version.py
+ttrv/__init__.py
+ttrv/__main__.py
+ttrv/__version__.py
+ttrv/clipboard.py
+ttrv/config.py
+ttrv/content.py
+ttrv/docs.py
+ttrv/exceptions.py
+ttrv/inbox_page.py
+ttrv/mime_parsers.py
+ttrv/oauth.py
+ttrv/objects.py
+ttrv/page.py
+ttrv/submission_page.py
+ttrv/subreddit_page.py
+ttrv/subscription_page.py
+ttrv/terminal.py
+ttrv/theme.py
+ttrv.egg-info/PKG-INFO
+ttrv.egg-info/SOURCES.txt
+ttrv.egg-info/dependency_links.txt
+ttrv.egg-info/entry_points.txt
+ttrv.egg-info/requires.txt
+ttrv.egg-info/top_level.txt
+ttrv/packages/__init__.py
+ttrv/packages/praw/__init__.py
+ttrv/packages/praw/decorator_helpers.py
+ttrv/packages/praw/decorators.py
+ttrv/packages/praw/errors.py
+ttrv/packages/praw/handlers.py
+ttrv/packages/praw/helpers.py
+ttrv/packages/praw/internal.py
+ttrv/packages/praw/multiprocess.py
+ttrv/packages/praw/objects.py
+ttrv/packages/praw/praw.ini
+ttrv/packages/praw/settings.py
+ttrv/templates/index.html
+ttrv/templates/mailcap
+ttrv/templates/ttrv.cfg
+ttrv/themes/colorblind-dark.cfg
+ttrv/themes/default.cfg.example
+ttrv/themes/molokai.cfg
+ttrv/themes/papercolor.cfg
+ttrv/themes/solarized-dark.cfg
+ttrv/themes/solarized-light.cfg
\ No newline at end of file
diff --git a/ttrv.egg-info/dependency_links.txt b/ttrv.egg-info/dependency_links.txt
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/ttrv.egg-info/dependency_links.txt
@@ -0,0 +1 @@
+
diff --git a/ttrv.egg-info/entry_points.txt b/ttrv.egg-info/entry_points.txt
new file mode 100644
index 0000000..e69b723
--- /dev/null
+++ b/ttrv.egg-info/entry_points.txt
@@ -0,0 +1,3 @@
+[console_scripts]
+ttrv = ttrv.__main__:main
+
diff --git a/ttrv.egg-info/requires.txt b/ttrv.egg-info/requires.txt
new file mode 100644
index 0000000..f8610ad
--- /dev/null
+++ b/ttrv.egg-info/requires.txt
@@ -0,0 +1,16 @@
+beautifulsoup4
+decorator
+kitchen
+requests>=2.4.0
+six
+
+[:python_version<'3.6']
+mailcap-fix
+
+[test]
+coveralls
+pytest>=3.1.0
+coverage
+mock
+pylint
+vcrpy
diff --git a/ttrv.egg-info/top_level.txt b/ttrv.egg-info/top_level.txt
new file mode 100644
index 0000000..3e75005
--- /dev/null
+++ b/ttrv.egg-info/top_level.txt
@@ -0,0 +1 @@
+ttrv
diff --git a/ttrv/__init__.py b/ttrv/__init__.py
new file mode 100644
index 0000000..06497f7
--- /dev/null
+++ b/ttrv/__init__.py
@@ -0,0 +1,33 @@
+# -*- coding: utf-8 -*-
+r"""
+________ _____ ______
+___ __/__________________ ______(_)____________ ___ /
+__ / _ _ \_ ___/_ __ `__ \_ /__ __ \ __ `/_ /
+_ / / __/ / _ / / / / / / _ / / / /_/ /_ /
+/_/ \___//_/ /_/ /_/ /_//_/ /_/ /_/\__,_/ /_/
+
+
+________ __________________________
+___ __ \__________ /_____ /__(_)_ /_
+__ /_/ / _ \ __ /_ __ /__ /_ __/
+_ _, _// __/ /_/ / / /_/ / _ / / /_
+/_/ |_| \___/\__,_/ \__,_/ /_/ \__/
+
+
+___ ______
+__ | / /__(_)_______ ______________
+__ | / /__ /_ _ \_ | /| / / _ \_ ___/
+__ |/ / _ / / __/_ |/ |/ // __/ /
+_____/ /_/ \___/____/|__/ \___//_/
+
+(TTVR)
+"""
+
+from __future__ import unicode_literals
+
+from .__version__ import __version__
+
+__title__ = 'Tilde Terminal Reddit Viewer'
+__author__ = 'tildeclub'
+__license__ = 'The MIT License (MIT)'
+__copyright__ = '(c) 2019 tilde.club'
diff --git a/ttrv/__main__.py b/ttrv/__main__.py
new file mode 100755
index 0000000..2f5b078
--- /dev/null
+++ b/ttrv/__main__.py
@@ -0,0 +1,280 @@
+# -*- coding: utf-8 -*-
+# pylint: disable=wrong-import-position
+
+from __future__ import unicode_literals
+from __future__ import print_function
+
+import os
+import sys
+import locale
+import logging
+import warnings
+
+import six
+import requests
+
+# Need to check for curses compatibility before performing the trv imports
+try:
+ import curses
+except ImportError:
+ if sys.platform == 'win32':
+ sys.exit('Fatal Error: This program is not compatible with Windows '
+ 'Operating Systems.')
+ else:
+ sys.exit('Fatal Error: Your python distribution appears to be missing '
+ '_curses.so.\nWas it compiled without support for curses?')
+
+# If we want to override the $BROWSER variable that the python webbrowser
+# references, it needs to be done before the webbrowser module is imported
+# for the first time.
+webbrowser_import_warning = ('webbrowser' in sys.modules)
+TTRV_BROWSER, BROWSER = os.environ.get('TTRV_BROWSER'), os.environ.get('BROWSER')
+if TTRV_BROWSER:
+ os.environ['BROWSER'] = TTRV_BROWSER
+
+from . import docs
+from . import packages
+from .packages import praw
+from .config import Config, copy_default_config, copy_default_mailcap
+from .theme import Theme
+from .oauth import OAuthHelper
+from .terminal import Terminal
+from .content import RequestHeaderRateLimiter
+from .objects import curses_session, patch_webbrowser
+from .subreddit_page import SubredditPage
+from .submission_page import SubmissionPage
+from .exceptions import ConfigError, SubredditError, SubmissionError
+from .__version__ import __version__
+
+_logger = logging.getLogger(__name__)
+
+
+# Pycharm debugging note:
+# You can use pycharm to debug a curses application by launching ttrv in a
+# console window (python -m ttrv) and using pycharm to attach to the remote
+# process. On Ubuntu, you may need to allow ptrace permissions by setting
+# ptrace_scope to 0 in /etc/sysctl.d/10-ptrace.conf.
+# http://blog.mellenthin.de/archives/2010/10/18/gdb-attach-fails
+
+
+def main():
+ """Main entry point"""
+
+ # Squelch SSL warnings
+ logging.captureWarnings(True)
+ if six.PY3:
+ # These ones get triggered even when capturing warnings is turned on
+ warnings.simplefilter('ignore', ResourceWarning) # pylint:disable=E0602
+
+ # Set the terminal title
+ if os.getenv('DISPLAY'):
+ title = 'ttrv {0}'.format(__version__)
+ sys.stdout.write('\x1b]2;{0}\x07'.format(title))
+ sys.stdout.flush()
+
+ args = Config.get_args()
+ fargs, bindings = Config.get_file(args.get('config'))
+
+ # Apply the file config first, then overwrite with any command line args
+ config = Config()
+ config.update(**fargs)
+ config.update(**args)
+
+ # If key bindings are supplied in the config file, overwrite the defaults
+ if bindings:
+ config.keymap.set_bindings(bindings)
+
+ if config['copy_config']:
+ return copy_default_config()
+ if config['copy_mailcap']:
+ return copy_default_mailcap()
+ if config['list_themes']:
+ return Theme.print_themes()
+
+ # Load the browsing history from previous sessions
+ config.load_history()
+
+ # Load any previously saved auth session token
+ config.load_refresh_token()
+ if config['clear_auth']:
+ config.delete_refresh_token()
+
+ if config['log']:
+ # Log request headers to the file (print hack only works on python 3.x)
+ # from http import client
+ # _http_logger = logging.getLogger('http.client')
+ # client.HTTPConnection.debuglevel = 2
+ # def print_to_file(*args, **_):
+ # if args[0] != "header:":
+ # _http_logger.info(' '.join(args))
+ # client.print = print_to_file
+ logging.basicConfig(
+ level=logging.DEBUG,
+ filename=config['log'],
+ format='%(asctime)s:%(levelname)s:%(filename)s:%(lineno)d:%(message)s')
+ else:
+ # Add an empty handler so the logger doesn't complain
+ logging.root.addHandler(logging.NullHandler())
+
+ # Make sure the locale is UTF-8 for unicode support
+ default_locale = locale.setlocale(locale.LC_ALL, '')
+ try:
+ encoding = locale.getlocale()[1] or locale.getdefaultlocale()[1]
+ except ValueError:
+ # http://stackoverflow.com/a/19961403
+ # OS X on some terminals will set the LC_CTYPE to "UTF-8"
+ # (as opposed to something like "en_US.UTF-8") and python
+ # doesn't know how to handle it.
+ _logger.warning('Error parsing system locale: `%s`,'
+ ' falling back to utf-8', default_locale)
+ encoding = 'UTF-8'
+
+ if not encoding or encoding.lower() != 'utf-8':
+ text = ('System encoding was detected as (%s) instead of UTF-8'
+ ', falling back to ascii only mode' % encoding)
+ warnings.warn(text)
+ config['ascii'] = True
+
+ if packages.__praw_bundled__:
+ praw_info = 'packaged, commit {}'.format(packages.__praw_hash__[:12])
+ else:
+ praw_info = 'system installed v{}'.format(praw.__version__)
+
+ # Update the webbrowser module's default behavior
+ patch_webbrowser()
+ if webbrowser_import_warning:
+ _logger.warning('webbrowser module was unexpectedly imported before'
+ '$BROWSER could be overwritten')
+
+ # Construct the reddit user agent
+ user_agent = docs.AGENT.format(version=__version__)
+
+ debug_info = [
+ 'ttrv version: ttrv {}'.format(__version__),
+ 'ttrv module path: {}'.format(os.path.abspath(__file__)),
+ 'python version: {}'.format(sys.version.replace('\n', ' ')),
+ 'python executable: {}'.format(sys.executable),
+ 'praw version: {}'.format(praw_info),
+ 'locale, encoding: {}, {}'.format(default_locale, encoding),
+ 'Environment Variables']
+ for name, value in [
+ ('BROWSER', BROWSER),
+ ('DISPLAY', os.getenv('DISPLAY')),
+ ('EDITOR', os.getenv('EDITOR')),
+ ('LANG', os.getenv('LANG')),
+ ('PAGER', os.getenv('PAGER')),
+ ('TTRV_BROWSER', TTRV_BROWSER),
+ ('TTRV_EDITOR', os.getenv('TTRV_EDITOR')),
+ ('TTRV_PAGER', os.getenv('TTRV_PAGER')),
+ ('TTRV_URLVIEWER', os.getenv('TTRV_URLVIEWER')),
+ ('TERM', os.getenv('TERM')),
+ ('VISUAL', os.getenv('VISUAL')),
+ ('XDG_CONFIG_HOME', os.getenv('XDG_CONFIG_HOME')),
+ ('XDG_DATA_HOME', os.getenv('XDG_DATA_HOME')),
+ ]:
+ debug_info.append(' {:<16}: {}'.format(name, value or ''))
+ debug_info.append('')
+ debug_text = '\n'.join(debug_info)
+
+ _logger.info(debug_text)
+ if config['debug_info']:
+ print(debug_text)
+ return
+
+ try:
+ with curses_session() as stdscr:
+
+ term = Terminal(stdscr, config)
+
+ if config['monochrome'] or config['theme'] == 'monochrome':
+ _logger.info('Using monochrome theme')
+ theme = Theme(use_color=False)
+ elif config['theme'] and config['theme'] != 'default':
+ _logger.info('Loading theme: %s', config['theme'])
+ theme = Theme.from_name(config['theme'])
+ else:
+ # Set to None to let the terminal figure out which theme
+ # to use depending on if colors are supported or not
+ theme = None
+ term.set_theme(theme)
+
+ with term.loader('Initializing', catch_exception=False):
+ reddit = praw.Reddit(user_agent=user_agent,
+ decode_html_entities=False,
+ disable_update_check=True,
+ timeout=10, # 10 second request timeout
+ handler=RequestHeaderRateLimiter())
+
+ # Dial the request cache up from 30 seconds to 5 minutes
+ # I'm trying this out to make navigation back and forth
+ # between pages quicker, it may still need to be fine tuned.
+ reddit.config.api_request_delay = 300
+
+ # Authorize on launch if the refresh token is present
+ oauth = OAuthHelper(reddit, term, config)
+ if config['autologin'] and config.refresh_token:
+ oauth.authorize(autologin=True)
+
+ # Open the supplied submission link before opening the subreddit
+ if config['link']:
+ # Expand shortened urls like https://redd.it/
+ # Praw won't accept the shortened versions, add the reddit
+ # headers to avoid a 429 response from reddit.com
+ url = requests.head(
+ config['link'],
+ headers=reddit.http.headers,
+ allow_redirects=True
+ ).url
+
+ page = None
+ with term.loader('Loading submission'):
+ try:
+ page = SubmissionPage(reddit, term, config, oauth, url)
+ except Exception as e:
+ _logger.exception(e)
+ raise SubmissionError('Unable to load {0}'.format(url))
+ while page:
+ page = page.loop()
+
+ page = None
+ name = config['subreddit']
+ with term.loader('Loading subreddit'):
+ try:
+ page = SubredditPage(reddit, term, config, oauth, name)
+ except Exception as e:
+ # If we can't load the subreddit that was requested, try
+ # to load the "popular" page instead so at least the
+ # application still launches. This used to use the user's
+ # front page, but some users have an empty front page.
+ _logger.exception(e)
+ page = SubredditPage(reddit, term, config, oauth, 'popular')
+ raise SubredditError('Unable to load {0}'.format(name))
+
+ # Launch the subreddit page
+ while page:
+ page = page.loop()
+
+ except ConfigError as e:
+ _logger.exception(e)
+ print(e)
+ except Exception as e:
+ _logger.exception(e)
+ import traceback
+ exit_message = '\n'.join([
+ debug_text,
+ traceback.format_exc(),
+ 'ttrv has crashed. Please report this traceback at:',
+ 'https://github.com/tildeclub/ttrv/issues\n'])
+ sys.stderr.write(exit_message)
+ return 1 # General error exception code
+ except KeyboardInterrupt:
+ pass
+ finally:
+ # Try to save the browsing history
+ config.save_history()
+ # Ensure sockets are closed to prevent a ResourceWarning
+ if 'reddit' in locals():
+ reddit.handler.http.close()
+
+
+sys.exit(main())
diff --git a/ttrv/__version__.py b/ttrv/__version__.py
new file mode 100644
index 0000000..a1be6b4
--- /dev/null
+++ b/ttrv/__version__.py
@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+__version__ = '1.27.3'
diff --git a/ttrv/clipboard.py b/ttrv/clipboard.py
new file mode 100644
index 0000000..4194b4c
--- /dev/null
+++ b/ttrv/clipboard.py
@@ -0,0 +1,51 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+import sys
+import subprocess
+
+from .exceptions import ProgramError
+
+
+def _subprocess_copy(text, args_list):
+ p = subprocess.Popen(args_list, stdin=subprocess.PIPE, close_fds=True)
+ p.communicate(input=text.encode('utf-8'))
+
+
+def copy(text):
+ """
+ Copy text to OS clipboard.
+ """
+
+ if sys.platform == 'darwin':
+ copy_osx(text)
+ else:
+ # For Linux, BSD, cygwin, etc.
+ copy_linux(text)
+
+
+def copy_osx(text):
+ _subprocess_copy(text, ['pbcopy', 'w'])
+
+
+def copy_linux(text):
+
+ def get_command_name():
+ # Checks for the installation of xsel or xclip
+ for cmd in ['xsel', 'xclip']:
+ cmd_exists = subprocess.call(
+ ['which', cmd],
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE) == 0
+ if cmd_exists:
+ return cmd
+ return None
+
+ cmd_args = {
+ 'xsel': ['xsel', '-b', '-i'],
+ 'xclip': ['xclip', '-selection', 'c']}
+ cmd_name = get_command_name()
+
+ if cmd_name is None:
+ raise ProgramError("External copy application not found")
+
+ _subprocess_copy(text, cmd_args.get(cmd_name))
diff --git a/ttrv/config.py b/ttrv/config.py
new file mode 100644
index 0000000..b0e194e
--- /dev/null
+++ b/ttrv/config.py
@@ -0,0 +1,303 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+import os
+import codecs
+import shutil
+import argparse
+from functools import partial
+
+import six
+from six.moves import configparser
+
+from . import docs, __version__
+from .objects import KeyMap
+
+PACKAGE = os.path.dirname(__file__)
+HOME = os.path.expanduser('~')
+TEMPLATES = os.path.join(PACKAGE, 'templates')
+DEFAULT_CONFIG = os.path.join(TEMPLATES, 'ttrv.cfg')
+DEFAULT_MAILCAP = os.path.join(TEMPLATES, 'mailcap')
+DEFAULT_THEMES = os.path.join(PACKAGE, 'themes')
+XDG_CONFIG_HOME = os.getenv('XDG_CONFIG_HOME', os.path.join(HOME, '.config'))
+XDG_DATA_HOME = os.getenv('XDG_DATA_HOME', os.path.join(HOME, '.local', 'share'))
+CONFIG = os.path.join(XDG_CONFIG_HOME, 'ttrv', 'ttrv.cfg')
+MAILCAP = os.path.join(HOME, '.mailcap')
+TOKEN = os.path.join(XDG_DATA_HOME, 'ttrv', 'refresh-token')
+HISTORY = os.path.join(XDG_DATA_HOME, 'ttrv', 'history.log')
+THEMES = os.path.join(XDG_CONFIG_HOME, 'ttrv', 'themes')
+
+
+def build_parser():
+ parser = argparse.ArgumentParser(
+ prog='ttrv', description=docs.SUMMARY,
+ epilog=docs.CONTROLS,
+ usage=docs.USAGE,
+ formatter_class=argparse.RawDescriptionHelpFormatter)
+ parser.add_argument(
+ 'link', metavar='URL', nargs='?',
+ help='[optional] Full URL of a submission to open')
+ parser.add_argument(
+ '-s', dest='subreddit',
+ help='Name of the subreddit that will be loaded on start')
+ parser.add_argument(
+ '-l', dest='link_deprecated',
+ help=argparse.SUPPRESS) # Deprecated, use the positional arg instead
+ parser.add_argument(
+ '--log', metavar='FILE', action='store',
+ help='Log HTTP requests to the given file')
+ parser.add_argument(
+ '--config', metavar='FILE', action='store',
+ help='Load configuration settings from the given file')
+ parser.add_argument(
+ '--ascii', action='store_const', const=True,
+ help='Enable ascii-only mode')
+ parser.add_argument(
+ '--monochrome', action='store_const', const=True,
+ help='Disable color')
+ parser.add_argument(
+ '--theme', metavar='FILE', action='store',
+ help='Color theme to use, see --list-themes for valid options')
+ parser.add_argument(
+ '--list-themes', metavar='FILE', action='store_const', const=True,
+ help='List all of the available color themes')
+ parser.add_argument(
+ '--non-persistent', dest='persistent', action='store_const', const=False,
+ help='Forget the authenticated user when the program exits')
+ parser.add_argument(
+ '--no-autologin', dest='autologin', action='store_const', const=False,
+ help='Do not authenticate automatically on startup')
+ parser.add_argument(
+ '--clear-auth', dest='clear_auth', action='store_const', const=True,
+ help='Remove any saved user data before launching')
+ parser.add_argument(
+ '--copy-config', dest='copy_config', action='store_const', const=True,
+ help='Copy the default configuration to {HOME}/.config/ttrv/ttrv.cfg')
+ parser.add_argument(
+ '--copy-mailcap', dest='copy_mailcap', action='store_const', const=True,
+ help='Copy an example mailcap configuration to {HOME}/.mailcap')
+ parser.add_argument(
+ '--enable-media', dest='enable_media', action='store_const', const=True,
+ help='Open external links using programs defined in the mailcap config')
+ parser.add_argument(
+ '-V', '--version', action='version', version='ttrv ' + __version__)
+ parser.add_argument(
+ '--no-flash', dest='flash', action='store_const', const=False,
+ help='Disable screen flashing')
+ parser.add_argument(
+ '--debug-info', dest='debug_info', action='store_const', const=True,
+ help='Show system and environment information and exit')
+ return parser
+
+
+def copy_default_mailcap(filename=MAILCAP):
+ """
+ Copy the example mailcap configuration to the specified file.
+ """
+ return _copy_settings_file(DEFAULT_MAILCAP, filename, 'mailcap')
+
+
+def copy_default_config(filename=CONFIG):
+ """
+ Copy the default ttrv user configuration to the specified file.
+ """
+ return _copy_settings_file(DEFAULT_CONFIG, filename, 'config')
+
+
+def _copy_settings_file(source, destination, name):
+ """
+ Copy a file from the repo to the user's home directory.
+ """
+
+ if os.path.exists(destination):
+ try:
+ ch = six.moves.input(
+ 'File %s already exists, overwrite? y/[n]):' % destination)
+ if ch not in ('Y', 'y'):
+ return
+ except KeyboardInterrupt:
+ return
+
+ filepath = os.path.dirname(destination)
+ if not os.path.exists(filepath):
+ os.makedirs(filepath)
+
+ print('Copying default %s to %s' % (name, destination))
+ shutil.copy(source, destination)
+ os.chmod(destination, 0o664)
+
+
+class OrderedSet(object):
+ """
+ A simple implementation of an ordered set. A set is used to check
+ for membership, and a list is used to maintain ordering.
+ """
+
+ def __init__(self, elements=None):
+ elements = elements or []
+ self._set = set(elements)
+ self._list = elements
+
+ def __contains__(self, item):
+ return item in self._set
+
+ def __len__(self):
+ return len(self._list)
+
+ def __getitem__(self, item):
+ return self._list[item]
+
+ def add(self, item):
+ self._set.add(item)
+ self._list.append(item)
+
+
+class Config(object):
+ """
+ This class manages the loading and saving of configs and other files.
+ """
+
+ def __init__(self, history_file=HISTORY, token_file=TOKEN, **kwargs):
+
+ self.history_file = history_file
+ self.token_file = token_file
+ self.config = kwargs
+
+ default, bindings = self.get_file(DEFAULT_CONFIG)
+ self.default = default
+ self.keymap = KeyMap(bindings)
+
+ # `refresh_token` and `history` are saved/loaded at separate locations,
+ # so they are treated differently from the rest of the config options.
+ self.refresh_token = None
+ self.history = OrderedSet()
+
+ def __getitem__(self, item):
+ if item in self.config:
+ return self.config[item]
+ else:
+ return self.default.get(item, None)
+
+ def __setitem__(self, key, value):
+ self.config[key] = value
+
+ def __delitem__(self, key):
+ self.config.pop(key, None)
+
+ def update(self, **kwargs):
+ self.config.update(kwargs)
+
+ def load_refresh_token(self):
+ if os.path.exists(self.token_file):
+ with open(self.token_file) as fp:
+ self.refresh_token = fp.read().strip()
+ else:
+ self.refresh_token = None
+
+ def save_refresh_token(self):
+ self._ensure_filepath(self.token_file)
+ with open(self.token_file, 'w+') as fp:
+ fp.write(self.refresh_token)
+
+ def delete_refresh_token(self):
+ if os.path.exists(self.token_file):
+ os.remove(self.token_file)
+ self.refresh_token = None
+
+ def load_history(self):
+ if os.path.exists(self.history_file):
+ with codecs.open(self.history_file, encoding='utf-8') as fp:
+ self.history = OrderedSet([line.strip() for line in fp])
+ else:
+ self.history = OrderedSet()
+
+ def save_history(self):
+ self._ensure_filepath(self.history_file)
+ with codecs.open(self.history_file, 'w+', encoding='utf-8') as fp:
+ fp.writelines('\n'.join(self.history[-self['history_size']:]))
+
+ def delete_history(self):
+ if os.path.exists(self.history_file):
+ os.remove(self.history_file)
+ self.history = OrderedSet()
+
+ @staticmethod
+ def get_args():
+ """
+ Load settings from the command line.
+ """
+
+ parser = build_parser()
+ args = vars(parser.parse_args())
+
+ # Overwrite the deprecated "-l" option into the link variable
+ if args['link_deprecated'] and args['link'] is None:
+ args['link'] = args['link_deprecated']
+ args.pop('link_deprecated', None)
+
+ # Filter out argument values that weren't supplied
+ return {key: val for key, val in args.items() if val is not None}
+
+ @classmethod
+ def get_file(cls, filename=None):
+ """
+ Load settings from an ttrv configuration file.
+ """
+
+ if filename is None:
+ filename = CONFIG
+
+ config = configparser.ConfigParser()
+ if os.path.exists(filename):
+ with codecs.open(filename, encoding='utf-8') as fp:
+ config.readfp(fp)
+
+ return cls._parse_ttrv_file(config)
+
+ @staticmethod
+ def _parse_ttrv_file(config):
+
+ ttrv = {}
+ if config.has_section('ttrv'):
+ ttrv = dict(config.items('ttrv'))
+
+ # convert non-string params to their typed representation
+ params = {
+ 'ascii': partial(config.getboolean, 'ttrv'),
+ 'monochrome': partial(config.getboolean, 'ttrv'),
+ 'persistent': partial(config.getboolean, 'ttrv'),
+ 'autologin': partial(config.getboolean, 'ttrv'),
+ 'clear_auth': partial(config.getboolean, 'ttrv'),
+ 'enable_media': partial(config.getboolean, 'ttrv'),
+ 'history_size': partial(config.getint, 'ttrv'),
+ 'oauth_redirect_port': partial(config.getint, 'ttrv'),
+ 'oauth_scope': lambda x: ttrv[x].split(','),
+ 'max_comment_cols': partial(config.getint, 'ttrv'),
+ 'max_pager_cols': partial(config.getint, 'ttrv'),
+ 'hide_username': partial(config.getboolean, 'ttrv'),
+ 'flash': partial(config.getboolean, 'ttrv'),
+ 'force_new_browser_window': partial(config.getboolean, 'ttrv')
+ }
+
+ for key, func in params.items():
+ if key in ttrv:
+ ttrv[key] = func(key)
+
+ bindings = {}
+ if config.has_section('bindings'):
+ bindings = dict(config.items('bindings'))
+
+ for name, keys in bindings.items():
+ bindings[name] = [key.strip() for key in keys.split(',')]
+
+ return ttrv, bindings
+
+ @staticmethod
+ def _ensure_filepath(filename):
+ """
+ Ensure that the directory exists before trying to write to the file.
+ """
+
+ filepath = os.path.dirname(filename)
+ if not os.path.exists(filepath):
+ os.makedirs(filepath)
diff --git a/ttrv/content.py b/ttrv/content.py
new file mode 100644
index 0000000..ede0f1c
--- /dev/null
+++ b/ttrv/content.py
@@ -0,0 +1,1189 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+import re
+import time
+import logging
+from datetime import datetime
+from timeit import default_timer as timer
+
+import six
+from bs4 import BeautifulSoup
+from kitchen.text.display import wrap
+
+from . import exceptions
+from .packages import praw
+from .packages.praw.errors import InvalidSubreddit
+from .packages.praw.helpers import normalize_url
+from .packages.praw.handlers import DefaultHandler
+
+_logger = logging.getLogger(__name__)
+
+
+class Content(object):
+
+ def get(self, index, n_cols):
+ """
+ Grab the item at the given index, and format the text to fit a width of
+ n columns.
+ """
+ raise NotImplementedError
+
+ def iterate(self, index, step, n_cols=70):
+ """
+ Return an iterator that starts and the current index and increments
+ by the given step.
+ """
+
+ while True:
+ if step < 0 and index < 0:
+ # Hack to prevent displaying a submission's post if iterating
+ # comments in the negative direction
+ break
+ try:
+ yield self.get(index, n_cols=n_cols)
+ except IndexError:
+ break
+ index += step
+
+ @property
+ def range(self):
+ """
+ Return the minimm and maximum valid indicies.
+ """
+ raise NotImplementedError
+
+ @staticmethod
+ def flatten_comments(comments, root_level=0):
+ """
+ Flatten a PRAW comment tree while preserving the nested level of each
+ comment via the `nested_level` attribute.
+
+ There are a couple of different ways that the input comment list can be
+ organized depending on its source:
+
+ 1. Comments that are returned from the get_submission() api call.
+ In this case, the comments list will contain only top level
+ comments and replies will be attached to those comments via
+ the `comment.replies` property.
+
+ 2. Comments that are returned from the comments() method on a
+ MoreComments object. In this case, the api returns all of the
+ comments and replies as a flat list. We need to sort out which
+ ones are replies to other comments by looking at the parent_id
+ parameter and checking if the id matches another comment.
+
+ In addition, there is a bug in praw where a MoreComments object that is
+ also a reply will be added below the comment as a sibling instead of
+ a child. So it is especially important that this method is robust and
+ double-checks all of the parent_id's of the comments.
+
+ Reference:
+ https://github.com/praw-dev/praw/issues/391
+
+ """
+
+ stack = comments[:]
+ for item in stack:
+ item.nested_level = root_level
+
+ retval, parent_candidates = [], {}
+ while stack:
+ item = stack.pop(0)
+
+ # The MoreComments item count should never be zero, discard it if
+ # it is. Need to look into this further.
+ if isinstance(item, praw.objects.MoreComments) and item.count == 0:
+ continue
+
+ if item.parent_id:
+ # Search the list of previous comments for a possible parent
+ # The match is based off of the parent_id parameter E.g.
+ # parent.id = c0tprcm
+ # child.parent_id = t1_c0tprcm
+ parent = parent_candidates.get(item.parent_id[3:])
+ if parent:
+ item.nested_level = parent.nested_level + 1
+
+ # Add all of the attached replies to the front of the stack to be
+ # parsed separately
+ if hasattr(item, 'replies'):
+ for n in item.replies:
+ n.nested_level = item.nested_level + 1
+ stack[0:0] = item.replies
+
+ # The comment is now a potential parent for the items that are
+ # remaining on the stack.
+ parent_candidates[item.id] = item
+
+ retval.append(item)
+ return retval
+
+ @classmethod
+ def strip_praw_comment(cls, comment):
+ """
+ Parse through a submission comment and return a dict with data ready to
+ be displayed through the terminal.
+ """
+
+ data = {}
+ data['object'] = comment
+
+ if isinstance(comment, praw.objects.MoreComments):
+ data['type'] = 'MoreComments'
+ data['level'] = comment.nested_level
+ data['count'] = comment.count
+ data['body'] = 'More comments'
+ data['hidden'] = True
+
+ elif hasattr(comment, 'nested_level'):
+ author = getattr(comment, 'author', '[deleted]')
+ name = getattr(author, 'name', '[deleted]')
+ sub = getattr(comment, 'submission', '[deleted]')
+ sub_author = getattr(sub, 'author', '[deleted]')
+ sub_name = getattr(sub_author, 'name', '[deleted]')
+ flair = getattr(comment, 'author_flair_text', '')
+ permalink = getattr(comment, 'permalink', None)
+ stickied = getattr(comment, 'stickied', False)
+
+ data['type'] = 'Comment'
+ data['level'] = comment.nested_level
+ data['body'] = comment.body
+ data['html'] = comment.body_html
+ data['created'] = cls.humanize_timestamp(comment.created_utc)
+ data['score'] = '{0} pts'.format(
+ '-' if comment.score_hidden else comment.score)
+ data['author'] = name
+ data['is_author'] = (name == sub_name)
+ data['flair'] = flair
+ data['likes'] = comment.likes
+ data['gold'] = comment.gilded
+ data['permalink'] = permalink
+ data['stickied'] = stickied
+ data['hidden'] = False
+ data['saved'] = comment.saved
+ if comment.edited:
+ data['edited'] = '(edit {})'.format(
+ cls.humanize_timestamp(comment.edited))
+ else:
+ data['edited'] = ''
+ 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 separate api call to load
+ # the full comment.
+ author = getattr(comment, 'author', '[deleted]')
+ stickied = getattr(comment, 'stickied', False)
+ flair = getattr(comment, 'author_flair_text', '')
+
+ data['type'] = 'SavedComment'
+ data['level'] = None
+ 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'
+ data['score'] = '{0} pts'.format(
+ '-' if comment.score_hidden else comment.score)
+ data['likes'] = comment.likes
+ data['created'] = cls.humanize_timestamp(comment.created_utc)
+ data['saved'] = comment.saved
+ data['stickied'] = stickied
+ data['gold'] = comment.gilded
+ data['author'] = author
+ data['flair'] = flair
+ data['hidden'] = False
+ if comment.edited:
+ data['edited'] = '(edit {})'.format(
+ cls.humanize_timestamp(comment.edited))
+ else:
+ data['edited'] = ''
+
+ return data
+
+ @classmethod
+ def strip_praw_submission(cls, sub):
+ """
+ Parse through a submission and return a dict with data ready to be
+ displayed through the terminal.
+
+ Definitions:
+ permalink - URL to the reddit page with submission comments.
+ url_full - URL that the submission points to.
+ url - URL that will be displayed on the subreddit page, may be
+ "selfpost", "x-post submission", "x-post subreddit", or an
+ external link.
+ """
+
+ reddit_link = re.compile(
+ r'https?://(www\.)?(np\.)?redd(it\.com|\.it)/r/.*')
+ author = getattr(sub, 'author', '[deleted]')
+ name = getattr(author, 'name', '[deleted]')
+ flair = getattr(sub, 'link_flair_text', '')
+
+ data = {}
+ data['object'] = sub
+ data['type'] = 'Submission'
+ data['title'] = sub.title
+ data['text'] = sub.selftext
+ data['html'] = sub.selftext_html or ''
+ data['created'] = cls.humanize_timestamp(sub.created_utc)
+ data['created_long'] = cls.humanize_timestamp(sub.created_utc, True)
+ data['comments'] = '{0} comments'.format(sub.num_comments)
+ data['score'] = '{0} pts'.format('-' if sub.hide_score else sub.score)
+ data['author'] = name
+ data['permalink'] = sub.permalink
+ data['subreddit'] = six.text_type(sub.subreddit)
+ data['flair'] = '[{0}]'.format(flair.strip(' []')) if flair else ''
+ data['url_full'] = sub.url
+ data['likes'] = sub.likes
+ data['gold'] = sub.gilded
+ data['nsfw'] = sub.over_18
+ data['stickied'] = sub.stickied
+ data['hidden'] = sub.hidden
+ data['xpost_subreddit'] = None
+ data['index'] = None # This is filled in later by the method caller
+ data['saved'] = sub.saved
+ if sub.edited:
+ data['edited'] = '(edit {})'.format(
+ cls.humanize_timestamp(sub.edited))
+ data['edited_long'] = '(edit {})'.format(
+ cls.humanize_timestamp(sub.edited, True))
+ else:
+ data['edited'] = ''
+ data['edited_long'] = ''
+
+ if sub.url.split('/r/')[-1] == sub.permalink.split('/r/')[-1]:
+ data['url'] = 'self.{0}'.format(data['subreddit'])
+ data['url_type'] = 'selfpost'
+ elif reddit_link.match(sub.url):
+ # Strip the subreddit name from the permalink to avoid having
+ # submission.subreddit.url make a separate API call
+ url_parts = sub.url.split('/')
+ data['xpost_subreddit'] = url_parts[4]
+ data['url'] = 'self.{0}'.format(url_parts[4])
+ if 'comments' in url_parts:
+ data['url_type'] = 'x-post submission'
+ else:
+ data['url_type'] = 'x-post subreddit'
+ else:
+ data['url'] = sub.url
+ data['url_type'] = 'external'
+
+ return data
+
+ @staticmethod
+ def strip_praw_subscription(subscription):
+ """
+ Parse through a subscription and return a dict with data ready to be
+ displayed through the terminal.
+ """
+
+ data = {}
+ data['object'] = subscription
+ if isinstance(subscription, praw.objects.Multireddit):
+ data['type'] = 'Multireddit'
+ data['name'] = subscription.path
+ data['title'] = subscription.description_md
+ else:
+ data['type'] = 'Subscription'
+ data['name'] = "/r/" + subscription.display_name
+ data['title'] = subscription.title
+
+ return data
+
+ @classmethod
+ def strip_praw_message(cls, msg):
+ """
+ Parse through a message and return a dict with data ready to be
+ displayed through the terminal. Messages can be of either type
+ praw.objects.Message or praw.object.Comment. The comments returned will
+ contain special fields unique to messages and can't be parsed as normal
+ comment objects.
+ """
+ author = getattr(msg, 'author', None)
+
+ data = {}
+ data['object'] = msg
+
+ if isinstance(msg, praw.objects.Message):
+ data['type'] = 'Message'
+ data['level'] = msg.nested_level
+ data['distinguished'] = msg.distinguished
+ data['permalink'] = None
+ data['submission_permalink'] = None
+ data['subreddit_name'] = None
+ data['link_title'] = None
+ data['context'] = None
+ else:
+ data['type'] = 'InboxComment'
+ data['level'] = 0
+ data['distinguished'] = None
+ data['permalink'] = msg._fast_permalink
+ data['submission_permalink'] = '/'.join(data['permalink'].split('/')[:-2])
+ data['subreddit_name'] = msg.subreddit_name_prefixed
+ data['link_title'] = msg.link_title
+ data['context'] = msg.context
+
+ data['id'] = msg.id
+ data['subject'] = msg.subject
+ data['body'] = msg.body
+ data['html'] = msg.body_html
+ data['created'] = cls.humanize_timestamp(msg.created_utc)
+ data['created_long'] = cls.humanize_timestamp(msg.created_utc, True)
+ data['recipient'] = msg.dest
+ data['distinguished'] = msg.distinguished
+ data['author'] = author.name if author else '[deleted]'
+ data['is_new'] = msg.new
+ data['was_comment'] = msg.was_comment
+ return data
+
+ @staticmethod
+ def humanize_timestamp(utc_timestamp, verbose=False):
+ """
+ Convert a utc timestamp into a human readable relative-time.
+ """
+
+ timedelta = datetime.utcnow() - datetime.utcfromtimestamp(utc_timestamp)
+
+ seconds = int(timedelta.total_seconds())
+ if seconds < 60:
+ return 'moments ago' if verbose else '0min'
+
+ minutes = seconds // 60
+ if minutes < 60:
+ if verbose and minutes == 1:
+ return '1 minutes ago'
+ elif verbose:
+ return '%d minutes ago' % minutes
+ else:
+ return '%dmin' % minutes
+
+ hours = minutes // 60
+ if hours < 24:
+ if verbose and hours == 1:
+ return '1 hour ago'
+ elif verbose:
+ return '%d hours ago' % hours
+ else:
+ return '%dhr' % hours
+
+ days = hours // 24
+ if days < 30:
+ if verbose and days == 1:
+ return '1 day ago'
+ elif verbose:
+ return '%d days ago' % days
+ else:
+ return '%dday' % days
+
+ months = days // 30.4
+ if months < 12:
+ if verbose and months == 1:
+ return '1 month ago'
+ elif verbose:
+ return '%d months ago' % months
+ else:
+ return '%dmonth' % months
+
+ years = months // 12
+ if verbose and years == 1:
+ return '1 year ago'
+ elif verbose:
+ return '%d years ago' % years
+ else:
+ return '%dyr' % years
+
+ @staticmethod
+ def wrap_text(text, width):
+ """
+ Wrap text paragraphs to the given character width while preserving
+ newlines.
+ """
+ out = []
+ for paragraph in text.splitlines():
+ # Wrap returns an empty list when paragraph is a newline. In order
+ # to preserve newlines we substitute a list containing an empty
+ # string.
+ lines = wrap(paragraph, width=width) or ['']
+ out.extend(lines)
+ return out
+
+ @staticmethod
+ def extract_links(html):
+ """
+ Extract a list of hyperlinks from an HTML document.
+ """
+ links = []
+ soup = BeautifulSoup(html, 'html.parser')
+ for link in soup.findAll('a'):
+ href = link.get('href')
+ if not href:
+ continue
+ if href.startswith('/'):
+ href = 'https://www.reddit.com' + href
+ links.append({'text': link.text, 'href': href})
+ return links
+
+
+class SubmissionContent(Content):
+ """
+ Grab a submission from PRAW and lazily store comments to an internal
+ list for repeat access.
+ """
+
+ def __init__(self, submission, loader, indent_size=2, max_indent_level=8,
+ order=None, max_comment_cols=120):
+
+ submission_data = self.strip_praw_submission(submission)
+ comments = self.flatten_comments(submission.comments)
+
+ self.indent_size = indent_size
+ self.max_indent_level = max_indent_level
+ self.name = submission_data['permalink']
+ self.order = order
+ self.query = None
+ self._loader = loader
+ self._submission = submission
+ self._submission_data = submission_data
+ self._comment_data = [self.strip_praw_comment(c) for c in comments]
+ self._max_comment_cols = max_comment_cols
+
+ @classmethod
+ def from_url(cls, reddit, url, loader, indent_size=2, max_indent_level=8,
+ order=None, max_comment_cols=120):
+
+ # Reddit forces SSL
+ url = url.replace('http:', 'https:')
+
+ # Sometimes reddit will return a 403 FORBIDDEN when trying to access an
+ # np link while using OAUTH. Cause is unknown.
+ url = url.replace('https://np.', 'https://www.')
+
+ # Sometimes reddit will return internal links like "context" as
+ # relative URLs.
+ if url.startswith('/'):
+ url = 'https://www.reddit.com' + url
+
+ submission = reddit.get_submission(url, comment_sort=order)
+ return cls(submission, loader, indent_size, max_indent_level, order,
+ max_comment_cols)
+
+ @property
+ def range(self):
+ return -1, len(self._comment_data) - 1
+
+ def get(self, index, n_cols=70):
+ """
+ Grab the `i`th submission, with the title field formatted to fit inside
+ of a window of width `n`
+ """
+
+ if index < -1:
+ raise IndexError
+
+ elif index == -1:
+ data = self._submission_data
+ data['split_title'] = self.wrap_text(data['title'], width=n_cols-2)
+ data['split_text'] = self.wrap_text(data['text'], width=n_cols-2)
+ data['n_rows'] = len(data['split_title'] + data['split_text']) + 5
+ data['h_offset'] = 0
+
+ else:
+ data = self._comment_data[index]
+ indent_level = min(data['level'], self.max_indent_level)
+ data['h_offset'] = indent_level * self.indent_size
+
+ if data['type'] == 'Comment':
+ width = min(n_cols - data['h_offset'], self._max_comment_cols)
+ data['split_body'] = self.wrap_text(data['body'], width=width)
+ data['n_rows'] = len(data['split_body']) + 1
+ else:
+ data['n_rows'] = 1
+
+ return data
+
+ def toggle(self, index, n_cols=70):
+ """
+ Toggle the state of the object at the given index.
+
+ If it is a comment, pack it into a hidden comment.
+ If it is a hidden comment, unpack it.
+ If it is more comments, load the comments.
+ """
+ data = self.get(index)
+
+ if data['type'] == 'Submission':
+ # Can't hide the submission!
+ pass
+
+ elif data['type'] == 'Comment':
+ cache = [data]
+ count = 1
+ for d in self.iterate(index + 1, 1, n_cols):
+ if d['level'] <= data['level']:
+ break
+
+ count += d.get('count', 1)
+ cache.append(d)
+
+ comment = {
+ 'type': 'HiddenComment',
+ 'cache': cache,
+ 'count': count,
+ 'level': data['level'],
+ 'body': 'Hidden',
+ 'hidden': True}
+
+ self._comment_data[index:index + len(cache)] = [comment]
+
+ elif data['type'] == 'HiddenComment':
+ self._comment_data[index:index + 1] = data['cache']
+
+ elif data['type'] == 'MoreComments':
+ with self._loader('Loading comments'):
+ # Undefined behavior if using a nested loader here
+ assert self._loader.depth == 1
+ comments = data['object'].comments(update=True)
+ if not self._loader.exception:
+ comments = self.flatten_comments(comments, data['level'])
+ comment_data = [self.strip_praw_comment(c) for c in comments]
+ self._comment_data[index:index + 1] = comment_data
+
+ else:
+ raise ValueError('%s type not recognized' % data['type'])
+
+
+class SubredditContent(Content):
+ """
+ Grab a subreddit from PRAW and lazily stores submissions to an internal
+ list for repeat access.
+ """
+
+ def __init__(self, name, submissions, loader, order=None,
+ max_title_rows=4, query=None, filter_nsfw=False):
+
+ self.name = name
+ self.order = order
+ self.query = query
+ self.max_title_rows = max_title_rows
+ self.filter_nsfw = filter_nsfw
+ self._loader = loader
+ self._submissions = submissions
+ self._submission_data = []
+
+ # Verify that content exists for the given submission generator.
+ # This is necessary because PRAW loads submissions lazily, and
+ # there is is no other way to check things like multireddits that
+ # don't have a real corresponding subreddit object.
+ try:
+ self.get(0)
+ except IndexError:
+ full_name = self.name
+ if self.order:
+ full_name += '/' + self.order
+ raise exceptions.NoSubmissionsError(full_name)
+
+ @classmethod
+ def from_name(cls, reddit, name, loader, order=None, query=None):
+ """
+ Params:
+ reddit (praw.Reddit): Instance of the reddit api.
+ name (text): The name of the desired subreddit, user, multireddit,
+ etc. In most cases this translates directly from the URL that
+ reddit itself uses. This is what users will type in the command
+ prompt when they navigate to a new location.
+ loader (terminal.loader): Handler for the load screen that will be
+ displayed when making http requests.
+ order (text): If specified, the order that posts will be sorted in.
+ For `top` and `controversial`, you can specify the time frame
+ by including a dash, e.g. "top-year". If an order is not
+ specified, it will be extracted from the name.
+ query (text): Content to search for on the given subreddit or
+ user's page.
+ """
+ # TODO: This desperately needs to be refactored
+
+ # Strip leading, trailing, and redundant backslashes
+ parts = [seg for seg in name.strip(' /').split('/') if seg]
+
+ # Check for the resource type, assume /r/ as the default
+ if len(parts) >= 3 and parts[2] == 'm':
+ # E.g. /u/civilization_phaze_3/m/multireddit ->
+ # resource_root = "u/civilization_phaze_3/m"
+ # parts = ["multireddit"]
+ resource_root, parts = '/'.join(parts[:3]), parts[3:]
+ elif len(parts) > 1 and parts[0] in ['r', 'u', 'user', 'domain']:
+ # E.g. /u/civilization_phaze_3 ->
+ # resource_root = "u"
+ # parts = ["civilization_phaze_3"]
+ #
+ # E.g. /r/python/top-week ->
+ # resource_root = "r"
+ # parts = ["python", "top-week"]
+ resource_root = parts.pop(0)
+ else:
+ resource_root = 'r'
+
+ if resource_root == 'user':
+ resource_root = 'u'
+ elif resource_root.startswith('user/'):
+ # Special check for multi-reddit resource roots
+ # E.g.
+ # before: resource_root = "user/civilization_phaze_3/m"
+ # After: resource_root = "u/civilization_phaze_3/m"
+ resource_root = 'u' + resource_root[4:]
+
+ # The parts left should be in one of the following forms:
+ # [resource]
+ # [resource, order]
+ # [resource, user_room, order]
+
+ user_rooms = ['overview', 'submitted', 'comments']
+ private_user_rooms = ['upvoted', 'downvoted', 'hidden', 'saved']
+ user_room = None
+
+ if len(parts) == 1:
+ # E.g. /r/python
+ # parts = ["python"]
+ # resource = "python"
+ # resource_order = None
+ resource, resource_order = parts[0], None
+ elif resource_root == 'u' and len(parts) in [2, 3] \
+ and parts[1] in user_rooms + private_user_rooms:
+ # E.g. /u/spez/submitted/top ->
+ # parts = ["spez", "submitted", "top"]
+ # resource = "spez"
+ # user_room = "submitted"
+ # resource_order = "top"
+ resource, user_room = parts[:2]
+ resource_order = parts[2] if len(parts) == 3 else None
+ elif len(parts) == 2:
+ # E.g. /r/python/top
+ # parts = ["python", "top"]
+ # resource = "python
+ # resource_order = "top"
+ resource, resource_order = parts
+ else:
+ raise InvalidSubreddit('`{}` is an invalid format'.format(name))
+
+ if not resource:
+ # Praw does not correctly handle empty strings
+ # https://github.com/praw-dev/praw/issues/615
+ raise InvalidSubreddit('Subreddit cannot be empty')
+
+ # If the order was explicitly passed in, it will take priority over
+ # the order that was extracted from the name
+ order = order or resource_order
+
+ display_order = order
+ display_name = '/'.join(['', resource_root, resource])
+ if user_room and resource_root == 'u':
+ display_name += '/' + user_room
+
+ # Split the order from the period E.g. controversial-all, top-hour
+ if order and '-' in order:
+ order, period = order.split('-', 1)
+ else:
+ period = None
+
+ if query:
+ # The allowed orders for sorting search results are different
+ orders = ['relevance', 'top', 'comments', 'new', None]
+ period_allowed = ['top', 'comments']
+ else:
+ orders = ['hot', 'top', 'rising', 'new', 'controversial', 'gilded', None]
+ period_allowed = ['top', 'controversial']
+
+ if order not in orders:
+ raise InvalidSubreddit('Invalid order `%s`' % order)
+ if period not in ['all', 'day', 'hour', 'month', 'week', 'year', None]:
+ raise InvalidSubreddit('Invalid period `%s`' % period)
+ if period and order not in period_allowed:
+ raise InvalidSubreddit(
+ '`%s` order does not allow sorting by period' % order)
+
+ # On some objects, praw doesn't allow you to pass arguments for the
+ # order and period. Instead you need to call special helper functions
+ # such as Multireddit.get_controversial_from_year(). Build the method
+ # name here for convenience.
+ if period:
+ method_alias = 'get_{0}_from_{1}'.format(order, period)
+ elif order:
+ method_alias = 'get_{0}'.format(order)
+ else:
+ method_alias = 'get_hot'
+
+ # Here's where we start to build the submission generators
+ if query:
+ if resource_root == 'u':
+ search = '/r/{subreddit}/search'
+ author = reddit.user.name if resource == 'me' else resource
+ query = 'author:{0} {1}'.format(author, query)
+ subreddit = None
+ else:
+ search = resource_root + '/{subreddit}/search'
+ subreddit = None if resource == 'front' else resource
+
+ reddit.config.API_PATHS['search'] = search
+ submissions = reddit.search(query, subreddit=subreddit,
+ sort=order, period=period)
+
+ elif resource_root == 'domain':
+ order = order or 'hot'
+ submissions = reddit.get_domain_listing(
+ resource, sort=order, period=period, limit=None)
+
+ elif resource_root.endswith('/m'):
+ redditor = resource_root.split('/')[1]
+
+ if redditor == 'me':
+ if not reddit.is_oauth_session():
+ raise exceptions.AccountError('Not logged in')
+ else:
+ redditor = reddit.user.name
+ display_name = display_name.replace(
+ '/me/', '/{0}/'.format(redditor))
+
+ multireddit = reddit.get_multireddit(redditor, resource)
+ submissions = getattr(multireddit, method_alias)(limit=None)
+
+ elif resource_root == 'u' and resource == 'me':
+ if not reddit.is_oauth_session():
+ raise exceptions.AccountError('Not logged in')
+ else:
+ user_room = user_room or 'overview'
+ order = order or 'new'
+ period = period or 'all'
+ method = getattr(reddit.user, 'get_%s' % user_room)
+ submissions = method(sort=order, time=period, limit=None)
+
+ elif resource_root == 'u':
+ user_room = user_room or 'overview'
+ if user_room not in user_rooms:
+ # Tried to access a private room like "u/me/hidden" for a
+ # different redditor
+ raise InvalidSubreddit('Unavailable Resource')
+ order = order or 'new'
+ period = period or 'all'
+ redditor = reddit.get_redditor(resource)
+ method = getattr(redditor, 'get_%s' % user_room)
+ submissions = method(sort=order, time=period, limit=None)
+
+ elif resource == 'front':
+ if order in (None, 'hot'):
+ submissions = reddit.get_front_page(limit=None)
+ elif period:
+ # For the front page, praw makes you send the period as `t`
+ # instead of calling reddit.get_hot_from_week()
+ method_alias = 'get_{0}'.format(order)
+ method = getattr(reddit, method_alias)
+ submissions = method(limit=None, params={'t': period})
+ else:
+ submissions = getattr(reddit, method_alias)(limit=None)
+
+ else:
+ subreddit = reddit.get_subreddit(resource)
+ submissions = getattr(subreddit, method_alias)(limit=None)
+
+ # For special subreddits like /r/random we want to replace the
+ # display name with the one returned by the request.
+ display_name = '/r/{0}'.format(subreddit.display_name)
+
+ filter_nsfw = (reddit.user and reddit.user.over_18 is False)
+
+ # We made it!
+ return cls(display_name, submissions, loader, order=display_order,
+ query=query, filter_nsfw=filter_nsfw)
+
+ @property
+ def range(self):
+ # Note that for subreddits, the submissions are generated lazily and
+ # there is no actual "end" index. Instead, we return the bottom index
+ # that we have loaded so far.
+ return 0, len(self._submission_data) - 1
+
+ def get(self, index, n_cols=70):
+ """
+ Grab the `i`th submission, with the title field formatted to fit inside
+ of a window of width `n_cols`
+ """
+
+ if index < 0:
+ raise IndexError
+
+ nsfw_count = 0
+ while index >= len(self._submission_data):
+ try:
+ with self._loader('Loading more submissions'):
+ submission = next(self._submissions)
+ if self._loader.exception:
+ raise IndexError
+ except StopIteration:
+ raise IndexError
+ else:
+
+ # Skip NSFW posts based on the reddit user's profile settings.
+ # If we see 20+ NSFW posts at the beginning, assume the subreddit
+ # only has NSFW content and abort. This allows us to avoid making
+ # an additional API call to check if a subreddit is over18 (which
+ # doesn't work for things like multireddits anyway)
+ if self.filter_nsfw and submission.over_18:
+ nsfw_count += 1
+ if not self._submission_data and nsfw_count >= 20:
+ raise exceptions.SubredditError(
+ 'You must be over 18+ to view this subreddit')
+ continue
+ else:
+ nsfw_count = 0
+
+ if hasattr(submission, 'title'):
+ data = self.strip_praw_submission(submission)
+ else:
+ # when submission is a saved comment
+ data = self.strip_praw_comment(submission)
+
+ data['index'] = len(self._submission_data) + 1
+ # Add the post number to the beginning of the title
+ data['title'] = '{0}. {1}'.format(data['index'], data['title'])
+ self._submission_data.append(data)
+
+ # Modifies the original dict, faster than copying
+ data = self._submission_data[index]
+ data['split_title'] = self.wrap_text(data['title'], width=n_cols)
+ if len(data['split_title']) > self.max_title_rows:
+ data['split_title'] = data['split_title'][:self.max_title_rows-1]
+ data['split_title'].append('(Not enough space to display)')
+ data['n_rows'] = len(data['split_title']) + 3
+ data['h_offset'] = 0
+
+ return data
+
+
+class SubscriptionContent(Content):
+
+ def __init__(self, name, subscriptions, loader):
+
+ self.name = name
+ self.order = None
+ self.query = None
+ self._loader = loader
+ self._subscriptions = subscriptions
+ self._subscription_data = []
+
+ try:
+ self.get(0)
+ except IndexError:
+ raise exceptions.SubscriptionError('No content')
+
+ # Load 1024 subscriptions up front (one http request's worth)
+ # For most people this should be all of their subscriptions. This
+ # allows the user to jump to the end of the page with `G`.
+ if name != 'Popular Subreddits':
+ try:
+ self.get(1023)
+ except IndexError:
+ pass
+
+ @classmethod
+ def from_user(cls, reddit, loader, content_type='subreddit'):
+ if content_type == 'subreddit':
+ name = 'My Subreddits'
+ items = reddit.get_my_subreddits(limit=None)
+ elif content_type == 'multireddit':
+ name = 'My Multireddits'
+ # Multireddits are returned as a list
+ items = iter(reddit.get_my_multireddits())
+ elif content_type == 'popular':
+ name = 'Popular Subreddits'
+ items = reddit.get_popular_subreddits(limit=None)
+ else:
+ raise exceptions.SubscriptionError('Invalid type %s' % content_type)
+
+ return cls(name, items, loader)
+
+ @property
+ def range(self):
+ return 0, len(self._subscription_data) - 1
+
+ def get(self, index, n_cols=70):
+ """
+ Grab the `i`th object, with the title field formatted to fit
+ inside of a window of width `n_cols`
+ """
+
+ if index < 0:
+ raise IndexError
+
+ while index >= len(self._subscription_data):
+ try:
+ with self._loader('Loading content'):
+ subscription = next(self._subscriptions)
+ if self._loader.exception:
+ raise IndexError
+ except StopIteration:
+ raise IndexError
+ else:
+ data = self.strip_praw_subscription(subscription)
+ self._subscription_data.append(data)
+
+ data = self._subscription_data[index]
+ data['split_title'] = self.wrap_text(data['title'], width=n_cols)
+ data['n_rows'] = len(data['split_title']) + 1
+ data['h_offset'] = 0
+
+ return data
+
+
+class InboxContent(Content):
+
+ def __init__(self, order, content_generator, loader,
+ indent_size=2, max_indent_level=8):
+
+ self.name = 'My Inbox'
+ self.order = order
+ self.query = None
+ self.indent_size = indent_size
+ self.max_indent_level = max_indent_level
+ self._loader = loader
+ self._content_generator = content_generator
+ self._content_data = []
+
+ try:
+ self.get(0)
+ except IndexError:
+ if order == 'all':
+ raise exceptions.InboxError('Empty Inbox')
+ else:
+ raise exceptions.InboxError('Empty Inbox [%s]' % order)
+
+ @classmethod
+ def from_user(cls, reddit, loader, order='all'):
+ if order == 'all':
+ items = reddit.get_inbox(limit=None)
+ elif order == 'unread':
+ items = reddit.get_unread(limit=None)
+ elif order == 'messages':
+ items = reddit.get_messages(limit=None)
+ elif order == 'comments':
+ items = reddit.get_comment_replies(limit=None)
+ elif order == 'posts':
+ items = reddit.get_post_replies(limit=None)
+ elif order == 'mentions':
+ items = reddit.get_mentions(limit=None)
+ elif order == 'sent':
+ items = reddit.get_sent(limit=None)
+ else:
+ raise exceptions.InboxError('Invalid order %s' % order)
+
+ return cls(order, items, loader)
+
+ @property
+ def range(self):
+ return 0, len(self._content_data) - 1
+
+ def get(self, index, n_cols=70):
+ """
+ Grab the `i`th object, with the title field formatted to fit
+ inside of a window of width `n_cols`
+ """
+
+ if index < 0:
+ raise IndexError
+
+ while index >= len(self._content_data):
+ try:
+ with self._loader('Loading content'):
+ item = next(self._content_generator)
+ if self._loader.exception:
+ raise IndexError
+ except StopIteration:
+ raise IndexError
+ else:
+ if isinstance(item, praw.objects.Message):
+ # Message chains can be treated like comment trees
+ for child_message in self.flatten_comments([item]):
+ data = self.strip_praw_message(child_message)
+ self._content_data.append(data)
+ else:
+ # Comments also return children, but we don't display them
+ # in the Inbox page so they don't need to be parsed here.
+ data = self.strip_praw_message(item)
+ self._content_data.append(data)
+
+ data = self._content_data[index]
+ indent_level = min(data['level'], self.max_indent_level)
+ data['h_offset'] = indent_level * self.indent_size
+ width = n_cols - data['h_offset']
+ data['split_body'] = self.wrap_text(data['body'], width=width)
+ data['n_rows'] = len(data['split_body']) + 2
+
+ return data
+
+
+class RequestHeaderRateLimiter(DefaultHandler):
+ """Custom PRAW request handler for rate-limiting requests.
+
+ This is an alternative to PRAW 3's DefaultHandler that uses
+ Reddit's modern API guidelines to rate-limit requests based
+ on the X-Ratelimit-* headers returned from Reddit. Most of
+ these methods are copied from or derived from the DefaultHandler.
+
+ References:
+ https://github.com/reddit/reddit/wiki/API
+ https://github.com/praw-dev/prawcore/blob/master/prawcore/rate_limit.py
+ """
+
+ def __init__(self):
+
+ # In PRAW's convention, these variables were bound to the
+ # class so the cache could be shared among all of the ``reddit``
+ # instances. In TRV's use-case there is only ever a single reddit
+ # instance so it made sense to clean up the globals and transfer them
+ # to method variables
+ self.cache = {}
+ self.timeouts = {}
+
+ # These are used for the header rate-limiting
+ self.used = None
+ self.remaining = None
+ self.seconds_to_reset = None
+ self.next_request_timestamp = None
+
+ super(RequestHeaderRateLimiter, self).__init__()
+
+ def _delay(self):
+ """
+ Pause before making the next HTTP request.
+ """
+ if self.next_request_timestamp is None:
+ return
+
+ sleep_seconds = self.next_request_timestamp - time.time()
+ if sleep_seconds <= 0:
+ return
+ time.sleep(sleep_seconds)
+
+ def _update(self, response_headers):
+ """
+ Update the state of the rate limiter based on the response headers:
+
+ X-Ratelimit-Used: Approximate number of requests used this period
+ X-Ratelimit-Remaining: Approximate number of requests left to use
+ X-Ratelimit-Reset: Approximate number of seconds to end of period
+
+ PRAW 5's rate limiting logic is structured for making hundreds of
+ evenly-spaced API requests, which makes sense for running something
+ like a bot or crawler.
+
+ This handler's logic, on the other hand, is geared more towards
+ interactive usage. It allows for short, sporadic bursts of requests.
+ The assumption is that actual users browsing reddit shouldn't ever be
+ in danger of hitting the rate limit. If they do hit the limit, they
+ will be cutoff until the period resets.
+ """
+
+ if 'x-ratelimit-remaining' not in response_headers:
+ # This could be because the API returned an error response, or it
+ # could be because we're using something like read-only credentials
+ # which Reddit doesn't appear to care about rate limiting.
+ return
+
+ self.used = float(response_headers['x-ratelimit-used'])
+ self.remaining = float(response_headers['x-ratelimit-remaining'])
+ self.seconds_to_reset = int(response_headers['x-ratelimit-reset'])
+ _logger.debug('Rate limit: %s used, %s remaining, %s reset',
+ self.used, self.remaining, self.seconds_to_reset)
+
+ if self.remaining <= 0:
+ self.next_request_timestamp = time.time() + self.seconds_to_reset
+ else:
+ self.next_request_timestamp = None
+
+ def _clear_timeouts(self, cache_timeout):
+ """
+ Clear the cache of timed out results.
+ """
+
+ for key in list(self.timeouts):
+ if timer() - self.timeouts[key] > cache_timeout:
+ del self.timeouts[key]
+ del self.cache[key]
+
+ def clear_cache(self):
+ """Remove all items from the cache."""
+ self.cache = {}
+ self.timeouts = {}
+
+ def evict(self, urls):
+ """Remove items from cache matching URLs.
+
+ Return the number of items removed.
+ """
+ if isinstance(urls, six.text_type):
+ urls = [urls]
+ urls = set(normalize_url(url) for url in urls)
+ retval = 0
+ for key in list(self.cache):
+ if key[0] in urls:
+ retval += 1
+ del self.cache[key]
+ del self.timeouts[key]
+ return retval
+
+ def request(self, _cache_key, _cache_ignore, _cache_timeout, **kwargs):
+ """
+ This is a wrapper function that handles the caching of the request.
+
+ See DefaultHandler.with_cache for reference.
+ """
+ if _cache_key:
+ # Pop the request's session cookies from the cache key.
+ # These appear to be unreliable and change with every
+ # request. Also, with the introduction of OAuth I don't think
+ # that cookies are being used to store anything that
+ # differentiates API requests anyways
+ url, items = _cache_key
+ _cache_key = (url, (items[0], items[1], items[3], items[4]))
+
+ if kwargs['request'].method != 'GET':
+ # I added this check for TTRV, I have no idea why PRAW would ever
+ # want to cache POST/PUT/DELETE requests
+ _cache_ignore = True
+
+ if _cache_ignore:
+ return self._request(**kwargs)
+
+ self._clear_timeouts(_cache_timeout)
+ if _cache_key in self.cache:
+ return self.cache[_cache_key]
+
+ result = self._request(**kwargs)
+
+ # The handlers don't call `raise_for_status` so we need to ignore
+ # status codes that will result in an exception that should not be
+ # cached.
+ if result.status_code not in (200, 302):
+ return result
+
+ self.timeouts[_cache_key] = timer()
+ self.cache[_cache_key] = result
+ return result
+
+ def _request(self, request, proxies, timeout, verify, **_):
+ """
+ This is where we apply rate limiting and make the HTTP request.
+ """
+
+ settings = self.http.merge_environment_settings(
+ request.url, proxies, False, verify, None)
+
+ self._delay()
+ response = self.http.send(
+ request, timeout=timeout, allow_redirects=False, **settings)
+ self._update(response.headers)
+
+ return response
diff --git a/ttrv/docs.py b/ttrv/docs.py
new file mode 100644
index 0000000..471a1e5
--- /dev/null
+++ b/ttrv/docs.py
@@ -0,0 +1,229 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+AGENT = """\
+desktop:https://github.com/tildeclub/ttrv:{version}\
+(by /u/civilization_phaze_3)\
+"""
+
+SUMMARY = """
+TVR (Terminal Viewer Reddit) is a terminal interface to view and interact with reddit.
+"""
+
+USAGE = """\
+ttrv [URL] [-s SUBREDDIT]
+
+ $ ttrv https://www.reddit.com/r/programming/comments/7h9l31
+ $ ttrv -s linux
+"""
+
+CONTROLS = """
+Move the cursor using the arrow keys or vim style movement.
+Press `?` to open the help screen.
+"""
+
+HELP = """\
+====================================
+Reddit Terminal Viewer
+
+https://github.com/tildeclub/ttrv
+====================================
+
+[Basic Commands]
+ j : Move the cursor down
+ k : Move the cursor up
+ l : View the currently selected item
+ h : Return to the previous view
+ m : Move the cursor up one page
+ n : Move the cursor down one page
+ gg : Jump to the top of the page
+ G : Jump to the bottom of the page
+ 1-7 : Sort submissions by category
+ r : Refresh the content on the current page
+ u : Login to your reddit account
+ q : Quit
+ Q : Force quit
+ y : Copy submission permalink to clipboard
+ Y : Copy submission link to clipboard
+ F2 : Cycle to the previous color theme
+ F3 : Cycle to the next color theme
+ ? : Show the help screen
+ / : Open a prompt to select a subreddit
+
+[Authenticated Commands]
+ a : Upvote
+ z : Downvote
+ c : Compose a new submission or comment
+ C : Compose a new private message
+ e : Edit the selected submission or comment
+ d : Delete the selected submission or comment
+ i : View your inbox (see Inbox Mode)
+ s : View your subscribed subreddits (see Subscription Mode)
+ S : View your subscribed multireddits (see Subscription Mode)
+ u : Logout of your reddit account
+ w : Save the selected submission or comment
+
+[Subreddit Mode]
+ l : View the comments for the selected submission (see Submission Mode)
+ o : Open the selected submission link using your web browser
+ SPACE : Mark the selected submission as hidden
+ p : Toggle between the currently viewed subreddit and /r/front
+ f : Open a prompt to search the current subreddit for a text string
+
+[Submission Mode]
+ h : Close the submission and return to the previous page
+ l : View the selected comment using the system's pager
+ o : Open a link in the comment using your web browser
+ SPACE : Fold or expand the selected comment and its children
+ b : Send the comment text to the system's urlviewer application
+ J : Move the cursor down the the next comment at the same indentation
+ K : Move the cursor up to the parent comment
+
+[Subscription Mode]
+ h : Close your subscriptions and return to the previous page
+ l : Open the selected subreddit or multireddit
+
+[Inbox Mode]
+ h : Close your inbox and return to the previous page
+ l : View the context of the selected comment
+ o : Open the submission of the selected comment
+ c : Reply to the selected comment or message
+ w : Mark the selected comment or message as seen
+
+[Prompt]
+ The / key opens a text prompt at the bottom of the screen. You can use this
+ to type in the name of the subreddit that you want to open. The following
+ text formats are recognized:
+
+ /python - Open a subreddit, shorthand
+ /r/python - Open a subreddit
+ /r/python/new - Open a subreddit, sorted by category
+ /r/python/controversial-year - Open a subreddit, sorted by category and time
+ /r/python+linux+commandline - Open multiple subreddits merged together
+ /comments/30rwj2 - Open a submission, shorthand
+ /r/python/comments/30rwj2 - Open a submission
+ /r/front - Open your front page
+ /u/me - View your submissions
+ /u/me/saved - View your saved content
+ /u/me/hidden - View your hidden content
+ /u/me/upvoted - View your upvoted content
+ /u/me/downvoted - View your downvoted content
+ /u/spez - View a user's submissions and comments
+ /u/spez/submitted - View a user's submissions
+ /u/spez/comments - View a user's comments
+ /u/multi-mod/m/android - Open a user's curated multireddit
+ /domain/python.org - Search for links for the given domain
+"""
+
+BANNER_SUBREDDIT = """
+[1]hot [2]top [3]rising [4]new [5]controversial [6]gilded
+"""
+
+BANNER_SUBMISSION = """
+[1]hot [2]top [3]rising [4]new [5]controversial
+"""
+
+BANNER_SEARCH = """
+[1]relevance [2]top [3]comments [4]new
+"""
+
+BANNER_INBOX = """
+[1]all [2]unread [3]messages [4]comments [5]posts [6]mentions [7]sent
+"""
+
+FOOTER_SUBREDDIT = """
+[?]Help [q]Quit [l]Comments [/]Prompt [u]Login [o]Open [c]Post [a/z]Vote [r]Refresh
+"""
+
+FOOTER_SUBMISSION = """
+[?]Help [q]Quit [h]Return [space]Fold/Expand [o]Open [c]Comment [a/z]Vote [r]Refresh
+"""
+
+FOOTER_SUBSCRIPTION = """
+[?]Help [q]Quit [h]Return [l]Select Subreddit [r]Refresh
+"""
+
+FOOTER_INBOX = """
+[?]Help [l]View Context [o]Open Submission [c]Reply [w]Mark Read [r]Refresh
+"""
+
+TOKEN = "INSTRUCTIONS"
+
+REPLY_FILE = """
+""".format(token=TOKEN)
+
+COMMENT_EDIT_FILE = """
+
+{{content}}
+""".format(token=TOKEN)
+
+SUBMISSION_FILE = """
+""".format(token=TOKEN)
+
+SUBMISSION_EDIT_FILE = """
+
+{{content}}
+""".format(token=TOKEN)
+
+MESSAGE_FILE = """
+""".format(token=TOKEN)
+
+OAUTH_ACCESS_DENIED = """\
+ Access Denied
+ Reddit Terminal Viewer was
+ denied access and will continue to operate in unauthenticated mode,
+ you can close this window.
+"""
+
+OAUTH_ERROR = """\
+ Error
+ {error}
+"""
+
+OAUTH_INVALID = """\
+ Wait...
+ This page is supposed to be a Reddit OAuth callback.
+ You can't just come here hands in your pocket!
+"""
+
+OAUTH_SUCCESS = """\
+ Access Granted
+ Reddit Terminal Viewer
+ will now log in, you can close this window.
+"""
+
+TIME_ORDER_MENU = """
+Links from:
+ [1] Past hour
+ [2] Past 24 hours
+ [3] Past week
+ [4] Past month
+ [5] Past year
+ [6] All time
+"""
diff --git a/ttrv/exceptions.py b/ttrv/exceptions.py
new file mode 100644
index 0000000..70102b7
--- /dev/null
+++ b/ttrv/exceptions.py
@@ -0,0 +1,63 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+
+class EscapeInterrupt(Exception):
+ "Signal that the ESC key has been pressed"
+
+
+class ConfigError(Exception):
+ "There was a problem with the configuration"
+
+
+class TTRVError(Exception):
+ "Base TTRV error class"
+
+
+class AccountError(TTRVError):
+ "Could not access user account"
+
+
+class SubmissionError(TTRVError):
+ "Submission could not be loaded"
+
+
+class SubredditError(TTRVError):
+ "Subreddit could not be loaded"
+
+
+class NoSubmissionsError(TTRVError):
+ "No submissions for the given page"
+
+ def __init__(self, name):
+ self.name = name
+ message = '`{0}` has no submissions'.format(name)
+ super(NoSubmissionsError, self).__init__(message)
+
+
+class SubscriptionError(TTRVError):
+ "Content could not be fetched"
+
+
+class InboxError(TTRVError):
+ "Content could not be fetched"
+
+
+class ProgramError(TTRVError):
+ "Problem executing an external program"
+
+
+class BrowserError(TTRVError):
+ "Could not open a web browser tab"
+
+
+class TemporaryFileError(TTRVError):
+ "Indicates that an error has occurred and the file should not be deleted"
+
+
+class MailcapEntryNotFound(TTRVError):
+ "A valid mailcap entry could not be coerced from the given url"
+
+
+class InvalidRefreshToken(TTRVError):
+ "The refresh token is corrupt and cannot be used to login"
diff --git a/ttrv/inbox_page.py b/ttrv/inbox_page.py
new file mode 100644
index 0000000..7968c4d
--- /dev/null
+++ b/ttrv/inbox_page.py
@@ -0,0 +1,204 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from . import docs
+from .content import InboxContent
+from .page import Page, PageController, logged_in
+from .objects import Navigator, Command
+
+
+class InboxController(PageController):
+ character_map = {}
+
+
+class InboxPage(Page):
+ BANNER = docs.BANNER_INBOX
+ FOOTER = docs.FOOTER_INBOX
+
+ name = 'inbox'
+
+ def __init__(self, reddit, term, config, oauth, content_type='all'):
+ super(InboxPage, self).__init__(reddit, term, config, oauth)
+
+ self.controller = InboxController(self, keymap=config.keymap)
+ self.content = InboxContent.from_user(reddit, term.loader, content_type)
+ self.nav = Navigator(self.content.get)
+ self.content_type = content_type
+
+ def handle_selected_page(self):
+ """
+ Open the subscription and submission pages subwindows, but close the
+ current page if any other type of page is selected.
+ """
+ if not self.selected_page:
+ pass
+ if self.selected_page.name in ('subscription', 'submission'):
+ # Launch page in a subwindow
+ self.selected_page = self.selected_page.loop()
+ elif self.selected_page.name in ('subreddit', 'inbox'):
+ # Replace the current page
+ self.active = False
+ else:
+ raise RuntimeError(self.selected_page.name)
+
+ @logged_in
+ def refresh_content(self, order=None, name=None):
+ """
+ Re-download all inbox content and reset the page index
+ """
+ self.content_type = order or self.content_type
+
+ with self.term.loader():
+ self.content = InboxContent.from_user(
+ self.reddit, self.term.loader, self.content_type)
+ if not self.term.loader.exception:
+ self.nav = Navigator(self.content.get)
+
+ @InboxController.register(Command('SORT_1'))
+ def load_content_inbox(self):
+ self.refresh_content(order='all')
+
+ @InboxController.register(Command('SORT_2'))
+ def load_content_unread_messages(self):
+ self.refresh_content(order='unread')
+
+ @InboxController.register(Command('SORT_3'))
+ def load_content_messages(self):
+ self.refresh_content(order='messages')
+
+ @InboxController.register(Command('SORT_4'))
+ def load_content_comment_replies(self):
+ self.refresh_content(order='comments')
+
+ @InboxController.register(Command('SORT_5'))
+ def load_content_post_replies(self):
+ self.refresh_content(order='posts')
+
+ @InboxController.register(Command('SORT_6'))
+ def load_content_username_mentions(self):
+ self.refresh_content(order='mentions')
+
+ @InboxController.register(Command('SORT_7'))
+ def load_content_sent_messages(self):
+ self.refresh_content(order='sent')
+
+ @InboxController.register(Command('INBOX_MARK_READ'))
+ @logged_in
+ def mark_seen(self):
+ """
+ Mark the selected message or comment as seen.
+ """
+ data = self.get_selected_item()
+ if data['is_new']:
+ with self.term.loader('Marking as read'):
+ data['object'].mark_as_read()
+ if not self.term.loader.exception:
+ data['is_new'] = False
+ else:
+ with self.term.loader('Marking as unread'):
+ data['object'].mark_as_unread()
+ if not self.term.loader.exception:
+ data['is_new'] = True
+
+ @InboxController.register(Command('INBOX_REPLY'))
+ @logged_in
+ def inbox_reply(self):
+ """
+ Reply to the selected private message or comment from the inbox.
+ """
+ self.reply()
+
+ @InboxController.register(Command('INBOX_EXIT'))
+ def close_inbox(self):
+ """
+ Close inbox and return to the previous page.
+ """
+ self.active = False
+
+ @InboxController.register(Command('INBOX_VIEW_CONTEXT'))
+ @logged_in
+ def view_context(self):
+ """
+ View the context surrounding the selected comment.
+ """
+ url = self.get_selected_item().get('context')
+ if url:
+ self.selected_page = self.open_submission_page(url)
+
+ @InboxController.register(Command('INBOX_OPEN_SUBMISSION'))
+ @logged_in
+ def open_submission(self):
+ """
+ Open the full submission and comment tree for the selected comment.
+ """
+ url = self.get_selected_item().get('submission_permalink')
+ if url:
+ self.selected_page = self.open_submission_page(url)
+
+ def _draw_item(self, win, data, inverted):
+
+ n_rows, n_cols = win.getmaxyx()
+ n_cols -= 1 # Leave space for the cursor in the first column
+
+ # Handle the case where the window is not large enough to fit the data.
+ valid_rows = range(0, n_rows)
+ offset = 0 if not inverted else -(data['n_rows'] - n_rows)
+
+ row = offset
+ if row in valid_rows:
+ if data['is_new']:
+ attr = self.term.attr('New')
+ self.term.add_line(win, '[new]', row, 1, attr)
+ self.term.add_space(win)
+
+ attr = self.term.attr('MessageSubject')
+ self.term.add_line(win, '{subject}'.format(**data), attr=attr)
+ self.term.add_space(win)
+ else:
+ attr = self.term.attr('MessageSubject')
+ self.term.add_line(win, '{subject}'.format(**data), row, 1, attr)
+ self.term.add_space(win)
+
+ if data['link_title']:
+ attr = self.term.attr('MessageLink')
+ self.term.add_line(win, '{link_title}'.format(**data), attr=attr)
+
+ row = offset + 1
+ if row in valid_rows:
+ # reddit.user might be ``None`` if the user logs out while viewing
+ # this page
+ if data['author'] == getattr(self.reddit.user, 'name', None):
+ self.term.add_line(win, 'to ', row, 1)
+ text = '{recipient}'.format(**data)
+ else:
+ self.term.add_line(win, 'from ', row, 1)
+ text = '{author}'.format(**data)
+ attr = self.term.attr('MessageAuthor')
+ self.term.add_line(win, text, attr=attr)
+ self.term.add_space(win)
+
+ if data['distinguished']:
+ attr = self.term.attr('Distinguished')
+ text = '[{distinguished}]'.format(**data)
+ self.term.add_line(win, text, attr=attr)
+ self.term.add_space(win)
+
+ attr = self.term.attr('Created')
+ text = 'sent {created_long}'.format(**data)
+ self.term.add_line(win, text, attr=attr)
+ self.term.add_space(win)
+
+ if data['subreddit_name']:
+ attr = self.term.attr('MessageSubreddit')
+ text = 'via {subreddit_name}'.format(**data)
+ self.term.add_line(win, text, attr=attr)
+ self.term.add_space(win)
+
+ attr = self.term.attr('MessageText')
+ for row, text in enumerate(data['split_body'], start=offset + 2):
+ if row in valid_rows:
+ self.term.add_line(win, text, row, 1, attr=attr)
+
+ attr = self.term.attr('CursorBlock')
+ for y in range(n_rows):
+ self.term.addch(win, y, 0, str(' '), attr)
diff --git a/ttrv/mime_parsers.py b/ttrv/mime_parsers.py
new file mode 100644
index 0000000..b926816
--- /dev/null
+++ b/ttrv/mime_parsers.py
@@ -0,0 +1,505 @@
+import re
+import logging
+import mimetypes
+import json
+
+import requests
+from bs4 import BeautifulSoup
+
+_logger = logging.getLogger(__name__)
+
+
+class BaseMIMEParser(object):
+ """
+ BaseMIMEParser can be sub-classed to define custom handlers for determining
+ the MIME type of external urls.
+ """
+ pattern = re.compile(r'.*$')
+
+ @staticmethod
+ def get_mimetype(url):
+ """
+ Guess based on the file extension.
+
+ Args:
+ url (text): Web url that was linked to by a reddit submission.
+
+ Returns:
+ modified_url (text): The url (or filename) that will be used when
+ constructing the command to run.
+ content_type (text): The mime-type that will be used when
+ constructing the command to run. If the mime-type is unknown,
+ return None and the program will fallback to using the web
+ browser.
+ """
+ filename = url.split('?')[0]
+ filename = filename.split('#')[0]
+ content_type, _ = mimetypes.guess_type(filename)
+ return url, content_type
+
+
+class OpenGraphMIMEParser(BaseMIMEParser):
+ """
+ Open graph protocol is used on many web pages.
+
+
+
+
+ If the page is a video page both of the above tags will be present and
+ priority is given to video content.
+
+ see http://ogp.me
+ """
+ pattern = re.compile(r'.*$')
+
+ @staticmethod
+ def get_mimetype(url):
+ page = requests.get(url)
+ soup = BeautifulSoup(page.content, 'html.parser')
+ for og_type in ['video', 'image']:
+ prop = 'og:' + og_type + ':secure_url'
+ tag = soup.find('meta', attrs={'property': prop})
+ if not tag:
+ prop = 'og:' + og_type
+ tag = soup.find('meta', attrs={'property': prop})
+ if tag:
+ return BaseMIMEParser.get_mimetype(tag.get('content'))
+
+ return url, None
+
+
+class VideoTagMIMEParser(BaseMIMEParser):
+ """
+
+
+
+
+
+ """
+ pattern = re.compile(r'.*$')
+
+ @staticmethod
+ def get_mimetype(url):
+ page = requests.get(url)
+ soup = BeautifulSoup(page.content, 'html.parser')
+
+ # TODO: Handle pages with multiple videos
+ video = soup.find('video')
+ source = None
+ if video:
+ source = video.find('source', attr={'res': 'HD'})
+ source = source or video.find('source', attr={'type': 'video/mp4'})
+ source = source or video.find('source')
+ if source:
+ return source.get('src'), source.get('type')
+ else:
+ return url, None
+
+
+class GfycatMIMEParser(BaseMIMEParser):
+ """
+ Gfycat provides a primitive json api to generate image links. URLs can be
+ downloaded as either gif, mp4, webm, or mjpg. Mp4 was selected because it's
+ fast and works with VLC.
+
+ https://gfycat.com/api
+
+ https://gfycat.com/UntidyAcidicIberianemeraldlizard -->
+ https://giant.gfycat.com/UntidyAcidicIberianemeraldlizard.webm
+ """
+ pattern = re.compile(r'https?://(www\.)?gfycat\.com/[^.]+$')
+
+ @staticmethod
+ def get_mimetype(url):
+ identifier = url.split('/')[-1]
+ api_url = 'https://api.gfycat.com/v1/gfycats/{}'.format(identifier)
+ resp = requests.get(api_url)
+ image_url = resp.json()['gfyItem']['mp4Url']
+ return image_url, 'video/mp4'
+
+
+class YoutubeMIMEParser(BaseMIMEParser):
+ """
+ Youtube videos can be streamed with vlc or downloaded with youtube-dl.
+ Assign a custom mime-type so they can be referenced in mailcap.
+ """
+ pattern = re.compile(
+ r'(?:https?://)?(m\.)?(?:youtu\.be/|(?:www\.)?youtube\.com/watch'
+ r'(?:\.php)?\'?.*v=)([a-zA-Z0-9\-_]+)')
+
+ @staticmethod
+ def get_mimetype(url):
+ return url, 'video/x-youtube'
+
+
+class VimeoMIMEParser(BaseMIMEParser):
+ """
+ Vimeo videos can be streamed with vlc or downloaded with youtube-dl.
+ Assign a custom mime-type so they can be referenced in mailcap.
+ """
+ pattern = re.compile(r'https?://(www\.)?vimeo\.com/\d+$')
+
+ @staticmethod
+ def get_mimetype(url):
+ return url, 'video/x-youtube'
+
+
+class GifvMIMEParser(BaseMIMEParser):
+ """
+ Special case for .gifv, which is a custom video format for imgur serves
+ as html with a special frame. Note that attempting for download as
+ .webm also returns this html page. However, .mp4 appears to return the raw
+ video file.
+ """
+ pattern = re.compile(r'.*[.]gifv$')
+
+ @staticmethod
+ def get_mimetype(url):
+ modified_url = url[:-4] + 'mp4'
+ return modified_url, 'video/mp4'
+
+
+class RedditUploadsMIMEParser(BaseMIMEParser):
+ """
+ Reddit uploads do not have a file extension, but we can grab the mime-type
+ from the page header.
+ """
+ pattern = re.compile(r'https://i\.reddituploads\.com/.+$')
+
+ @staticmethod
+ def get_mimetype(url):
+ page = requests.head(url)
+ content_type = page.headers.get('Content-Type', '')
+ content_type = content_type.split(';')[0] # Strip out the encoding
+ return url, content_type
+
+
+class RedditVideoMIMEParser(BaseMIMEParser):
+ """
+ Reddit hosted videos/gifs.
+ Media uses MPEG-DASH format (.mpd)
+ """
+ pattern = re.compile(r'https://v\.redd\.it/.+$')
+
+ @staticmethod
+ def get_mimetype(url):
+ request_url = url + '/DASHPlaylist.mpd'
+ page = requests.get(request_url)
+ soup = BeautifulSoup(page.content, 'html.parser')
+ if not soup.find('representation', attrs={'mimetype': 'audio/mp4'}):
+ reps = soup.find_all('representation', attrs={'mimetype': 'video/mp4'})
+ reps = sorted(reps, reverse=True, key=lambda t: int(t.get('bandwidth')))
+ if reps:
+ url_suffix = reps[0].find('baseurl')
+ if url_suffix:
+ return url + '/' + url_suffix.text, 'video/mp4'
+
+ return request_url, 'video/x-youtube'
+
+
+class ImgurApiMIMEParser(BaseMIMEParser):
+ """
+ Imgur now provides a json API exposing its entire infrastructure. Each Imgur
+ page has an associated hash and can either contain an album, a gallery,
+ or single image.
+
+ The default client token for TTRV is shared among users and allows a maximum
+ global number of requests per day of 12,500. If we find that this limit is
+ not sufficient for all of ttrv's traffic, this method will be revisited.
+
+ Reference:
+ https://apidocs.imgur.com
+ """
+ CLIENT_ID = None
+ pattern = re.compile(
+ r'https?://(w+\.)?(m\.)?imgur\.com/'
+ r'((?Pa|album|gallery)/)?(?P[a-zA-Z0-9]+)$')
+
+ @classmethod
+ def get_mimetype(cls, url):
+
+ endpoint = 'https://api.imgur.com/3/{domain}/{page_hash}'
+ headers = {'authorization': 'Client-ID {0}'.format(cls.CLIENT_ID)}
+
+ m = cls.pattern.match(url)
+ page_hash = m.group('hash')
+
+ if m.group('domain') in ('a', 'album'):
+ domain = 'album'
+ else:
+ # This could be a gallery or a single image, but there doesn't
+ # seem to be a way to reliably distinguish between the two.
+ # Assume a gallery, which appears to be more common, and fallback
+ # to an image request upon failure.
+ domain = 'gallery'
+
+ if not cls.CLIENT_ID:
+ return cls.fallback(url, domain)
+
+ api_url = endpoint.format(domain=domain, page_hash=page_hash)
+ r = requests.get(api_url, headers=headers)
+
+ if domain == 'gallery' and r.status_code != 200:
+ # Not a gallery, try to download using the image endpoint
+ api_url = endpoint.format(domain='image', page_hash=page_hash)
+ r = requests.get(api_url, headers=headers)
+
+ if r.status_code != 200:
+ _logger.warning('Imgur API failure, status %s', r.status_code)
+ return cls.fallback(url, domain)
+
+ data = r.json().get('data')
+ if not data:
+ _logger.warning('Imgur API failure, resp %s', r.json())
+ return cls.fallback(url, domain)
+
+ if 'images' in data and len(data['images']) > 1:
+ # TODO: handle imgur albums with mixed content, i.e. jpeg and gifv
+ link = ' '.join([d['link'] for d in data['images'] if not d['animated']])
+ mime = 'image/x-imgur-album'
+ else:
+ data = data['images'][0] if 'images' in data else data
+ # this handles single image galleries
+
+ link = data['mp4'] if data['animated'] else data['link']
+ mime = 'video/mp4' if data['animated'] else data['type']
+
+ link = link.replace('http://', 'https://')
+ return link, mime
+
+ @classmethod
+ def fallback(cls, url, domain):
+ """
+ Attempt to use one of the scrapers if the API doesn't work
+ """
+ if domain == 'album':
+ # The old Imgur album scraper has stopped working and I haven't
+ # put in the effort to figure out why
+ return url, None
+ else:
+ return ImgurScrapeMIMEParser.get_mimetype(url)
+
+
+class ImgurScrapeMIMEParser(BaseMIMEParser):
+ """
+ The majority of imgur links don't point directly to the image, so we need
+ to open the provided url and scrape the page for the link.
+
+ Scrape the actual image url from an imgur landing page. Imgur intentionally
+ obscures this on most reddit links in order to draw more traffic for their
+ advertisements.
+
+ There are a couple of tags that supply the relevant info:
+
+
+
+ """
+ pattern = re.compile(r'https?://(w+\.)?(m\.)?imgur\.com/[^.]+$')
+
+ @staticmethod
+ def get_mimetype(url):
+ page = requests.get(url)
+ soup = BeautifulSoup(page.content, 'html.parser')
+ tag = soup.find('meta', attrs={'name': 'twitter:image'})
+ if tag:
+ url = tag.get('content')
+ if GifvMIMEParser.pattern.match(url):
+ return GifvMIMEParser.get_mimetype(url)
+ return BaseMIMEParser.get_mimetype(url)
+
+
+class InstagramMIMEParser(OpenGraphMIMEParser):
+ """
+ Instagram uses the Open Graph protocol
+ """
+ pattern = re.compile(r'https?://(www\.)?instagr((am\.com)|\.am)/p/[^.]+$')
+
+
+class StreamableMIMEParser(OpenGraphMIMEParser):
+ """
+ Streamable uses the Open Graph protocol
+ """
+ pattern = re.compile(r'https?://(www\.)?streamable\.com/[^.]+$')
+
+
+class LiveleakMIMEParser(BaseMIMEParser):
+ """
+ https://www.liveleak.com/view?i=12c_3456789
+
+
+
+
+ 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'})
+ source = source or vid.find('source')
+ if source:
+ urls.append((source.get('src'), source.get('type')))
+
+ # TODO: Handle pages with multiple videos
+ if urls:
+ return urls[0]
+
+ def filter_iframe(t):
+ return t.name == 'iframe' and 'youtube.com' in t['src']
+
+ iframe = soup.find_all(filter_iframe)
+ if iframe:
+ return YoutubeMIMEParser.get_mimetype(iframe[0]['src'].strip('/'))
+
+ return url, None
+
+
+class ClippitUserMIMEParser(BaseMIMEParser):
+ """
+ Clippit uses a video player container
+ """
+ pattern = re.compile(r'https?://(www\.)?clippituser\.tv/c/.+$')
+
+ @staticmethod
+ def get_mimetype(url):
+ page = requests.get(url)
+ soup = BeautifulSoup(page.content, 'html.parser')
+ tag = soup.find(id='player-container')
+ if tag:
+ quality = ['data-{}-file'.format(_) for _ in ['hd', 'sd']]
+ new_url = tag.get(quality[0])
+ if new_url:
+ return new_url, 'video/mp4'
+
+ return url, None
+
+
+class GifsMIMEParser(OpenGraphMIMEParser):
+ """
+ Gifs.com uses the Open Graph protocol
+ """
+ pattern = re.compile(r'https?://(www\.)?gifs\.com/gif/.+$')
+
+
+class GiphyMIMEParser(OpenGraphMIMEParser):
+ """
+ Giphy.com uses the Open Graph protocol
+ """
+ pattern = re.compile(r'https?://(www\.)?giphy\.com/gifs/.+$')
+
+
+class ImgflipMIMEParser(OpenGraphMIMEParser):
+ """
+ imgflip.com uses the Open Graph protocol
+ """
+ pattern = re.compile(r'https?://(www\.)?imgflip\.com/i/.+$')
+
+
+class LivememeMIMEParser(OpenGraphMIMEParser):
+ """
+ livememe.com uses the Open Graph protocol
+ """
+ pattern = re.compile(r'https?://(www\.)?livememe\.com/[^.]+$')
+
+
+class MakeamemeMIMEParser(OpenGraphMIMEParser):
+ """
+ makeameme.com uses the Open Graph protocol
+ """
+ pattern = re.compile(r'https?://(www\.)?makeameme\.org/meme/.+$')
+
+
+class FlickrMIMEParser(OpenGraphMIMEParser):
+ """
+ Flickr uses the Open Graph protocol
+ """
+ # TODO: handle albums/photosets (https://www.flickr.com/services/api)
+ pattern = re.compile(r'https?://(www\.)?flickr\.com/photos/[^/]+/[^/]+/?$')
+
+
+class StreamjaMIMEParser(VideoTagMIMEParser):
+ """
+ Embedded HTML5 video element
+ """
+ pattern = re.compile(r'https?://(www\.)?streamja\.com/[^/]+/?$')
+
+
+class WorldStarHipHopMIMEParser(BaseMIMEParser):
+ """
+
+
+
+
+ Sometimes only one video source is available
+ """
+ pattern = re.compile(r'https?://((www|m)\.)?worldstarhiphop\.com/videos/video.php\?v=\w+$')
+
+ @staticmethod
+ def get_mimetype(url):
+ page = requests.get(url)
+ soup = BeautifulSoup(page.content, 'html.parser')
+
+ def filter_source(t):
+ return t.name == 'source' and t['src'] and t['type'] == 'video/mp4'
+
+ source = soup.find_all(filter_source)
+ if source:
+ return source[0]['src'], 'video/mp4'
+
+ def filter_iframe(t):
+ return t.name == 'iframe' and 'youtube.com' in t['src']
+
+ iframe = soup.find_all(filter_iframe)
+ if iframe:
+ return YoutubeMIMEParser.get_mimetype(iframe[0]['src'])
+
+ return url, None
+
+
+class RedGifsParser(BaseMIMEParser):
+ """
+ Extract from application/ld+json
+ """
+ pattern = re.compile(r'https?://(www\.)?redgifs\.com/watch/.+$')
+
+ @staticmethod
+ def get_mimetype(url):
+ page = requests.get(url)
+ soup = BeautifulSoup(page.content, 'html.parser')
+ tag = soup.find('script', attrs={'type': 'application/ld+json'})
+ if tag:
+ ld_json = json.loads(tag.text)
+ return ld_json['video']['contentUrl'], 'video/mp4'
+
+ return url, None
+
+
+# Parsers should be listed in the order they will be checked
+parsers = [
+ StreamjaMIMEParser,
+ ClippitUserMIMEParser,
+ StreamableMIMEParser,
+ InstagramMIMEParser,
+ GfycatMIMEParser,
+ ImgurApiMIMEParser,
+ RedditUploadsMIMEParser,
+ RedditVideoMIMEParser,
+ YoutubeMIMEParser,
+ VimeoMIMEParser,
+ LiveleakMIMEParser,
+ FlickrMIMEParser,
+ GifsMIMEParser,
+ GiphyMIMEParser,
+ ImgflipMIMEParser,
+ LivememeMIMEParser,
+ MakeamemeMIMEParser,
+ WorldStarHipHopMIMEParser,
+ GifvMIMEParser,
+ RedGifsParser,
+ BaseMIMEParser]
diff --git a/ttrv/oauth.py b/ttrv/oauth.py
new file mode 100644
index 0000000..fcae5d4
--- /dev/null
+++ b/ttrv/oauth.py
@@ -0,0 +1,248 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+import os
+import time
+import uuid
+import string
+import codecs
+import logging
+import threading
+
+# pylint: disable=import-error
+from six.moves.urllib.parse import urlparse, parse_qs
+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__)
+
+INDEX = os.path.join(TEMPLATES, 'index.html')
+
+
+class OAuthHTTPServer(HTTPServer):
+
+ def handle_error(self, request, client_address):
+ """
+ The default HTTPServer's error handler prints the request traceback
+ to stdout, which breaks the curses display.
+
+ Override it to log to a file instead.
+ """
+ _logger.exception('Error processing request in OAuth HTTP Server')
+
+
+class OAuthHandler(BaseHTTPRequestHandler):
+
+ # params are stored as a global because we don't have control over what
+ # gets passed into the handler __init__. These will be accessed by the
+ # OAuthHelper class.
+ params = {'state': None, 'code': None, 'error': None}
+ shutdown_on_request = True
+
+ def do_GET(self):
+ """
+ Accepts GET requests to http://localhost:6500/, and stores the query
+ params in the global dict. If shutdown_on_request is true, stop the
+ server after the first successful request.
+
+ The http request may contain the following query params:
+ - state : unique identifier, should match what we passed to reddit
+ - code : code that can be exchanged for a refresh token
+ - error : if provided, the OAuth error that occurred
+ """
+
+ parsed_path = urlparse(self.path)
+ if parsed_path.path != '/':
+ self.send_error(404)
+
+ qs = parse_qs(parsed_path.query)
+ self.params['state'] = qs['state'][0] if 'state' in qs else None
+ self.params['code'] = qs['code'][0] if 'code' in qs else None
+ self.params['error'] = qs['error'][0] if 'error' in qs else None
+
+ body = self.build_body()
+
+ # send_response also sets the Server and Date headers
+ self.send_response(200)
+ self.send_header('Content-Type', 'text/html; charset=UTF-8')
+ self.send_header('Content-Length', len(body))
+ self.end_headers()
+
+ self.wfile.write(body)
+
+ if self.shutdown_on_request:
+ # Shutdown the server after serving the request
+ # http://stackoverflow.com/a/22533929
+ thread = threading.Thread(target=self.server.shutdown)
+ thread.daemon = True
+ thread.start()
+
+ def log_message(self, fmt, *args):
+ """
+ Redirect logging to our own handler instead of stdout
+ """
+ _logger.debug(fmt, *args)
+
+ def build_body(self, template_file=INDEX):
+ """
+ Params:
+ template_file (text): Path to an index.html template
+
+ Returns:
+ body (bytes): THe utf-8 encoded document body
+ """
+
+ if self.params['error'] == 'access_denied':
+ message = docs.OAUTH_ACCESS_DENIED
+ elif self.params['error'] is not None:
+ message = docs.OAUTH_ERROR.format(error=self.params['error'])
+ elif self.params['state'] is None or self.params['code'] is None:
+ message = docs.OAUTH_INVALID
+ else:
+ message = docs.OAUTH_SUCCESS
+
+ with codecs.open(template_file, 'r', 'utf-8') as fp:
+ index_text = fp.read()
+
+ body = string.Template(index_text).substitute(message=message)
+ body = codecs.encode(body, 'utf-8')
+ return body
+
+
+class OAuthHelper(object):
+
+ params = OAuthHandler.params
+
+ def __init__(self, reddit, term, config):
+
+ self.term = term
+ self.reddit = reddit
+ self.config = config
+
+ # Wait to initialize the server, we don't want to reserve the port
+ # unless we know that the server needs to be used.
+ self.server = None
+
+ self.reddit.set_oauth_app_info(
+ self.config['oauth_client_id'],
+ self.config['oauth_client_secret'],
+ self.config['oauth_redirect_uri'])
+
+ # Reddit's mobile website works better on terminal browsers
+ if not self.term.display:
+ if '.compact' not in self.reddit.config.API_PATHS['authorize']:
+ self.reddit.config.API_PATHS['authorize'] += '.compact'
+
+ def authorize(self, autologin=False):
+
+ self.params.update(state=None, code=None, error=None)
+
+ # If we already have a token, request new access credentials
+ if self.config.refresh_token:
+ with self.term.loader('Logging in'):
+ 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')
+
+ else:
+ if not autologin:
+ # Only show the welcome message if explicitly logging
+ # in, not when TTRV first launches.
+ message = 'Welcome {}!'.format(self.reddit.user.name)
+ self.term.show_notification(message)
+
+ return
+
+ state = uuid.uuid4().hex
+ authorize_url = self.reddit.get_authorize_url(
+ state, scope=self.config['oauth_scope'], refreshable=True)
+
+ if self.server is None:
+ address = ('', self.config['oauth_redirect_port'])
+ self.server = OAuthHTTPServer(address, OAuthHandler)
+
+ if self.term.display:
+ # Open a background browser (e.g. firefox) which is non-blocking.
+ # The server will block until it responds to its first request,
+ # at which point we can check the callback params.
+ OAuthHandler.shutdown_on_request = True
+ with self.term.loader('Opening browser for authorization'):
+ self.term.open_browser(authorize_url)
+ self.server.serve_forever()
+ if self.term.loader.exception:
+ # Don't need to call server.shutdown() because serve_forever()
+ # is wrapped in a try-finally that doees it for us.
+ return
+ else:
+ # Open the terminal webbrowser in a background thread and wait
+ # while for the user to close the process. Once the process is
+ # closed, the iloop is stopped and we can check if the user has
+ # hit the callback URL.
+ OAuthHandler.shutdown_on_request = False
+ with self.term.loader('Redirecting to reddit', delay=0):
+ # This load message exists to provide user feedback
+ time.sleep(1)
+
+ thread = threading.Thread(target=self.server.serve_forever)
+ thread.daemon = True
+ thread.start()
+ try:
+ self.term.open_browser(authorize_url)
+ except Exception as e:
+ # If an exception is raised it will be seen by the thread
+ # so we don't need to explicitly shutdown() the server
+ _logger.exception(e)
+ self.term.show_notification('Browser Error', style='Error')
+ else:
+ self.server.shutdown()
+ finally:
+ thread.join()
+
+ if self.params['error'] == 'access_denied':
+ self.term.show_notification('Denied access', style='Error')
+ return
+ elif self.params['error']:
+ self.term.show_notification('Authentication error', style='Error')
+ return
+ elif self.params['state'] is None:
+ # Something went wrong but it's not clear what happened
+ return
+ elif self.params['state'] != state:
+ self.term.show_notification('UUID mismatch', style='Error')
+ return
+
+ with self.term.loader('Logging in'):
+ info = self.reddit.get_access_information(self.params['code'])
+ if self.term.loader.exception:
+ return
+
+ message = 'Welcome {}!'.format(self.reddit.user.name)
+ self.term.show_notification(message)
+
+ self.config.refresh_token = info['refresh_token']
+ if self.config['persistent']:
+ self.config.save_refresh_token()
+
+ def clear_oauth_data(self):
+ self.reddit.clear_authentication()
+ self.config.delete_refresh_token()
diff --git a/ttrv/objects.py b/ttrv/objects.py
new file mode 100644
index 0000000..d002cda
--- /dev/null
+++ b/ttrv/objects.py
@@ -0,0 +1,709 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+import re
+import os
+import sys
+import time
+import signal
+import inspect
+import weakref
+import logging
+import threading
+import webbrowser
+import curses
+import curses.ascii
+from contextlib import contextmanager
+
+import six
+import requests
+
+from . import exceptions
+from .packages import praw
+
+
+_logger = logging.getLogger(__name__)
+
+
+def patch_webbrowser():
+ """
+ Some custom patches on top of the python webbrowser module to fix
+ user reported bugs and limitations of the module.
+ """
+
+ # https://bugs.python.org/issue31014
+ # https://github.com/michael-lazar/ttrv/issues/588
+ def register_patch(name, klass, instance=None, update_tryorder=None, preferred=False):
+ """
+ Wrapper around webbrowser.register() that detects if the function was
+ invoked with the legacy function signature. If so, the signature is
+ fixed before passing it along to the underlying function.
+
+ Examples:
+ register(name, klass, instance, -1)
+ register(name, klass, instance, update_tryorder=-1)
+ register(name, klass, instance, preferred=True)
+ """
+ if update_tryorder is not None:
+ preferred = (update_tryorder == -1)
+ return webbrowser._register(name, klass, instance, preferred=preferred)
+
+ if sys.version_info[:2] >= (3, 7):
+ webbrowser._register = webbrowser.register
+ webbrowser.register = register_patch
+
+ # Add support for browsers that aren't defined in the python standard library
+ webbrowser.register('surf', None, webbrowser.BackgroundBrowser('surf'))
+ webbrowser.register('vimb', None, webbrowser.BackgroundBrowser('vimb'))
+ webbrowser.register('qutebrowser', None, webbrowser.BackgroundBrowser('qutebrowser'))
+
+ # Fix the opera browser, see https://github.com/michael-lazar/ttrv/issues/476.
+ # By default, opera will open a new tab in the current window, which is
+ # what we want to do anyway.
+ webbrowser.register('opera', None, webbrowser.BackgroundBrowser('opera'))
+
+ # https://bugs.python.org/issue31348
+ # Use MacOS actionscript when opening the program defined in by $BROWSER
+ if sys.platform == 'darwin' and 'BROWSER' in os.environ:
+ _userchoices = os.environ["BROWSER"].split(os.pathsep)
+ for cmdline in reversed(_userchoices):
+ if cmdline in ('safari', 'firefox', 'chrome', 'default'):
+ browser = webbrowser.MacOSXOSAScript(cmdline)
+ webbrowser.register(cmdline, None, browser, update_tryorder=-1)
+
+
+@contextmanager
+def curses_session():
+ """
+ Setup terminal and initialize curses. Most of this copied from
+ curses.wrapper in order to convert the wrapper into a context manager.
+ """
+
+ try:
+ # Curses must wait for some time after the Escape key is pressed to
+ # check if it is the beginning of an escape sequence indicating a
+ # special key. The default wait time is 1 second, which means that
+ # http://stackoverflow.com/questions/27372068
+ os.environ['ESCDELAY'] = '25'
+
+ # Initialize curses
+ stdscr = curses.initscr()
+
+ # Turn off echoing of keys, and enter cbreak mode, where no buffering
+ # is performed on keyboard input
+ curses.noecho()
+ curses.cbreak()
+
+ # In keypad mode, escape sequences for special keys (like the cursor
+ # keys) will be interpreted and a special value like curses.KEY_LEFT
+ # will be returned
+ stdscr.keypad(1)
+
+ # Start color, too. Harmless if the terminal doesn't have color; user
+ # can test with has_color() later on. The try/catch works around a
+ # minor bit of over-conscientiousness in the curses module -- the error
+ # return from C start_color() is ignorable.
+ try:
+ curses.start_color()
+ curses.use_default_colors()
+ except:
+ _logger.warning('Curses failed to initialize color support')
+
+ # Hide the blinking cursor
+ try:
+ curses.curs_set(0)
+ except:
+ _logger.warning('Curses failed to initialize the cursor mode')
+
+ yield stdscr
+
+ finally:
+ if 'stdscr' in locals():
+ stdscr.keypad(0)
+ curses.echo()
+ curses.nocbreak()
+ curses.endwin()
+
+
+class LoadScreen(object):
+ """
+ Display a loading dialog while waiting for a blocking action to complete.
+
+ This class spins off a separate thread to animate the loading screen in the
+ background. The loading thread also takes control of stdscr.getch(). If
+ an exception occurs in the main thread while the loader is active, the
+ exception will be caught, attached to the loader object, and displayed as
+ a notification. The attached exception can be used to trigger context
+ sensitive actions. For example, if the connection hangs while opening a
+ submission, the user may press ctrl-c to raise a KeyboardInterrupt. In this
+ case we would *not* want to refresh the current page.
+
+ >>> with self.terminal.loader(...) as loader:
+ >>> # Perform a blocking request to load content
+ >>> blocking_request(...)
+ >>>
+ >>> if loader.exception is None:
+ >>> # Only run this if the load was successful
+ >>> self.refresh_content()
+
+ When a loader is nested inside of itself, the outermost loader takes
+ priority and all of the nested loaders become no-ops. Call arguments given
+ to nested loaders will be ignored, and errors will propagate to the parent.
+
+ >>> with self.terminal.loader(...) as loader:
+ >>>
+ >>> # Additional loaders will be ignored
+ >>> with self.terminal.loader(...):
+ >>> raise KeyboardInterrupt()
+ >>>
+ >>> # This code will not be executed because the inner loader doesn't
+ >>> # catch the exception
+ >>> assert False
+ >>>
+ >>> # The exception is finally caught by the outer loader
+ >>> assert isinstance(terminal.loader.exception, KeyboardInterrupt)
+ """
+
+ EXCEPTION_MESSAGES = [
+ (exceptions.TTRVError, '{0}'),
+ (praw.errors.OAuthException, 'OAuth Error'),
+ (praw.errors.OAuthScopeRequired, 'Not logged in'),
+ (praw.errors.LoginRequired, 'Not logged in'),
+ (praw.errors.InvalidCaptcha, 'Error, captcha required'),
+ (praw.errors.InvalidSubreddit, '{0.args[0]}'),
+ (praw.errors.PRAWException, '{0.__class__.__name__}'),
+ (requests.exceptions.Timeout, 'HTTP request timed out'),
+ (requests.exceptions.RequestException, '{0.__class__.__name__}'),
+ ]
+
+ def __init__(self, terminal):
+
+ self.exception = None
+ self.catch_exception = None
+ self.depth = 0
+ self._terminal = weakref.proxy(terminal)
+ self._args = None
+ self._animator = None
+ self._is_running = None
+
+ def __call__(
+ self,
+ message='Downloading',
+ trail='...',
+ delay=0.5,
+ interval=0.4,
+ catch_exception=True):
+ """
+ Params:
+ delay (float): Length of time that the loader will wait before
+ printing on the screen. Used to prevent flicker on pages that
+ load very fast.
+ interval (float): Length of time between each animation frame.
+ message (str): Message to display
+ trail (str): Trail of characters that will be animated by the
+ loading screen.
+ catch_exception (bool): If an exception occurs while the loader is
+ active, this flag determines whether it is caught or allowed to
+ bubble up.
+ """
+
+ if self.depth > 0:
+ return self
+
+ self.exception = None
+ self.catch_exception = catch_exception
+ self._args = (delay, interval, message, trail)
+ return self
+
+ def __enter__(self):
+
+ self.depth += 1
+ if self.depth > 1:
+ return self
+
+ self._animator = threading.Thread(target=self.animate, args=self._args)
+ self._animator.daemon = True
+ self._is_running = True
+ self._animator.start()
+ return self
+
+ def __exit__(self, exc_type, e, exc_tb):
+
+ self.depth -= 1
+ if self.depth > 0:
+ return
+
+ self._is_running = False
+ self._animator.join()
+
+ if e is None or not self.catch_exception:
+ # Skip exception handling
+ return
+
+ self.exception = e
+ exc_name = type(e).__name__
+ _logger.info('Loader caught: %s - %s', exc_name, e)
+
+ if isinstance(e, KeyboardInterrupt):
+ # Don't need to print anything for this one, just swallow it
+ return True
+
+ for e_type, message in self.EXCEPTION_MESSAGES:
+ # Some exceptions we want to swallow and display a notification
+ if isinstance(e, e_type):
+ msg = message.format(e)
+ self._terminal.show_notification(msg, style='Error')
+ return True
+
+ def animate(self, delay, interval, message, trail):
+
+ # The animation starts with a configurable delay before drawing on the
+ # screen. This is to prevent very short loading sections from
+ # flickering on the screen before immediately disappearing.
+ with self._terminal.no_delay():
+ start = time.time()
+ while (time.time() - start) < delay:
+ # Pressing escape triggers a keyboard interrupt
+ if self._terminal.getch() == self._terminal.ESCAPE:
+ os.kill(os.getpid(), signal.SIGINT)
+ self._is_running = False
+
+ if not self._is_running:
+ return
+ time.sleep(0.01)
+
+ # Build the notification window. Note that we need to use
+ # curses.newwin() instead of stdscr.derwin() so the text below the
+ # notification window does not got erased when we cover it up.
+ message_len = len(message) + len(trail)
+ n_rows, n_cols = self._terminal.stdscr.getmaxyx()
+ v_offset, h_offset = self._terminal.stdscr.getbegyx()
+ s_row = (n_rows - 3) // 2 + v_offset
+ s_col = (n_cols - message_len - 1) // 2 + h_offset
+ window = curses.newwin(3, message_len + 2, s_row, s_col)
+ window.bkgd(str(' '), self._terminal.attr('NoticeLoading'))
+
+ # Animate the loading prompt until the stopping condition is triggered
+ # when the context manager exits.
+ with self._terminal.no_delay():
+ while True:
+ for i in range(len(trail) + 1):
+ if not self._is_running:
+ window.erase()
+ del window
+ self._terminal.stdscr.touchwin()
+ self._terminal.stdscr.refresh()
+ return
+
+ window.erase()
+ window.border()
+ self._terminal.add_line(window, message + trail[:i], 1, 1)
+ window.refresh()
+
+ # Break up the designated sleep interval into smaller
+ # chunks so we can more responsively check for interrupts.
+ for _ in range(int(interval / 0.01)):
+ # Pressing escape triggers a keyboard interrupt
+ if self._terminal.getch() == self._terminal.ESCAPE:
+ os.kill(os.getpid(), signal.SIGINT)
+ self._is_running = False
+ break
+ time.sleep(0.01)
+
+
+class Navigator(object):
+ """
+ Handles the math behind cursor movement and screen paging.
+
+ This class determines how cursor movements effect the currently displayed
+ page. For example, if scrolling down the page, items are drawn from the
+ bottom up. This ensures that the item at the very bottom of the screen
+ (the one selected by cursor) will be fully drawn and not cut off. Likewise,
+ when scrolling up the page, items are drawn from the top down. If the
+ cursor is moved around without hitting the top or bottom of the screen, the
+ current mode is preserved.
+ """
+
+ def __init__(
+ self,
+ valid_page_cb,
+ page_index=0,
+ cursor_index=0,
+ inverted=False,
+ top_item_height=None):
+ """
+ Params:
+ valid_page_callback (func): This function, usually `Content.get`,
+ takes a page index and raises an IndexError if that index falls
+ out of bounds. This is used to determine the upper and lower
+ bounds of the page, i.e. when to stop scrolling.
+ page_index (int): Initial page index.
+ cursor_index (int): Initial cursor index, relative to the page.
+ inverted (bool): Whether the page scrolling is reversed of not.
+ normal - The page is drawn from the top of the screen,
+ starting with the page index, down to the bottom of
+ the screen.
+ inverted - The page is drawn from the bottom of the screen,
+ starting with the page index, up to the top of the
+ screen.
+ top_item_height (int): If this is set to a non-null value
+ The number of columns that the top-most item
+ should utilize if non-inverted. This is used for a special mode
+ where all items are drawn non-inverted except for the top one.
+ """
+
+ self.page_index = page_index
+ self.cursor_index = cursor_index
+ self.inverted = inverted
+ self.top_item_height = top_item_height
+ self._page_cb = valid_page_cb
+
+ @property
+ def step(self):
+ return 1 if not self.inverted else -1
+
+ @property
+ def position(self):
+ return self.page_index, self.cursor_index, self.inverted
+
+ @property
+ def absolute_index(self):
+ """
+ Return the index of the currently selected item.
+ """
+
+ return self.page_index + (self.step * self.cursor_index)
+
+ def move(self, direction, n_windows):
+ """
+ Move the cursor up or down by the given increment.
+
+ Params:
+ direction (int): `1` will move the cursor down one item and `-1`
+ will move the cursor up one item.
+ n_windows (int): The number of items that are currently being drawn
+ on the screen.
+
+ Returns:
+ valid (bool): Indicates whether or not the attempted cursor move is
+ allowed. E.g. When the cursor is on the last comment,
+ attempting to scroll down any further would not be valid.
+ redraw (bool): Indicates whether or not the screen needs to be
+ redrawn.
+ """
+
+ assert direction in (-1, 1)
+
+ valid, redraw = True, False
+ forward = ((direction * self.step) > 0)
+
+ if forward:
+ if self.page_index < 0:
+ if self._is_valid(0):
+ # Special case - advance the page index if less than zero
+ self.page_index = 0
+ self.cursor_index = 0
+ redraw = True
+ else:
+ valid = False
+ else:
+ self.cursor_index += 1
+ if not self._is_valid(self.absolute_index):
+ # Move would take us out of bounds
+ self.cursor_index -= 1
+ valid = False
+ elif self.cursor_index >= (n_windows - 1):
+ # Flip the orientation and reset the cursor
+ self.flip(self.cursor_index)
+ self.cursor_index = 0
+ self.top_item_height = None
+ redraw = True
+ else:
+ if self.cursor_index > 0:
+ self.cursor_index -= 1
+ if self.top_item_height and self.cursor_index == 0:
+ # Selecting the partially displayed item
+ self.top_item_height = None
+ redraw = True
+ else:
+ self.page_index -= self.step
+ if self._is_valid(self.absolute_index):
+ # We have reached the beginning of the page - move the
+ # index
+ self.top_item_height = None
+ redraw = True
+ else:
+ self.page_index += self.step
+ valid = False # Revert
+
+ return valid, redraw
+
+ def move_page(self, direction, n_windows):
+ """
+ Move the page down (positive direction) or up (negative direction).
+
+ Paging down:
+ The post on the bottom of the page becomes the post at the top of
+ the page and the cursor is moved to the top.
+ Paging up:
+ The post at the top of the page becomes the post at the bottom of
+ the page and the cursor is moved to the bottom.
+ """
+
+ assert direction in (-1, 1)
+ assert n_windows >= 0
+
+ # top of subreddit/submission page or only one
+ # submission/reply on the screen: act as normal move
+ if (self.absolute_index < 0) | (n_windows == 0):
+ valid, redraw = self.move(direction, n_windows)
+ else:
+ # first page
+ if self.absolute_index < n_windows and direction < 0:
+ self.page_index = -1
+ self.cursor_index = 0
+ self.inverted = False
+
+ # not submission mode: starting index is 0
+ if not self._is_valid(self.absolute_index):
+ self.page_index = 0
+ valid = True
+ else:
+ # flip to the direction of movement
+ if ((direction > 0) & (self.inverted is True)) \
+ | ((direction < 0) & (self.inverted is False)):
+ self.page_index += (self.step * (n_windows - 1))
+ self.inverted = not self.inverted
+ self.cursor_index \
+ = (n_windows - (direction < 0)) - self.cursor_index
+
+ valid = False
+ adj = 0
+ # check if reached the bottom
+ while not valid:
+ n_move = n_windows - adj
+ if n_move == 0:
+ break
+
+ self.page_index += n_move * direction
+ valid = self._is_valid(self.absolute_index)
+ if not valid:
+ self.page_index -= n_move * direction
+ adj += 1
+
+ redraw = True
+
+ return valid, redraw
+
+ def flip(self, n_windows):
+ """
+ Flip the orientation of the page.
+ """
+
+ assert n_windows >= 0
+ self.page_index += (self.step * n_windows)
+ self.cursor_index = n_windows
+ self.inverted = not self.inverted
+ self.top_item_height = None
+
+ def _is_valid(self, page_index):
+ """
+ Check if a page index will cause entries to fall outside valid range.
+ """
+
+ try:
+ self._page_cb(page_index)
+ except IndexError:
+ return False
+ else:
+ return True
+
+
+class Controller(object):
+ """
+ Event handler for triggering functions with curses keypresses.
+
+ Register a keystroke to a class method using the @register decorator.
+ >>> @Controller.register('a', 'A')
+ >>> def func(self, *args)
+ >>> ...
+
+ Register a KeyBinding that can be defined later by the config file
+ >>> @Controller.register(Command("UPVOTE"))
+ >>> def upvote(self, *args)
+ >> ...
+
+ Bind the controller to a class instance and trigger a key. Additional
+ arguments will be passed to the function.
+ >>> controller = Controller(self)
+ >>> controller.trigger('a', *args)
+ """
+
+ character_map = {}
+
+ def __init__(self, instance, keymap=None):
+
+ self.instance = instance
+ # Build a list of parent controllers that follow the object's MRO
+ # to check if any parent controllers have registered the keypress
+ self.parents = inspect.getmro(type(self))[:-1]
+ # Keep track of last key press for doubles like `gg`
+ self.last_char = None
+
+ if not keymap:
+ return
+
+ # Go through the controller and all of it's parents and look for
+ # Command objects in the character map. Use the keymap the lookup the
+ # keys associated with those command objects and add them to the
+ # character map.
+ for controller in self.parents:
+ for command, func in controller.character_map.copy().items():
+ if isinstance(command, Command):
+ for key in keymap.get(command):
+ val = keymap.parse(key)
+ # If a double key press is defined, the first half
+ # must be unbound
+ if isinstance(val, tuple):
+ if controller.character_map.get(val[0]) is not None:
+ raise exceptions.ConfigError(
+ "Invalid configuration! `%s` is bound to "
+ "duplicate commands in the "
+ "%s" % (key, controller.__name__))
+ # Mark the first half of the double with None so
+ # that no other command can use it
+ controller.character_map[val[0]] = None
+
+ # Check if the key is already programmed to trigger a
+ # different function.
+ if controller.character_map.get(val, func) != func:
+ raise exceptions.ConfigError(
+ "Invalid configuration! `%s` is bound to "
+ "duplicate commands in the "
+ "%s" % (key, controller.__name__))
+ controller.character_map[val] = func
+
+ def trigger(self, char, *args, **kwargs):
+
+ if isinstance(char, six.string_types) and len(char) == 1:
+ char = ord(char)
+
+ func = None
+ # Check if the controller (or any of the controller's parents) have
+ # registered a function to the given key
+ for controller in self.parents:
+ func = controller.character_map.get((self.last_char, char))
+ if func:
+ break
+ func = controller.character_map.get(char)
+ if func:
+ break
+
+ if func:
+ self.last_char = None
+ return func(self.instance, *args, **kwargs)
+ else:
+ self.last_char = char
+ return None
+
+ @classmethod
+ def register(cls, *chars):
+ def inner(f):
+ for char in chars:
+ if isinstance(char, six.string_types) and len(char) == 1:
+ cls.character_map[ord(char)] = f
+ else:
+ cls.character_map[char] = f
+ return f
+ return inner
+
+
+class Command(object):
+ """
+ Minimal class that should be used to wrap abstract commands that may be
+ implemented as one or more physical keystrokes.
+
+ E.g. Command("REFRESH") can be represented by the KeyMap to be triggered
+ by either `r` or `F5`
+ """
+
+ def __init__(self, val):
+ self.val = val.upper()
+
+ def __repr__(self):
+ return 'Command(%s)' % self.val
+
+ def __eq__(self, other):
+ return repr(self) == repr(other)
+
+ def __ne__(self, other):
+ return not self == other
+
+ def __hash__(self):
+ return hash(repr(self))
+
+
+class KeyMap(object):
+ """
+ Mapping between commands and the keys that they represent.
+ """
+
+ def __init__(self, bindings):
+ self._keymap = None
+ self.set_bindings(bindings)
+
+ def set_bindings(self, bindings):
+ new_keymap = {}
+ for command, keys in bindings.items():
+ if not isinstance(command, Command):
+ command = Command(command)
+ new_keymap[command] = keys
+
+ if not self._keymap:
+ self._keymap = new_keymap
+ else:
+ self._keymap.update(new_keymap)
+
+ def get(self, command):
+ if not isinstance(command, Command):
+ command = Command(command)
+ try:
+ return self._keymap[command]
+ except KeyError:
+ raise exceptions.ConfigError('Invalid configuration! `%s` key is '
+ 'undefined' % command.val)
+
+ @classmethod
+ def parse(cls, key):
+ """
+ Parse a key represented by a string and return its character code.
+ """
+
+ try:
+ if isinstance(key, int):
+ return key
+ elif re.match('[<]KEY_.*[>]', key):
+ # Curses control character
+ return getattr(curses, key[1:-1])
+ elif re.match('[<].*[>]', key):
+ # Ascii control character
+ return getattr(curses.ascii, key[1:-1])
+ elif key.startswith('0x'):
+ # Ascii hex code
+ return int(key, 16)
+ elif len(key) == 2:
+ # Double presses
+ return tuple(cls.parse(k) for k in key)
+ else:
+ # Ascii character
+ code = ord(key)
+ if 0 <= code <= 255:
+ return code
+ # Python 3.3 has a curses.get_wch() function that we can use
+ # for unicode keys, but Python 2.7 is limited to ascii.
+ raise exceptions.ConfigError('Invalid configuration! `%s` is '
+ 'not in the ascii range' % key)
+
+ except (AttributeError, ValueError, TypeError):
+ raise exceptions.ConfigError('Invalid configuration! "%s" is not a '
+ 'valid key' % key)
diff --git a/ttrv/packages/__init__.py b/ttrv/packages/__init__.py
new file mode 100644
index 0000000..51daec8
--- /dev/null
+++ b/ttrv/packages/__init__.py
@@ -0,0 +1,26 @@
+"""
+This stub allows the user to fallback to their system installation of
+praw if the bundled package is missing. This technique was inspired by the
+requests library and how it handles dependencies.
+
+Reference:
+ https://github.com/kennethreitz/requests/blob/master/requests/packages/__init__.py
+"""
+from __future__ import absolute_import
+
+import sys
+
+__praw_hash__ = '1656ec224e574eed9cda4efcb497825d54b4d926'
+__praw_bundled__ = True
+
+
+try:
+ from . import praw
+except ImportError:
+ import praw
+
+ if not praw.__version__.startswith('3.'):
+ raise RuntimeError('Invalid PRAW version ({0}) detected, '
+ 'ttrv requires PRAW version 3'.format(praw.__version__))
+ sys.modules['%s.praw' % __name__] = praw
+ __praw_bundled__ = False
diff --git a/ttrv/packages/praw/__init__.py b/ttrv/packages/praw/__init__.py
new file mode 100644
index 0000000..c26c04a
--- /dev/null
+++ b/ttrv/packages/praw/__init__.py
@@ -0,0 +1,2813 @@
+# This file is part of PRAW.
+#
+# PRAW is free software: you can redistribute it and/or modify it under the
+# terms of the GNU General Public License as published by the Free Software
+# Foundation, either version 3 of the License, or (at your option) any later
+# version.
+#
+# PRAW is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# PRAW. If not, see .
+
+"""
+Python Reddit API Wrapper.
+
+PRAW, an acronym for "Python Reddit API Wrapper", is a python package that
+allows for simple access to reddit's API. PRAW aims to be as easy to use as
+possible and is designed to follow all of reddit's API rules. You have to give
+a useragent, everything else is handled by PRAW so you needn't worry about
+violating them.
+
+More information about PRAW can be found at https://github.com/praw-dev/praw
+"""
+
+from __future__ import print_function, unicode_literals
+
+import json
+import os
+import platform
+import re
+import six
+import sys
+from . import decorators, errors
+from .handlers import DefaultHandler
+from .helpers import chunk_sequence, normalize_url
+from .internal import (_image_type, _prepare_request,
+ _raise_redirect_exceptions,
+ _raise_response_exceptions,
+ _to_reddit_list, _warn_pyopenssl)
+from .settings import CONFIG
+from requests import Session
+from requests.compat import urljoin
+from requests.utils import to_native_string
+from requests import Request
+# pylint: disable=F0401
+from six.moves import html_entities, http_cookiejar
+from six.moves.urllib.parse import parse_qs, urlparse, urlunparse
+# pylint: enable=F0401
+from warnings import warn_explicit
+
+
+__version__ = '3.6.1'
+
+
+class Config(object): # pylint: disable=R0903
+ """A class containing the configuration for a reddit site."""
+
+ API_PATHS = {'accept_mod_invite': 'api/accept_moderator_invite',
+ 'access_token_url': 'api/v1/access_token/',
+ 'approve': 'api/approve/',
+ 'authorize': 'api/v1/authorize/',
+ 'banned': 'r/{subreddit}/about/banned/',
+ 'blocked': 'prefs/blocked/',
+ 'by_id': 'by_id/',
+ 'captcha': 'captcha/',
+ 'clearflairtemplates': 'api/clearflairtemplates/',
+ 'collapse_message': 'api/collapse_message/',
+ 'comment': 'api/comment/',
+ 'comment_replies': 'message/comments/',
+ 'comments': 'comments/',
+ 'compose': 'api/compose/',
+ 'contest_mode': 'api/set_contest_mode/',
+ 'contributors': 'r/{subreddit}/about/contributors/',
+ 'controversial': 'controversial/',
+ 'default_subreddits': 'subreddits/default/',
+ 'del': 'api/del/',
+ 'deleteflair': 'api/deleteflair',
+ 'delete_redditor': 'api/delete_user',
+ 'delete_sr_header': 'r/{subreddit}/api/delete_sr_header',
+ 'delete_sr_image': 'r/{subreddit}/api/delete_sr_img',
+ 'distinguish': 'api/distinguish/',
+ 'domain': 'domain/{domain}/',
+ 'duplicates': 'duplicates/{submissionid}/',
+ 'edit': 'api/editusertext/',
+ 'edited': 'r/{subreddit}/about/edited/',
+ 'flair': 'api/flair/',
+ 'flairconfig': 'api/flairconfig/',
+ 'flaircsv': 'api/flaircsv/',
+ 'flairlist': 'r/{subreddit}/api/flairlist/',
+ 'flairselector': 'api/flairselector/',
+ 'flairtemplate': 'api/flairtemplate/',
+ 'friend': 'api/friend/',
+ 'friend_v1': 'api/v1/me/friends/{user}',
+ 'friends': 'prefs/friends/',
+ 'gild_thing': 'api/v1/gold/gild/{fullname}/',
+ 'gild_user': 'api/v1/gold/give/{username}/',
+ 'gilded': 'gilded/',
+ 'help': 'help/',
+ 'hide': 'api/hide/',
+ 'ignore_reports': 'api/ignore_reports/',
+ 'inbox': 'message/inbox/',
+ 'info': 'api/info/',
+ 'leavecontributor': 'api/leavecontributor',
+ 'leavemoderator': 'api/leavemoderator',
+ 'lock': 'api/lock/',
+ 'login': 'api/login/',
+ 'me': 'api/v1/me',
+ 'mentions': 'message/mentions',
+ 'message': 'message/messages/{messageid}/',
+ 'messages': 'message/messages/',
+ 'moderators': 'r/{subreddit}/about/moderators/',
+ 'modlog': 'r/{subreddit}/about/log/',
+ 'modqueue': 'r/{subreddit}/about/modqueue/',
+ 'mod_mail': 'r/{subreddit}/message/moderator/',
+ 'morechildren': 'api/morechildren/',
+ 'my_con_subreddits': 'subreddits/mine/contributor/',
+ 'my_mod_subreddits': 'subreddits/mine/moderator/',
+ 'my_multis': 'api/multi/mine/',
+ 'my_subreddits': 'subreddits/mine/subscriber/',
+ 'new': 'new/',
+ 'new_subreddits': 'subreddits/new/',
+ 'marknsfw': 'api/marknsfw/',
+ 'multireddit': 'user/{user}/m/{multi}/',
+ 'multireddit_add': ('api/multi/user/{user}/m/{multi}/r/'
+ '{subreddit}'),
+ 'multireddit_about': 'api/multi/user/{user}/m/{multi}/',
+ 'multireddit_copy': 'api/multi/copy/',
+ 'multireddit_mine': 'me/m/{multi}/',
+ 'multireddit_rename': 'api/multi/rename/',
+ 'multireddit_user': 'api/multi/user/{user}/',
+ 'mute_sender': 'api/mute_message_author/',
+ 'muted': 'r/{subreddit}/about/muted/',
+ 'popular_subreddits': 'subreddits/popular/',
+ 'post_replies': 'message/selfreply/',
+ 'read_message': 'api/read_message/',
+ 'reddit_url': '/',
+ 'register': 'api/register/',
+ 'remove': 'api/remove/',
+ 'report': 'api/report/',
+ 'reports': 'r/{subreddit}/about/reports/',
+ 'rising': 'rising/',
+ 'rules': 'r/{subreddit}/about/rules/',
+ 'save': 'api/save/',
+ 'saved': 'saved/',
+ 'search': 'r/{subreddit}/search/',
+ 'search_reddit_names': 'api/search_reddit_names/',
+ 'select_flair': 'api/selectflair/',
+ 'sent': 'message/sent/',
+ 'sticky': 'r/{subreddit}/about/sticky/',
+ 'sticky_submission': 'api/set_subreddit_sticky/',
+ 'site_admin': 'api/site_admin/',
+ 'spam': 'r/{subreddit}/about/spam/',
+ 'stylesheet': 'r/{subreddit}/about/stylesheet/',
+ 'submit': 'api/submit/',
+ 'sub_comments_gilded': 'r/{subreddit}/comments/gilded/',
+ 'sub_recommendations': 'api/recommend/sr/{subreddits}',
+ 'subreddit': 'r/{subreddit}/',
+ 'subreddit_about': 'r/{subreddit}/about/',
+ 'subreddit_comments': 'r/{subreddit}/comments/',
+ 'subreddit_css': 'api/subreddit_stylesheet/',
+ 'subreddit_random': 'r/{subreddit}/random/',
+ 'subreddit_settings': 'r/{subreddit}/about/edit/',
+ 'subreddit_traffic': 'r/{subreddit}/about/traffic/',
+ 'subscribe': 'api/subscribe/',
+ 'suggested_sort': 'api/set_suggested_sort/',
+ 'top': 'top/',
+ 'uncollapse_message': 'api/uncollapse_message/',
+ 'unfriend': 'api/unfriend/',
+ 'unhide': 'api/unhide/',
+ 'unlock': 'api/unlock/',
+ 'unmarknsfw': 'api/unmarknsfw/',
+ 'unmoderated': 'r/{subreddit}/about/unmoderated/',
+ 'unmute_sender': 'api/unmute_message_author/',
+ 'unignore_reports': 'api/unignore_reports/',
+ 'unread': 'message/unread/',
+ 'unread_message': 'api/unread_message/',
+ 'unsave': 'api/unsave/',
+ 'upload_image': 'api/upload_sr_img',
+ 'user': 'user/{user}/',
+ 'user_about': 'user/{user}/about/',
+ 'username_available': 'api/username_available/',
+ 'vote': 'api/vote/',
+ 'wiki_edit': 'api/wiki/edit/',
+ 'wiki_page': 'r/{subreddit}/wiki/{page}', # No /
+ 'wiki_page_editor': ('r/{subreddit}/api/wiki/alloweditor/'
+ '{method}'),
+ 'wiki_page_settings': 'r/{subreddit}/wiki/settings/{page}',
+ 'wiki_pages': 'r/{subreddit}/wiki/pages/',
+ 'wiki_banned': 'r/{subreddit}/about/wikibanned/',
+ 'wiki_contributors': 'r/{subreddit}/about/wikicontributors/'
+ }
+ WWW_PATHS = set(['authorize'])
+
+ @staticmethod
+ def ua_string(praw_info):
+ """Return the user-agent string.
+
+ The user-agent string contains PRAW version and platform version info.
+
+ """
+ if os.environ.get('SERVER_SOFTWARE') is not None:
+ # Google App Engine information
+ # https://developers.google.com/appengine/docs/python/
+ info = os.environ.get('SERVER_SOFTWARE')
+ else:
+ # Standard platform information
+ info = platform.platform(True).encode('ascii', 'ignore')
+
+ return '{0} PRAW/{1} Python/{2} {3}'.format(
+ praw_info, __version__, sys.version.split()[0], info)
+
+ def __init__(self, site_name, **kwargs):
+ """Initialize PRAW's configuration."""
+ def config_boolean(item):
+ return item and item.lower() in ('1', 'yes', 'true', 'on')
+
+ obj = dict(CONFIG.items(site_name))
+ # Overwrite configuration file settings with those given during
+ # instantiation of the Reddit instance.
+ for key, value in kwargs.items():
+ obj[key] = value
+
+ self.api_url = 'https://' + obj['api_domain']
+ self.permalink_url = 'https://' + obj['permalink_domain']
+ self.oauth_url = ('https://' if config_boolean(obj['oauth_https'])
+ else 'http://') + obj['oauth_domain']
+ self.api_request_delay = float(obj['api_request_delay'])
+ self.by_kind = {obj['comment_kind']: objects.Comment,
+ obj['message_kind']: objects.Message,
+ obj['redditor_kind']: objects.Redditor,
+ obj['submission_kind']: objects.Submission,
+ obj['subreddit_kind']: objects.Subreddit,
+ 'LabeledMulti': objects.Multireddit,
+ 'modaction': objects.ModAction,
+ 'more': objects.MoreComments,
+ 'wikipage': objects.WikiPage,
+ 'wikipagelisting': objects.WikiPageListing,
+ 'UserList': objects.UserList}
+ self.by_object = dict((value, key) for (key, value) in
+ six.iteritems(self.by_kind))
+ self.by_object[objects.LoggedInRedditor] = obj['redditor_kind']
+ self.cache_timeout = float(obj['cache_timeout'])
+ self.check_for_updates = config_boolean(obj['check_for_updates'])
+ self.domain = obj['permalink_domain']
+ self.output_chars_limit = int(obj['output_chars_limit'])
+ self.log_requests = int(obj['log_requests'])
+ self.http_proxy = (obj.get('http_proxy') or os.getenv('http_proxy') or
+ None)
+ self.https_proxy = (obj.get('https_proxy') or
+ os.getenv('https_proxy') or None)
+ # We use `get(...) or None` because `get` may return an empty string
+
+ self.validate_certs = config_boolean(obj.get('validate_certs'))
+
+ self.client_id = obj.get('oauth_client_id') or None
+ self.client_secret = obj.get('oauth_client_secret') or None
+ self.redirect_uri = obj.get('oauth_redirect_uri') or None
+ self.grant_type = obj.get('oauth_grant_type') or None
+ self.refresh_token = obj.get('oauth_refresh_token') or None
+ self.store_json_result = config_boolean(obj.get('store_json_result'))
+
+ if 'short_domain' in obj and obj['short_domain']:
+ self._short_domain = 'http://' + obj['short_domain']
+ else:
+ self._short_domain = None
+ self.timeout = float(obj['timeout'])
+ try:
+ self.user = obj['user'] if obj['user'] else None
+ self.pswd = obj['pswd']
+ except KeyError:
+ self.user = self.pswd = None
+
+ def __getitem__(self, key):
+ """Return the URL for key."""
+ prefix = self.permalink_url if key in self.WWW_PATHS else self.api_url
+ return urljoin(prefix, self.API_PATHS[key])
+
+ @property
+ def short_domain(self):
+ """Return the short domain of the reddit server.
+
+ Used to generate the shortlink. For reddit.com the short_domain is
+ redd.it.
+
+ """
+ if self._short_domain:
+ return self._short_domain
+ else:
+ raise errors.ClientException('No short domain specified.')
+
+
+class BaseReddit(object):
+ """A base class that allows access to reddit's API.
+
+ You should **not** directly instantiate instances of this class. Use
+ :class:`.Reddit` instead.
+
+ """
+
+ RETRY_CODES = [502, 503, 504]
+ update_checked = False
+ openssl_warned = False
+
+ def __init__(self, user_agent, site_name=None, handler=None,
+ disable_update_check=False, **kwargs):
+ """Initialize our connection with a reddit server.
+
+ The user_agent is how your application identifies itself. Read the
+ official API guidelines for user_agents
+ https://github.com/reddit/reddit/wiki/API. Applications using default
+ user_agents such as "Python/urllib" are drastically limited.
+
+ site_name allows you to specify which reddit you want to connect to.
+ The installation defaults are reddit.com, if you only need to connect
+ to reddit.com then you can safely ignore this. If you want to connect
+ to another reddit, set site_name to the name of that reddit. This must
+ match with an entry in praw.ini. If site_name is None, then the site
+ name will be looked for in the environment variable REDDIT_SITE. If it
+ is not found there, the default site name reddit matching reddit.com
+ will be used.
+
+ disable_update_check allows you to prevent an update check from
+ occurring in spite of the check_for_updates setting in praw.ini.
+
+ All additional parameters specified via kwargs will be used to
+ initialize the Config object. This can be used to specify configuration
+ settings during instantiation of the Reddit instance. See
+ https://praw.readthedocs.io/en/latest/pages/configuration_files.html
+ for more details.
+
+ """
+ if not user_agent or not isinstance(user_agent, six.string_types):
+ raise TypeError('user_agent must be a non-empty string.')
+ if 'bot' in user_agent.lower():
+ warn_explicit(
+ 'The keyword `bot` in your user_agent may be problematic.',
+ UserWarning, '', 0)
+
+ self.config = Config(site_name or os.getenv('REDDIT_SITE') or 'reddit',
+ **kwargs)
+ self.handler = handler or DefaultHandler()
+ self.http = Session()
+ self.http.headers['User-Agent'] = self.config.ua_string(user_agent)
+ self.http.validate_certs = self.config.validate_certs
+
+ # This `Session` object is only used to store request information that
+ # is used to make prepared requests. It _should_ never be used to make
+ # a direct request, thus we raise an exception when it is used.
+
+ def _req_error(*_, **__):
+ raise errors.ClientException('Do not make direct requests.')
+ self.http.request = _req_error
+
+ if self.config.http_proxy or self.config.https_proxy:
+ self.http.proxies = {}
+ if self.config.http_proxy:
+ self.http.proxies['http'] = self.config.http_proxy
+ if self.config.https_proxy:
+ self.http.proxies['https'] = self.config.https_proxy
+ self.modhash = None
+
+ # Check for updates if permitted and this is the first Reddit instance
+ # if not disable_update_check and not BaseReddit.update_checked \
+ # and self.config.check_for_updates:
+ # update_check(__name__, __version__)
+ # BaseReddit.update_checked = True
+
+ # Warn against a potentially incompatible version of pyOpenSSL
+ if not BaseReddit.openssl_warned and self.config.validate_certs:
+ _warn_pyopenssl()
+ BaseReddit.openssl_warned = True
+
+ # Initial values
+ self._use_oauth = False
+
+ def _request(self, url, params=None, data=None, files=None, auth=None,
+ timeout=None, raw_response=False, retry_on_error=True,
+ method=None):
+ """Given a page url and a dict of params, open and return the page.
+
+ :param url: the url to grab content from.
+ :param params: a dictionary containing the GET data to put in the url
+ :param data: a dictionary containing the extra data to submit
+ :param files: a dictionary specifying the files to upload
+ :param auth: Add the HTTP authentication headers (see requests)
+ :param timeout: Specifies the maximum time that the actual HTTP request
+ can take.
+ :param raw_response: return the response object rather than the
+ response body
+ :param retry_on_error: if True retry the request, if it fails, for up
+ to 3 attempts
+ :returns: either the response body or the response object
+
+ """
+ def build_key_items(url, params, data, auth, files, method):
+ request = _prepare_request(self, url, params, data, auth, files,
+ method)
+
+ # Prepare extra arguments
+ key_items = []
+ oauth = request.headers.get('Authorization', None)
+ for key_value in (params, data, request.cookies, auth, oauth):
+ if isinstance(key_value, dict):
+ key_items.append(tuple(key_value.items()))
+ elif isinstance(key_value, http_cookiejar.CookieJar):
+ key_items.append(tuple(key_value.get_dict().items()))
+ else:
+ key_items.append(key_value)
+ kwargs = {'_rate_domain': self.config.domain,
+ '_rate_delay': int(self.config.api_request_delay),
+ '_cache_ignore': bool(files) or raw_response,
+ '_cache_timeout': int(self.config.cache_timeout)}
+
+ return (request, key_items, kwargs)
+
+ def decode(match):
+ return six.unichr(html_entities.name2codepoint[match.group(1)])
+
+ def handle_redirect():
+ response = None
+ url = request.url
+ while url: # Manually handle 302 redirects
+ request.url = url
+ kwargs['_cache_key'] = (normalize_url(request.url),
+ tuple(key_items))
+ response = self.handler.request(
+ request=request.prepare(),
+ proxies=self.http.proxies,
+ timeout=timeout,
+ verify=self.http.validate_certs, **kwargs)
+
+ if self.config.log_requests >= 2:
+ msg = 'status: {0}\n'.format(response.status_code)
+ sys.stderr.write(msg)
+ url = _raise_redirect_exceptions(response)
+ assert url != request.url
+ return response
+
+ timeout = self.config.timeout if timeout is None else timeout
+ request, key_items, kwargs = build_key_items(url, params, data,
+ auth, files, method)
+
+ tempauth = self._use_oauth
+ remaining_attempts = 3 if retry_on_error else 1
+ attempt_oauth_refresh = bool(self.refresh_token)
+ while True:
+ try:
+ self._use_oauth = self.is_oauth_session()
+ response = handle_redirect()
+ _raise_response_exceptions(response)
+ self.http.cookies.update(response.cookies)
+ if raw_response:
+ return response
+ else:
+ return re.sub('&([^;]+);', decode, response.text)
+ except errors.OAuthInvalidToken as error:
+ if not attempt_oauth_refresh:
+ raise
+ attempt_oauth_refresh = False
+ self._use_oauth = False
+ self.refresh_access_information()
+ self._use_oauth = tempauth
+ request, key_items, kwargs = build_key_items(url, params,
+ data, auth, files,
+ method)
+ except errors.HTTPException as error:
+ remaining_attempts -= 1
+ # pylint: disable=W0212
+ if error._raw.status_code not in self.RETRY_CODES or \
+ remaining_attempts == 0:
+ raise
+ finally:
+ self._use_oauth = tempauth
+
+ def _json_reddit_objecter(self, json_data):
+ """Return an appropriate RedditObject from json_data when possible."""
+ try:
+ object_class = self.config.by_kind[json_data['kind']]
+ except KeyError:
+ if 'json' in json_data:
+ if len(json_data) != 1:
+ msg = 'Unknown object type: {0}'.format(json_data)
+ warn_explicit(msg, UserWarning, '', 0)
+ return json_data['json']
+ else:
+ return object_class.from_api_response(self, json_data['data'])
+ return json_data
+
+ def evict(self, urls):
+ """Evict url(s) from the cache.
+
+ :param urls: An iterable containing normalized urls.
+ :returns: The number of items removed from the cache.
+
+ """
+ if isinstance(urls, six.string_types):
+ urls = (urls,)
+ return self.handler.evict(urls)
+
+ @decorators.oauth_generator
+ def get_content(self, url, params=None, limit=0, place_holder=None,
+ root_field='data', thing_field='children',
+ after_field='after', object_filter=None, **kwargs):
+ """A generator method to return reddit content from a URL.
+
+ Starts at the initial url, and fetches content using the `after`
+ JSON data until `limit` entries have been fetched, or the
+ `place_holder` has been reached.
+
+ :param url: the url to start fetching content from
+ :param params: dictionary containing extra GET data to put in the url
+ :param limit: the number of content entries to fetch. If limit <= 0,
+ fetch the default for your account (25 for unauthenticated
+ users). If limit is None, then fetch as many entries as possible
+ (reddit returns at most 100 per request, however, PRAW will
+ automatically make additional requests as necessary).
+ :param place_holder: if not None, the method will fetch `limit`
+ content, stopping if it finds content with `id` equal to
+ `place_holder`. The place_holder item is the last item to be
+ yielded from this generator. Note that the use of `place_holder` is
+ not 100% reliable as the place holder item may no longer exist due
+ to being removed or deleted.
+ :param root_field: indicates the field in the json response that holds
+ the data. Most objects use 'data', however some (flairlist) don't
+ have the 'data' object. Use None for the root object.
+ :param thing_field: indicates the field under the root_field which
+ contains the list of things. Most objects use 'children'.
+ :param after_field: indicates the field which holds the after item
+ element
+ :param object_filter: if set to an integer value, fetch content from
+ the corresponding list index in the JSON response. For example
+ the JSON response for submission duplicates is a list of objects,
+ and the object we want to fetch from is at index 1. So we set
+ object_filter=1 to filter out the other useless list elements.
+ :type place_holder: a string corresponding to a reddit base36 id
+ without prefix, e.g. 'asdfasdf'
+ :returns: a list of reddit content, of type Subreddit, Comment,
+ Submission or user flair.
+
+ """
+ _use_oauth = kwargs.get('_use_oauth', self.is_oauth_session())
+
+ objects_found = 0
+ params = params or {}
+ fetch_all = fetch_once = False
+ if limit is None:
+ fetch_all = True
+ params['limit'] = 1024 # Just use a big number
+ elif limit > 0:
+ params['limit'] = limit
+ else:
+ fetch_once = True
+
+ if hasattr(self, '_url_update'):
+ url = self._url_update(url) # pylint: disable=E1101
+
+ # While we still need to fetch more content to reach our limit, do so.
+ while fetch_once or fetch_all or objects_found < limit:
+ if _use_oauth: # Set the necessary _use_oauth value
+ assert self._use_oauth is False
+ self._use_oauth = _use_oauth
+ try:
+ page_data = self.request_json(url, params=params)
+ if object_filter:
+ page_data = page_data[object_filter]
+ finally: # Restore _use_oauth value
+ if _use_oauth:
+ self._use_oauth = False
+ fetch_once = False
+ root = page_data.get(root_field, page_data)
+ for thing in root[thing_field]:
+ yield thing
+ objects_found += 1
+ # Terminate when we've reached the limit, or place holder
+ if objects_found == limit or (place_holder and
+ thing.id == place_holder):
+ return
+ # Set/update the 'after' parameter for the next iteration
+ if root.get(after_field):
+ # We use `root.get` to also test if the value evaluates to True
+ params['after'] = root[after_field]
+ else:
+ return
+
+ @decorators.raise_api_exceptions
+ def request(self, url, params=None, data=None, retry_on_error=True,
+ method=None):
+ """Make a HTTP request and return the response.
+
+ :param url: the url to grab content from.
+ :param params: a dictionary containing the GET data to put in the url
+ :param data: a dictionary containing the extra data to submit
+ :param retry_on_error: if True retry the request, if it fails, for up
+ to 3 attempts
+ :param method: The HTTP method to use in the request.
+ :returns: The HTTP response.
+ """
+ return self._request(url, params, data, raw_response=True,
+ retry_on_error=retry_on_error, method=method)
+
+ @decorators.raise_api_exceptions
+ def request_json(self, url, params=None, data=None, as_objects=True,
+ retry_on_error=True, method=None):
+ """Get the JSON processed from a page.
+
+ :param url: the url to grab content from.
+ :param params: a dictionary containing the GET data to put in the url
+ :param data: a dictionary containing the extra data to submit
+ :param as_objects: if True return reddit objects else raw json dict.
+ :param retry_on_error: if True retry the request, if it fails, for up
+ to 3 attempts
+ :returns: JSON processed page
+
+ """
+ if not url.endswith('.json'):
+ url += '.json'
+ response = self._request(url, params, data, method=method,
+ retry_on_error=retry_on_error)
+ hook = self._json_reddit_objecter if as_objects else None
+ # Request url just needs to be available for the objecter to use
+ self._request_url = url # pylint: disable=W0201
+
+ if response == '':
+ # Some of the v1 urls don't return anything, even when they're
+ # successful.
+ return response
+
+ data = json.loads(response, object_hook=hook)
+ delattr(self, '_request_url')
+ # Update the modhash
+ if isinstance(data, dict) and 'data' in data \
+ and 'modhash' in data['data']:
+ self.modhash = data['data']['modhash']
+ return data
+
+
+class OAuth2Reddit(BaseReddit):
+ """Provides functionality for obtaining reddit OAuth2 access tokens.
+
+ You should **not** directly instantiate instances of this class. Use
+ :class:`.Reddit` instead.
+
+ """
+
+ def __init__(self, *args, **kwargs):
+ """Initialize an OAuth2Reddit instance."""
+ super(OAuth2Reddit, self).__init__(*args, **kwargs)
+ self.client_id = self.config.client_id
+ self.client_secret = self.config.client_secret
+ self.redirect_uri = self.config.redirect_uri
+
+ def _handle_oauth_request(self, data):
+ auth = (self.client_id, self.client_secret)
+ url = self.config['access_token_url']
+ response = self._request(url, auth=auth, data=data, raw_response=True)
+ if not response.ok:
+ msg = 'Unexpected OAuthReturn: {0}'.format(response.status_code)
+ raise errors.OAuthException(msg, url)
+ retval = response.json()
+ if 'error' in retval:
+ error = retval['error']
+ if error == 'invalid_grant':
+ raise errors.OAuthInvalidGrant(error, url)
+ raise errors.OAuthException(retval['error'], url)
+ return retval
+
+ @decorators.require_oauth
+ def get_access_information(self, code):
+ """Return the access information for an OAuth2 authorization grant.
+
+ :param code: the code received in the request from the OAuth2 server
+ :returns: A dictionary with the key/value pairs for ``access_token``,
+ ``refresh_token`` and ``scope``. The ``refresh_token`` value will
+ be None when the OAuth2 grant is not refreshable. The ``scope``
+ value will be a set containing the scopes the tokens are valid for.
+
+ """
+ if self.config.grant_type == 'password':
+ data = {'grant_type': 'password',
+ 'username': self.config.user,
+ 'password': self.config.pswd}
+ else:
+ data = {'code': code, 'grant_type': 'authorization_code',
+ 'redirect_uri': self.redirect_uri}
+ retval = self._handle_oauth_request(data)
+ return {'access_token': retval['access_token'],
+ 'refresh_token': retval.get('refresh_token'),
+ 'scope': set(retval['scope'].split(' '))}
+
+ @decorators.require_oauth
+ def get_authorize_url(self, state, scope='identity', refreshable=False):
+ """Return the URL to send the user to for OAuth2 authorization.
+
+ :param state: a unique string of your choice that represents this
+ individual client
+ :param scope: the reddit scope to ask permissions for. Multiple scopes
+ can be enabled by passing in a container of strings.
+ :param refreshable: when True, a permanent "refreshable" token is
+ issued
+
+ """
+ params = {'client_id': self.client_id, 'response_type': 'code',
+ 'redirect_uri': self.redirect_uri, 'state': state,
+ 'scope': _to_reddit_list(scope)}
+ params['duration'] = 'permanent' if refreshable else 'temporary'
+ request = Request('GET', self.config['authorize'], params=params)
+ return request.prepare().url
+
+ @property
+ def has_oauth_app_info(self):
+ """Return True when OAuth credentials are associated with the instance.
+
+ The necessary credentials are: ``client_id``, ``client_secret`` and
+ ``redirect_uri``.
+
+ """
+ return all((self.client_id is not None,
+ self.client_secret is not None,
+ self.redirect_uri is not None))
+
+ @decorators.require_oauth
+ def refresh_access_information(self, refresh_token):
+ """Return updated access information for an OAuth2 authorization grant.
+
+ :param refresh_token: the refresh token used to obtain the updated
+ information
+ :returns: A dictionary with the key/value pairs for access_token,
+ refresh_token and scope. The refresh_token value will be done when
+ the OAuth2 grant is not refreshable. The scope value will be a set
+ containing the scopes the tokens are valid for.
+
+ Password grants aren't refreshable, so use `get_access_information()`
+ again, instead.
+ """
+ if self.config.grant_type == 'password':
+ data = {'grant_type': 'password',
+ 'username': self.config.user,
+ 'password': self.config.pswd}
+ else:
+ data = {'grant_type': 'refresh_token',
+ 'redirect_uri': self.redirect_uri,
+ 'refresh_token': refresh_token}
+ retval = self._handle_oauth_request(data)
+ return {'access_token': retval['access_token'],
+ 'refresh_token': refresh_token,
+ 'scope': set(retval['scope'].split(' '))}
+
+ def set_oauth_app_info(self, client_id, client_secret, redirect_uri):
+ """Set the app information to use with OAuth2.
+
+ This function need only be called if your praw.ini site configuration
+ does not already contain the necessary information.
+
+ Go to https://www.reddit.com/prefs/apps/ to discover the appropriate
+ values for your application.
+
+ :param client_id: the client_id of your application
+ :param client_secret: the client_secret of your application
+ :param redirect_uri: the redirect_uri of your application
+
+ """
+ self.client_id = client_id
+ self.client_secret = client_secret
+ self.redirect_uri = redirect_uri
+
+
+class UnauthenticatedReddit(BaseReddit):
+ """This mixin provides bindings for basic functions of reddit's API.
+
+ None of these functions require authenticated access to reddit's API.
+
+ You should **not** directly instantiate instances of this class. Use
+ :class:`.Reddit` instead.
+
+ """
+
+ def __init__(self, *args, **kwargs):
+ """Initialize an UnauthenticatedReddit instance."""
+ super(UnauthenticatedReddit, self).__init__(*args, **kwargs)
+ # initialize to 1 instead of 0, because 0 does not reliably make
+ # new requests.
+ self._unique_count = 1
+
+ def create_redditor(self, user_name, password, email=''):
+ """Register a new user.
+
+ :returns: The json response from the server.
+
+ """
+ data = {'email': email,
+ 'passwd': password,
+ 'passwd2': password,
+ 'user': user_name}
+ return self.request_json(self.config['register'], data=data)
+
+ def default_subreddits(self, *args, **kwargs):
+ """Return a get_content generator for the default subreddits.
+
+ The additional parameters are passed directly into
+ :meth:`.get_content`. Note: the `url` parameter cannot be altered.
+
+ """
+ url = self.config['default_subreddits']
+ return self.get_content(url, *args, **kwargs)
+
+ @decorators.restrict_access(scope='read')
+ def get_comments(self, subreddit, gilded_only=False, *args, **kwargs):
+ """Return a get_content generator for comments in the given subreddit.
+
+ :param gilded_only: If True only return gilded comments.
+
+ The additional parameters are passed directly into
+ :meth:`.get_content`. Note: the `url` parameter cannot be altered.
+
+ """
+ key = 'sub_comments_gilded' if gilded_only else 'subreddit_comments'
+ url = self.config[key].format(subreddit=six.text_type(subreddit))
+ return self.get_content(url, *args, **kwargs)
+
+ @decorators.restrict_access(scope='read')
+ def get_controversial(self, *args, **kwargs):
+ """Return a get_content generator for controversial submissions.
+
+ Corresponds to submissions provided by
+ ``https://www.reddit.com/controversial/`` for the session.
+
+ The additional parameters are passed directly into
+ :meth:`.get_content`. Note: the `url` parameter cannot be altered.
+
+ """
+ return self.get_content(self.config['controversial'], *args, **kwargs)
+
+ @decorators.restrict_access(scope='read')
+ def get_domain_listing(self, domain, sort='hot', period=None, *args,
+ **kwargs):
+ """Return a get_content generator for submissions by domain.
+
+ Corresponds to the submissions provided by
+ ``https://www.reddit.com/domain/{domain}``.
+
+ :param domain: The domain to generate a submission listing for.
+ :param sort: When provided must be one of 'hot', 'new', 'rising',
+ 'controversial, 'gilded', or 'top'. Defaults to 'hot'.
+ :param period: When sort is either 'controversial', or 'top' the period
+ can be either None (for account default), 'all', 'year', 'month',
+ 'week', 'day', or 'hour'.
+
+ The additional parameters are passed directly into
+ :meth:`.get_content`. Note: the `url` parameter cannot be altered.
+
+ """
+ # Verify arguments
+ if sort not in ('controversial', 'hot', 'new', 'rising', 'top',
+ 'gilded'):
+ raise TypeError('Invalid sort parameter.')
+ if period not in (None, 'all', 'day', 'hour', 'month', 'week', 'year'):
+ raise TypeError('Invalid period parameter.')
+ if sort not in ('controversial', 'top') and period:
+ raise TypeError('Period cannot be set for that sort argument.')
+
+ url = self.config['domain'].format(domain=domain)
+ if sort != 'hot':
+ url += sort
+ if period: # Set or overwrite params 't' parameter
+ kwargs.setdefault('params', {})['t'] = period
+ return self.get_content(url, *args, **kwargs)
+
+ @decorators.restrict_access(scope='modflair')
+ def get_flair(self, subreddit, redditor, **params):
+ """Return the flair for a user on the given subreddit.
+
+ :param subreddit: Can be either a Subreddit object or the name of a
+ subreddit.
+ :param redditor: Can be either a Redditor object or the name of a
+ redditor.
+ :returns: None if the user doesn't exist, otherwise a dictionary
+ containing the keys `flair_css_class`, `flair_text`, and `user`.
+
+ """
+ name = six.text_type(redditor)
+ params.update(name=name)
+ url = self.config['flairlist'].format(
+ subreddit=six.text_type(subreddit))
+ data = self.request_json(url, params=params)
+ if not data['users'] or \
+ data['users'][0]['user'].lower() != name.lower():
+ return None
+ return data['users'][0]
+
+ @decorators.restrict_access(scope='read')
+ def get_front_page(self, *args, **kwargs):
+ """Return a get_content generator for the front page submissions.
+
+ Corresponds to the submissions provided by ``https://www.reddit.com/``
+ for the session.
+
+ The additional parameters are passed directly into
+ :meth:`.get_content`. Note: the `url` parameter cannot be altered.
+
+ """
+ return self.get_content(self.config['reddit_url'], *args, **kwargs)
+
+ @decorators.restrict_access(scope='read', generator_called=True)
+ def get_info(self, url=None, thing_id=None, *args, **kwargs):
+ """Look up existing items by thing_id (fullname) or url.
+
+ :param url: A url to lookup.
+ :param thing_id: A single thing_id, or a list of thing_ids. A thing_id
+ can be any one of Comment (``t1_``), Link (``t3_``), or Subreddit
+ (``t5_``) to lookup by fullname.
+ :returns: When a single ``thing_id`` is provided, return the
+ corresponding thing object, or ``None`` if not found. When a list
+ of ``thing_id``s or a ``url`` is provided return a list of thing
+ objects (up to ``limit``). ``None`` is returned if all of the
+ thing_ids or the URL is invalid.
+
+ The additional parameters are passed into :meth:`.get_content` after
+ the `params` parameter is exctracted and used to update the dictionary
+ of url parameters this function sends. Note: the `url` parameter
+ cannot be altered.
+
+ Also, if using thing_id and the `limit` parameter passed to
+ :meth:`.get_content` is used to slice the list of retreived things
+ before returning it to the user, for when `limit > 100` and
+ `(limit % 100) > 0`, to ensure a maximum of `limit` thigns are
+ returned.
+
+ """
+ if bool(url) == bool(thing_id):
+ raise TypeError('Only one of url or thing_id is required!')
+
+ # In these cases, we will have a list of things to return.
+ # Otherwise, it will just be one item.
+ if isinstance(thing_id, six.string_types) and ',' in thing_id:
+ thing_id = thing_id.split(',')
+ return_list = bool(url) or not isinstance(thing_id, six.string_types)
+
+ if url:
+ param_groups = [{'url': url}]
+ else:
+ if isinstance(thing_id, six.string_types):
+ thing_id = [thing_id]
+ id_chunks = chunk_sequence(thing_id, 100)
+ param_groups = [{'id': ','.join(id_chunk)} for
+ id_chunk in id_chunks]
+
+ items = []
+ update_with = kwargs.pop('params', {})
+ for param_group in param_groups:
+ param_group.update(update_with)
+ kwargs['params'] = param_group
+ chunk = self.get_content(self.config['info'], *args, **kwargs)
+ items.extend(list(chunk))
+
+ # if using ids, manually set the limit
+ if kwargs.get('limit'):
+ items = items[:kwargs['limit']]
+
+ if return_list:
+ return items if items else None
+ elif items:
+ return items[0]
+ else:
+ return None
+
+ @decorators.restrict_access(scope='read')
+ def get_moderators(self, subreddit, **kwargs):
+ """Return the list of moderators for the given subreddit."""
+ url = self.config['moderators'].format(
+ subreddit=six.text_type(subreddit))
+ return self.request_json(url, **kwargs)
+
+ @decorators.restrict_access(scope='read')
+ def get_new(self, *args, **kwargs):
+ """Return a get_content generator for new submissions.
+
+ Corresponds to the submissions provided by
+ ``https://www.reddit.com/new/`` for the session.
+
+ The additional parameters are passed directly into
+ :meth:`.get_content`. Note: the `url` parameter cannot be altered.
+
+ """
+ return self.get_content(self.config['new'], *args, **kwargs)
+
+ def get_new_subreddits(self, *args, **kwargs):
+ """Return a get_content generator for the newest subreddits.
+
+ The additional parameters are passed directly into
+ :meth:`.get_content`. Note: the `url` parameter cannot be altered.
+
+ """
+ url = self.config['new_subreddits']
+ return self.get_content(url, *args, **kwargs)
+
+ def get_popular_subreddits(self, *args, **kwargs):
+ """Return a get_content generator for the most active subreddits.
+
+ The additional parameters are passed directly into
+ :meth:`.get_content`. Note: the `url` parameter cannot be altered.
+
+ """
+ url = self.config['popular_subreddits']
+ return self.get_content(url, *args, **kwargs)
+
+ def get_random_subreddit(self, nsfw=False):
+ """Return a random Subreddit object.
+
+ :param nsfw: When true, return a random NSFW Subreddit object. Calling
+ in this manner will set the 'over18' cookie for the duration of the
+ PRAW session.
+
+ """
+ path = 'random'
+ if nsfw:
+ self.http.cookies.set('over18', '1')
+ path = 'randnsfw'
+ url = self.config['subreddit'].format(subreddit=path)
+ response = self._request(url, params={'unique': self._unique_count},
+ raw_response=True)
+ self._unique_count += 1
+ return self.get_subreddit(response.url.rsplit('/', 2)[-2])
+
+ def get_random_submission(self, subreddit='all'):
+ """Return a random Submission object.
+
+ :param subreddit: Limit the submission to the specified
+ subreddit(s). Default: all
+
+ """
+ url = self.config['subreddit_random'].format(
+ subreddit=six.text_type(subreddit))
+ try:
+ item = self.request_json(url,
+ params={'unique': self._unique_count})
+ self._unique_count += 1 # Avoid network-level caching
+ return objects.Submission.from_json(item)
+ except errors.RedirectException as exc:
+ self._unique_count += 1
+ return self.get_submission(exc.response_url)
+ raise errors.ClientException('Expected exception not raised.')
+
+ def get_redditor(self, user_name, *args, **kwargs):
+ """Return a Redditor instance for the user_name specified.
+
+ The additional parameters are passed directly into the
+ :class:`.Redditor` constructor.
+
+ """
+ return objects.Redditor(self, user_name, *args, **kwargs)
+
+ @decorators.restrict_access(scope='read')
+ def get_rising(self, *args, **kwargs):
+ """Return a get_content generator for rising submissions.
+
+ Corresponds to the submissions provided by
+ ``https://www.reddit.com/rising/`` for the session.
+
+ The additional parameters are passed directly into
+ :meth:`.get_content`. Note: the `url` parameter cannot be altered.
+
+ """
+ return self.get_content(self.config['rising'], *args, **kwargs)
+
+ @decorators.restrict_access(scope='read')
+ def get_rules(self, subreddit, bottom=False):
+ """Return the json dictionary containing rules for a subreddit.
+
+ :param subreddit: The subreddit whose rules we will return.
+
+ """
+ url = self.config['rules'].format(subreddit=six.text_type(subreddit))
+ return self.request_json(url)
+
+ @decorators.restrict_access(scope='read')
+ def get_sticky(self, subreddit, bottom=False):
+ """Return a Submission object for the sticky of the subreddit.
+
+ :param bottom: Get the top or bottom sticky. If the subreddit has only
+ a single sticky, it is considered the top one.
+
+ """
+ url = self.config['sticky'].format(subreddit=six.text_type(subreddit))
+ param = {'num': 2} if bottom else None
+ return objects.Submission.from_json(self.request_json(url,
+ params=param))
+
+ def get_submission(self, url=None, submission_id=None, comment_limit=0,
+ comment_sort=None, params=None):
+ """Return a Submission object for the given url or submission_id.
+
+ :param comment_limit: The desired number of comments to fetch. If <= 0
+ fetch the default number for the session's user. If None, fetch the
+ maximum possible.
+ :param comment_sort: The sort order for retrieved comments. When None
+ use the default for the session's user.
+ :param params: Dictionary containing extra GET data to put in the url.
+
+ """
+ if bool(url) == bool(submission_id):
+ raise TypeError('One (and only one) of id or url is required!')
+ if submission_id:
+ url = urljoin(self.config['comments'], submission_id)
+ return objects.Submission.from_url(self, url,
+ comment_limit=comment_limit,
+ comment_sort=comment_sort,
+ params=params)
+
+ def get_submissions(self, fullnames, *args, **kwargs):
+ """Generate Submission objects for each item provided in `fullnames`.
+
+ A submission fullname looks like `t3_`. Submissions are
+ yielded in the same order they appear in `fullnames`.
+
+ Up to 100 items are batched at a time -- this happens transparently.
+
+ The additional parameters are passed directly into
+ :meth:`.get_content`. Note: the `url` and `limit` parameters cannot be
+ altered.
+
+ """
+ fullnames = fullnames[:]
+ while fullnames:
+ cur = fullnames[:100]
+ fullnames[:100] = []
+ url = self.config['by_id'] + ','.join(cur)
+ for item in self.get_content(url, limit=len(cur), *args, **kwargs):
+ yield item
+
+ def get_subreddit(self, subreddit_name, *args, **kwargs):
+ """Return a Subreddit object for the subreddit_name specified.
+
+ The additional parameters are passed directly into the
+ :class:`.Subreddit` constructor.
+
+ """
+ sr_name_lower = subreddit_name.lower()
+ if sr_name_lower == 'random':
+ return self.get_random_subreddit()
+ elif sr_name_lower == 'randnsfw':
+ return self.get_random_subreddit(nsfw=True)
+ return objects.Subreddit(self, subreddit_name, *args, **kwargs)
+
+ def get_subreddit_recommendations(self, subreddits, omit=None):
+ """Return a list of recommended subreddits as Subreddit objects.
+
+ Subreddits with activity less than a certain threshold, will not have
+ any recommendations due to lack of data.
+
+ :param subreddits: A list of subreddits (either names or Subreddit
+ objects) to base the recommendations on.
+ :param omit: A list of subreddits (either names or Subreddit
+ objects) that will be filtered out of the result.
+
+ """
+ params = {'omit': _to_reddit_list(omit or [])}
+ url = self.config['sub_recommendations'].format(
+ subreddits=_to_reddit_list(subreddits))
+ result = self.request_json(url, params=params)
+ return [objects.Subreddit(self, sub['sr_name']) for sub in result]
+
+ @decorators.restrict_access(scope='read')
+ def get_top(self, *args, **kwargs):
+ """Return a get_content generator for top submissions.
+
+ Corresponds to the submissions provided by
+ ``https://www.reddit.com/top/`` for the session.
+
+ The additional parameters are passed directly into
+ :meth:`.get_content`. Note: the `url` parameter cannot be altered.
+
+ """
+ return self.get_content(self.config['top'], *args, **kwargs)
+
+ @decorators.restrict_access(scope='read')
+ def get_gilded(self, *args, **kwargs):
+ """Return a get_content generator for gilded submissions.
+
+ Corresponds to the submissions provided by
+ ``https://www.reddit.com/gilded/`` for the session.
+
+ The additional parameters are passed directly into
+ :meth:`.get_content`. Note: the `url` parameter cannot be altered.
+
+ """
+ return self.get_content(self.config['gilded'], *args, **kwargs)
+
+ # There exists a `modtraffic` scope, but it is unused.
+ @decorators.restrict_access(scope='modconfig')
+ def get_traffic(self, subreddit):
+ """Return the json dictionary containing traffic stats for a subreddit.
+
+ :param subreddit: The subreddit whose /about/traffic page we will
+ collect.
+
+ """
+ url = self.config['subreddit_traffic'].format(
+ subreddit=six.text_type(subreddit))
+ return self.request_json(url)
+
+ @decorators.restrict_access(scope='wikiread', login=False)
+ def get_wiki_page(self, subreddit, page):
+ """Return a WikiPage object for the subreddit and page provided."""
+ return objects.WikiPage(self, six.text_type(subreddit), page.lower())
+
+ @decorators.restrict_access(scope='wikiread', login=False)
+ def get_wiki_pages(self, subreddit):
+ """Return a list of WikiPage objects for the subreddit."""
+ url = self.config['wiki_pages'].format(
+ subreddit=six.text_type(subreddit))
+ return self.request_json(url)
+
+ def is_username_available(self, username):
+ """Return True if username is valid and available, otherwise False."""
+ params = {'user': username}
+ try:
+ result = self.request_json(self.config['username_available'],
+ params=params)
+ except errors.BadUsername:
+ return False
+ return result
+
+ def search(self, query, subreddit=None, sort=None, syntax=None,
+ period=None, *args, **kwargs):
+ """Return a generator for submissions that match the search query.
+
+ :param query: The query string to search for. If query is a URL only
+ submissions which link to that URL will be returned.
+ :param subreddit: Limit search results to the subreddit if provided.
+ :param sort: The sort order of the results.
+ :param syntax: The syntax of the search query.
+ :param period: The time period of the results.
+
+ The additional parameters are passed directly into
+ :meth:`.get_content`. Note: the `url` parameter cannot be altered.
+
+ See https://www.reddit.com/wiki/search for more information on how to
+ build a search query.
+
+ """
+ params = {'q': query}
+ if 'params' in kwargs:
+ params.update(kwargs['params'])
+ kwargs.pop('params')
+ if sort:
+ params['sort'] = sort
+ if syntax:
+ params['syntax'] = syntax
+ if period:
+ params['t'] = period
+ if subreddit:
+ params['restrict_sr'] = 'on'
+ subreddit = six.text_type(subreddit)
+ else:
+ subreddit = 'all'
+ url = self.config['search'].format(subreddit=subreddit)
+
+ depth = 2
+ while depth > 0:
+ depth -= 1
+ try:
+ for item in self.get_content(url, params=params, *args,
+ **kwargs):
+ yield item
+ break
+ except errors.RedirectException as exc:
+ parsed = urlparse(exc.response_url)
+ params = dict((k, ",".join(v)) for k, v in
+ parse_qs(parsed.query).items())
+ url = urlunparse(parsed[:3] + ("", "", ""))
+ # Handle redirects from URL searches
+ if 'already_submitted' in params:
+ yield self.get_submission(url)
+ break
+
+ def search_reddit_names(self, query):
+ """Return subreddits whose display name contains the query."""
+ data = {'query': query}
+ results = self.request_json(self.config['search_reddit_names'],
+ data=data)
+ return [self.get_subreddit(name) for name in results['names']]
+
+
+class AuthenticatedReddit(OAuth2Reddit, UnauthenticatedReddit):
+ """This class adds the methods necessary for authenticating with reddit.
+
+ Authentication can either be login based
+ (through :meth:`~praw.__init__.AuthenticatedReddit.login`), or OAuth2 based
+ (via :meth:`~praw.__init__.AuthenticatedReddit.set_access_credentials`).
+
+ You should **not** directly instantiate instances of this class. Use
+ :class:`.Reddit` instead.
+
+ """
+
+ def __init__(self, *args, **kwargs):
+ """Initialize an AuthenticatedReddit instance."""
+ super(AuthenticatedReddit, self).__init__(*args, **kwargs)
+ # Add variable to distinguish between authentication type
+ # * None means unauthenticated
+ # * True mean login authenticated
+ # * set(...) means OAuth authenticated with the scopes in the set
+ self._authentication = None
+ self.access_token = None
+ self.refresh_token = self.config.refresh_token or None
+ self.user = None
+
+ def __str__(self):
+ """Return a string representation of the AuthenticatedReddit."""
+ if isinstance(self._authentication, set):
+ return 'OAuth2 reddit session (scopes: {0})'.format(
+ ', '.join(self._authentication))
+ elif self._authentication:
+ return 'LoggedIn reddit session (user: {0})'.format(self.user)
+ else:
+ return 'Unauthenticated reddit session'
+
+ def _url_update(self, url):
+ # When getting posts from a multireddit owned by the authenticated
+ # Redditor, we are redirected to me/m/multi/. Handle that now
+ # instead of catching later.
+ if re.search('user/.*/m/.*', url):
+ redditor = url.split('/')[-4]
+ if self.user and self.user.name.lower() == redditor.lower():
+ url = url.replace("user/"+redditor, 'me')
+ return url
+
+ @decorators.restrict_access(scope='modself', mod=False)
+ def accept_moderator_invite(self, subreddit):
+ """Accept a moderator invite to the given subreddit.
+
+ Callable upon an instance of Subreddit with no arguments.
+
+ :returns: The json response from the server.
+
+ """
+ data = {'r': six.text_type(subreddit)}
+ # Clear moderated subreddits and cache
+ self.user._mod_subs = None # pylint: disable=W0212
+ self.evict(self.config['my_mod_subreddits'])
+ return self.request_json(self.config['accept_mod_invite'], data=data)
+
+ def clear_authentication(self):
+ """Clear any existing authentication on the reddit object.
+
+ This function is implicitly called on `login` and
+ `set_access_credentials`.
+
+ """
+ self._authentication = None
+ self.access_token = None
+ self.refresh_token = None
+ self.http.cookies.clear()
+ self.user = None
+
+ def delete(self, password, message=""):
+ """Delete the currently authenticated redditor.
+
+ WARNING!
+
+ This action is IRREVERSIBLE. Use only if you're okay with NEVER
+ accessing this reddit account again.
+
+ :param password: password for currently authenticated account
+ :param message: optional 'reason for deletion' message.
+ :returns: json response from the server.
+
+ """
+ data = {'user': self.user.name,
+ 'passwd': password,
+ 'delete_message': message,
+ 'confirm': True}
+ return self.request_json(self.config['delete_redditor'], data=data)
+
+ @decorators.restrict_access(scope='wikiedit')
+ def edit_wiki_page(self, subreddit, page, content, reason=''):
+ """Create or edit a wiki page with title `page` for `subreddit`.
+
+ :returns: The json response from the server.
+
+ """
+ data = {'content': content,
+ 'page': page,
+ 'r': six.text_type(subreddit),
+ 'reason': reason}
+ evict = self.config['wiki_page'].format(
+ subreddit=six.text_type(subreddit), page=page.lower())
+ self.evict(evict)
+ return self.request_json(self.config['wiki_edit'], data=data)
+
+ def get_access_information(self, code, # pylint: disable=W0221
+ update_session=True):
+ """Return the access information for an OAuth2 authorization grant.
+
+ :param code: the code received in the request from the OAuth2 server
+ :param update_session: Update the current session with the retrieved
+ token(s).
+ :returns: A dictionary with the key/value pairs for access_token,
+ refresh_token and scope. The refresh_token value will be done when
+ the OAuth2 grant is not refreshable.
+
+ """
+ retval = super(AuthenticatedReddit, self).get_access_information(code)
+ if update_session:
+ self.set_access_credentials(**retval)
+ return retval
+
+ @decorators.restrict_access(scope='flair')
+ def get_flair_choices(self, subreddit, link=None):
+ """Return available flair choices and current flair.
+
+ :param link: If link is given, return the flair options for this
+ submission. Not normally given directly, but instead set by calling
+ the flair_choices method for Submission objects.
+ Use the default for the session's user.
+
+ :returns: A dictionary with 2 keys. 'current' containing current flair
+ settings for the authenticated user and 'choices' containing a list
+ of possible flair choices.
+
+ """
+ data = {'r': six.text_type(subreddit), 'link': link}
+ return self.request_json(self.config['flairselector'], data=data)
+
+ @decorators.restrict_access(scope='read', login=True)
+ def get_friends(self, **params):
+ """Return a UserList of Redditors with whom the user is friends."""
+ url = self.config['friends']
+ return self.request_json(url, params=params)[0]
+
+ @decorators.restrict_access(scope='identity', oauth_only=True)
+ def get_me(self):
+ """Return a LoggedInRedditor object.
+
+ Note: This function is only intended to be used with an 'identity'
+ providing OAuth2 grant.
+ """
+ response = self.request_json(self.config['me'])
+ user = objects.Redditor(self, response['name'], response)
+ user.__class__ = objects.LoggedInRedditor
+ return user
+
+ def has_scope(self, scope):
+ """Return True if OAuth2 authorized for the passed in scope(s)."""
+ if not self.is_oauth_session():
+ return False
+ if '*' in self._authentication:
+ return True
+ if isinstance(scope, six.string_types):
+ scope = [scope]
+ return all(s in self._authentication for s in scope)
+
+ def is_logged_in(self):
+ """Return True when the session is authenticated via username/password.
+
+ Username and passwords are provided via
+ :meth:`~praw.__init__.AuthenticatedReddit.login`.
+
+ """
+ return self._authentication is True
+
+ def is_oauth_session(self):
+ """Return True when the current session is an OAuth2 session."""
+ return isinstance(self._authentication, set)
+
+ @decorators.deprecated('reddit intends to disable password-based '
+ 'authentication of API clients sometime in the '
+ 'near future. As a result this method will be '
+ 'removed in a future major version of PRAW.\n\n'
+ 'For more information please see:\n\n'
+ '* Original reddit deprecation notice: '
+ 'https://www.reddit.com/comments/2ujhkr/\n\n'
+ '* Updated delayed deprecation notice: '
+ 'https://www.reddit.com/comments/37e2mv/\n\n'
+ 'Pass ``disable_warning=True`` to ``login`` to '
+ 'disable this warning.')
+ def login(self, username=None, password=None, **kwargs):
+ """Login to a reddit site.
+
+ **DEPRECATED**. Will be removed in a future version of PRAW.
+
+ https://www.reddit.com/comments/2ujhkr/
+ https://www.reddit.com/comments/37e2mv/
+
+ Look for username first in parameter, then praw.ini and finally if both
+ were empty get it from stdin. Look for password in parameter, then
+ praw.ini (but only if username matches that in praw.ini) and finally
+ if they both are empty get it with getpass. Add the variables ``user``
+ (username) and ``pswd`` (password) to your praw.ini file to allow for
+ auto-login.
+
+ A successful login will overwrite any existing authentication.
+
+ """
+ if password and not username:
+ raise Exception('Username must be provided when password is.')
+ user = username or self.config.user
+ if not user:
+ sys.stdout.write('Username: ')
+ sys.stdout.flush()
+ user = sys.stdin.readline().strip()
+ pswd = None
+ else:
+ pswd = password or self.config.pswd
+ if not pswd:
+ import getpass
+ pswd = getpass.getpass('Password for {0}: '.format(user)
+ .encode('ascii', 'ignore'))
+
+ data = {'passwd': pswd,
+ 'user': user}
+ self.clear_authentication()
+ self.request_json(self.config['login'], data=data)
+ # Update authentication settings
+ self._authentication = True
+ self.user = self.get_redditor(user)
+ self.user.__class__ = objects.LoggedInRedditor
+
+ def refresh_access_information(self, # pylint: disable=W0221
+ refresh_token=None,
+ update_session=True):
+ """Return updated access information for an OAuth2 authorization grant.
+
+ :param refresh_token: The refresh token used to obtain the updated
+ information. When not provided, use the stored refresh_token.
+ :param update_session: Update the session with the returned data.
+ :returns: A dictionary with the key/value pairs for ``access_token``,
+ ``refresh_token`` and ``scope``. The ``refresh_token`` value will
+ be None when the OAuth2 grant is not refreshable. The ``scope``
+ value will be a set containing the scopes the tokens are valid for.
+
+ """
+ response = super(AuthenticatedReddit, self).refresh_access_information(
+ refresh_token=refresh_token or self.refresh_token)
+ if update_session:
+ self.set_access_credentials(**response)
+ return response
+
+ @decorators.restrict_access(scope='flair')
+ def select_flair(self, item, flair_template_id='', flair_text=''):
+ """Select user flair or link flair on subreddits.
+
+ This can only be used for assigning your own name flair or link flair
+ on your own submissions. For assigning other's flairs using moderator
+ access, see :meth:`~praw.__init__.ModFlairMixin.set_flair`.
+
+ :param item: A string, Subreddit object (for user flair), or
+ Submission object (for link flair). If ``item`` is a string it
+ will be treated as the name of a Subreddit.
+ :param flair_template_id: The id for the desired flair template. Use
+ the :meth:`~praw.objects.Subreddit.get_flair_choices` and
+ :meth:`~praw.objects.Submission.get_flair_choices` methods to find
+ the ids for the available user and link flair choices.
+ :param flair_text: A string containing the custom flair text.
+ Used on subreddits that allow it.
+
+ :returns: The json response from the server.
+
+ """
+ data = {'flair_template_id': flair_template_id or '',
+ 'text': flair_text or ''}
+ if isinstance(item, objects.Submission):
+ # Link flair
+ data['link'] = item.fullname
+ evict = item.permalink
+ else:
+ # User flair
+ data['name'] = self.user.name
+ data['r'] = six.text_type(item)
+ evict = self.config['flairlist'].format(
+ subreddit=six.text_type(item))
+ response = self.request_json(self.config['select_flair'], data=data)
+ self.evict(evict)
+ return response
+
+ @decorators.require_oauth
+ def set_access_credentials(self, scope, access_token, refresh_token=None,
+ update_user=True):
+ """Set the credentials used for OAuth2 authentication.
+
+ Calling this function will overwrite any currently existing access
+ credentials.
+
+ :param scope: A set of reddit scopes the tokens provide access to
+ :param access_token: the access token of the authentication
+ :param refresh_token: the refresh token of the authentication
+ :param update_user: Whether or not to set the user attribute for
+ identity scopes
+
+ """
+ if isinstance(scope, (list, tuple)):
+ scope = set(scope)
+ elif isinstance(scope, six.string_types):
+ scope = set(scope.split())
+ if not isinstance(scope, set):
+ raise TypeError('`scope` parameter must be a set')
+ self.clear_authentication()
+ # Update authentication settings
+ self._authentication = scope
+ self.access_token = access_token
+ self.refresh_token = refresh_token
+ # Update the user object
+ if update_user and ('identity' in scope or '*' in scope):
+ self.user = self.get_me()
+
+
+class ModConfigMixin(AuthenticatedReddit):
+ """Adds methods requiring the 'modconfig' scope (or mod access).
+
+ You should **not** directly instantiate instances of this class. Use
+ :class:`.Reddit` instead.
+
+ """
+
+ @decorators.restrict_access(scope='modconfig', mod=False)
+ @decorators.require_captcha
+ def create_subreddit(self, name, title, description='', language='en',
+ subreddit_type='public', content_options='any',
+ over_18=False, default_set=True, show_media=False,
+ domain='', wikimode='disabled', captcha=None,
+ **kwargs):
+ """Create a new subreddit.
+
+ :returns: The json response from the server.
+
+ This function may result in a captcha challenge. PRAW will
+ automatically prompt you for a response. See :ref:`handling-captchas`
+ if you want to manually handle captchas.
+
+ """
+ data = {'name': name,
+ 'title': title,
+ 'description': description,
+ 'lang': language,
+ 'type': subreddit_type,
+ 'link_type': content_options,
+ 'over_18': 'on' if over_18 else 'off',
+ 'allow_top': 'on' if default_set else 'off',
+ 'show_media': 'on' if show_media else 'off',
+ 'wikimode': wikimode,
+ 'domain': domain}
+ if captcha:
+ data.update(captcha)
+ return self.request_json(self.config['site_admin'], data=data)
+
+ @decorators.restrict_access(scope='modconfig')
+ def delete_image(self, subreddit, name=None, header=False):
+ """Delete an image from the subreddit.
+
+ :param name: The name of the image if removing a CSS image.
+ :param header: When true, delete the subreddit header.
+ :returns: The json response from the server.
+
+ """
+ subreddit = six.text_type(subreddit)
+ if name and header:
+ raise TypeError('Both name and header cannot be set.')
+ elif name:
+ data = {'img_name': name}
+ url = self.config['delete_sr_image']
+ self.evict(self.config['stylesheet'].format(subreddit=subreddit))
+ else:
+ data = True
+ url = self.config['delete_sr_header']
+ url = url.format(subreddit=subreddit)
+ return self.request_json(url, data=data)
+
+ @decorators.restrict_access(scope='modconfig')
+ def get_settings(self, subreddit, **params):
+ """Return the settings for the given subreddit."""
+ url = self.config['subreddit_settings'].format(
+ subreddit=six.text_type(subreddit))
+ return self.request_json(url, params=params)['data']
+
+ @decorators.restrict_access(scope='modconfig')
+ def set_settings(self, subreddit, title, public_description='',
+ description='', language='en', subreddit_type='public',
+ content_options='any', over_18=False, default_set=True,
+ show_media=False, domain='', domain_css=False,
+ domain_sidebar=False, header_hover_text='',
+ wikimode='disabled', wiki_edit_age=30,
+ wiki_edit_karma=100,
+ submit_link_label='', submit_text_label='',
+ exclude_banned_modqueue=False, comment_score_hide_mins=0,
+ public_traffic=False, collapse_deleted_comments=False,
+ spam_comments='low', spam_links='high',
+ spam_selfposts='high', submit_text='',
+ hide_ads=False, suggested_comment_sort='',
+ key_color='',
+ **kwargs):
+ """Set the settings for the given subreddit.
+
+ :param subreddit: Must be a subreddit object.
+ :returns: The json response from the server.
+
+ """
+ data = {'sr': subreddit.fullname,
+ 'allow_top': default_set,
+ 'comment_score_hide_mins': comment_score_hide_mins,
+ 'collapse_deleted_comments': collapse_deleted_comments,
+ 'description': description,
+ 'domain': domain or '',
+ 'domain_css': domain_css,
+ 'domain_sidebar': domain_sidebar,
+ 'exclude_banned_modqueue': exclude_banned_modqueue,
+ 'header-title': header_hover_text or '',
+ 'hide_ads': hide_ads,
+ 'key_color': key_color,
+ 'lang': language,
+ 'link_type': content_options,
+ 'over_18': over_18,
+ 'public_description': public_description,
+ 'public_traffic': public_traffic,
+ 'show_media': show_media,
+ 'submit_link_label': submit_link_label or '',
+ 'submit_text': submit_text,
+ 'submit_text_label': submit_text_label or '',
+ 'suggested_comment_sort': suggested_comment_sort or '',
+ 'spam_comments': spam_comments,
+ 'spam_links': spam_links,
+ 'spam_selfposts': spam_selfposts,
+ 'title': title,
+ 'type': subreddit_type,
+ 'wiki_edit_age': six.text_type(wiki_edit_age),
+ 'wiki_edit_karma': six.text_type(wiki_edit_karma),
+ 'wikimode': wikimode}
+
+ if kwargs:
+ msg = 'Extra settings fields: {0}'.format(kwargs.keys())
+ warn_explicit(msg, UserWarning, '', 0)
+ data.update(kwargs)
+ evict = self.config['subreddit_settings'].format(
+ subreddit=six.text_type(subreddit))
+ self.evict(evict)
+ return self.request_json(self.config['site_admin'], data=data)
+
+ @decorators.restrict_access(scope='modconfig')
+ def set_stylesheet(self, subreddit, stylesheet):
+ """Set stylesheet for the given subreddit.
+
+ :returns: The json response from the server.
+
+ """
+ subreddit = six.text_type(subreddit)
+ data = {'r': subreddit,
+ 'stylesheet_contents': stylesheet,
+ 'op': 'save'} # Options: save / preview
+ self.evict(self.config['stylesheet'].format(subreddit=subreddit))
+ return self.request_json(self.config['subreddit_css'], data=data)
+
+ @decorators.restrict_access(scope='modconfig')
+ def upload_image(self, subreddit, image_path, name=None,
+ header=False, upload_as=None):
+ """Upload an image to the subreddit.
+
+ :param image_path: A path to the jpg or png image you want to upload.
+ :param name: The name to provide the image. When None the name will be
+ filename less any extension.
+ :param header: When True, upload the image as the subreddit header.
+ :param upload_as: Must be `'jpg'`, `'png'` or `None`. When None, this
+ will match the format of the image itself. In all cases where both
+ this value and the image format is not png, reddit will also
+ convert the image mode to RGBA. reddit optimizes the image
+ according to this value.
+ :returns: A link to the uploaded image. Raises an exception otherwise.
+
+ """
+ if name and header:
+ raise TypeError('Both name and header cannot be set.')
+ if upload_as not in (None, 'png', 'jpg'):
+ raise TypeError("upload_as must be 'jpg', 'png', or None.")
+ with open(image_path, 'rb') as image:
+ image_type = upload_as or _image_type(image)
+ data = {'r': six.text_type(subreddit), 'img_type': image_type}
+ if header:
+ data['header'] = 1
+ else:
+ if not name:
+ name = os.path.splitext(os.path.basename(image.name))[0]
+ data['name'] = name
+
+ response = json.loads(self._request(
+ self.config['upload_image'], data=data, files={'file': image},
+ method=to_native_string('POST'), retry_on_error=False))
+
+ if response['errors']:
+ raise errors.APIException(response['errors'], None)
+ return response['img_src']
+
+ def update_settings(self, subreddit, **kwargs):
+ """Update only the given settings for the given subreddit.
+
+ The settings to update must be given by keyword and match one of the
+ parameter names in `set_settings`.
+
+ :returns: The json response from the server.
+
+ """
+ settings = self.get_settings(subreddit)
+ settings.update(kwargs)
+ del settings['subreddit_id']
+ return self.set_settings(subreddit, **settings)
+
+
+class ModFlairMixin(AuthenticatedReddit):
+ """Adds methods requiring the 'modflair' scope (or mod access).
+
+ You should **not** directly instantiate instances of this class. Use
+ :class:`.Reddit` instead.
+
+ """
+
+ @decorators.restrict_access(scope='modflair')
+ def add_flair_template(self, subreddit, text='', css_class='',
+ text_editable=False, is_link=False):
+ """Add a flair template to the given subreddit.
+
+ :returns: The json response from the server.
+
+ """
+ data = {'r': six.text_type(subreddit),
+ 'text': text,
+ 'css_class': css_class,
+ 'text_editable': six.text_type(text_editable),
+ 'flair_type': 'LINK_FLAIR' if is_link else 'USER_FLAIR'}
+ return self.request_json(self.config['flairtemplate'], data=data)
+
+ @decorators.restrict_access(scope='modflair')
+ def clear_flair_templates(self, subreddit, is_link=False):
+ """Clear flair templates for the given subreddit.
+
+ :returns: The json response from the server.
+
+ """
+ data = {'r': six.text_type(subreddit),
+ 'flair_type': 'LINK_FLAIR' if is_link else 'USER_FLAIR'}
+ return self.request_json(self.config['clearflairtemplates'], data=data)
+
+ @decorators.restrict_access(scope='modflair')
+ def configure_flair(self, subreddit, flair_enabled=False,
+ flair_position='right',
+ flair_self_assign=False,
+ link_flair_enabled=False,
+ link_flair_position='left',
+ link_flair_self_assign=False):
+ """Configure the flair setting for the given subreddit.
+
+ :returns: The json response from the server.
+
+ """
+ flair_enabled = 'on' if flair_enabled else 'off'
+ flair_self_assign = 'on' if flair_self_assign else 'off'
+ if not link_flair_enabled:
+ link_flair_position = ''
+ link_flair_self_assign = 'on' if link_flair_self_assign else 'off'
+ data = {'r': six.text_type(subreddit),
+ 'flair_enabled': flair_enabled,
+ 'flair_position': flair_position,
+ 'flair_self_assign_enabled': flair_self_assign,
+ 'link_flair_position': link_flair_position,
+ 'link_flair_self_assign_enabled': link_flair_self_assign}
+ return self.request_json(self.config['flairconfig'], data=data)
+
+ @decorators.restrict_access(scope='modflair')
+ def delete_flair(self, subreddit, user):
+ """Delete the flair for the given user on the given subreddit.
+
+ :returns: The json response from the server.
+
+ """
+ data = {'r': six.text_type(subreddit),
+ 'name': six.text_type(user)}
+ return self.request_json(self.config['deleteflair'], data=data)
+
+ @decorators.restrict_access(scope='modflair')
+ def get_flair_list(self, subreddit, *args, **kwargs):
+ """Return a get_content generator of flair mappings.
+
+ :param subreddit: Either a Subreddit object or the name of the
+ subreddit to return the flair list for.
+
+ The additional parameters are passed directly into
+ :meth:`.get_content`. Note: the `url`, `root_field`, `thing_field`, and
+ `after_field` parameters cannot be altered.
+
+ """
+ url = self.config['flairlist'].format(
+ subreddit=six.text_type(subreddit))
+ return self.get_content(url, *args, root_field=None,
+ thing_field='users', after_field='next',
+ **kwargs)
+
+ @decorators.restrict_access(scope='modflair')
+ def set_flair(self, subreddit, item, flair_text='', flair_css_class=''):
+ """Set flair for the user in the given subreddit.
+
+ `item` can be a string, Redditor object, or Submission object.
+ If `item` is a string it will be treated as the name of a Redditor.
+
+ This method can only be called by a subreddit moderator with flair
+ permissions. To set flair on yourself or your own links use
+ :meth:`~praw.__init__.AuthenticatedReddit.select_flair`.
+
+ :returns: The json response from the server.
+
+ """
+ data = {'r': six.text_type(subreddit),
+ 'text': flair_text or '',
+ 'css_class': flair_css_class or ''}
+ if isinstance(item, objects.Submission):
+ data['link'] = item.fullname
+ evict = item.permalink
+ else:
+ data['name'] = six.text_type(item)
+ evict = self.config['flairlist'].format(
+ subreddit=six.text_type(subreddit))
+ response = self.request_json(self.config['flair'], data=data)
+ self.evict(evict)
+ return response
+
+ @decorators.restrict_access(scope='modflair')
+ def set_flair_csv(self, subreddit, flair_mapping):
+ """Set flair for a group of users in the given subreddit.
+
+ flair_mapping should be a list of dictionaries with the following keys:
+ `user`: the user name,
+ `flair_text`: the flair text for the user (optional),
+ `flair_css_class`: the flair css class for the user (optional)
+
+ :returns: The json response from the server.
+
+ """
+ if not flair_mapping:
+ raise errors.ClientException('flair_mapping must be set')
+ item_order = ['user', 'flair_text', 'flair_css_class']
+ lines = []
+ for mapping in flair_mapping:
+ if 'user' not in mapping:
+ raise errors.ClientException('flair_mapping must '
+ 'contain `user` key')
+ lines.append(','.join([mapping.get(x, '') for x in item_order]))
+ response = []
+ while len(lines):
+ data = {'r': six.text_type(subreddit),
+ 'flair_csv': '\n'.join(lines[:100])}
+ response.extend(self.request_json(self.config['flaircsv'],
+ data=data))
+ lines = lines[100:]
+ evict = self.config['flairlist'].format(
+ subreddit=six.text_type(subreddit))
+ self.evict(evict)
+ return response
+
+
+class ModLogMixin(AuthenticatedReddit):
+ """Adds methods requiring the 'modlog' scope (or mod access).
+
+ You should **not** directly instantiate instances of this class. Use
+ :class:`.Reddit` instead.
+
+ """
+
+ @decorators.restrict_access(scope='modlog')
+ def get_mod_log(self, subreddit, mod=None, action=None, *args, **kwargs):
+ """Return a get_content generator for moderation log items.
+
+ :param subreddit: Either a Subreddit object or the name of the
+ subreddit to return the modlog for.
+ :param mod: If given, only return the actions made by this moderator.
+ Both a moderator name or Redditor object can be used here.
+ :param action: If given, only return entries for the specified action.
+
+ The additional parameters are passed directly into
+ :meth:`.get_content`. Note: the `url` parameter cannot be altered.
+
+ """
+ params = kwargs.setdefault('params', {})
+ if mod is not None:
+ params['mod'] = six.text_type(mod)
+ if action is not None:
+ params['type'] = six.text_type(action)
+ url = self.config['modlog'].format(subreddit=six.text_type(subreddit))
+ return self.get_content(url, *args, **kwargs)
+
+
+class ModOnlyMixin(AuthenticatedReddit):
+ """Adds methods requiring the logged in moderator access.
+
+ You should **not** directly instantiate instances of this class. Use
+ :class:`.Reddit` instead.
+
+ """
+
+ def _get_userlist(self, url, user_only, *args, **kwargs):
+ content = self.get_content(url, *args, **kwargs)
+ for data in content:
+ user = objects.Redditor(self, data['name'], fetch=False)
+ user.id = data['id'].split('_')[1]
+ if user_only:
+ yield user
+ else:
+ data['name'] = user
+ yield data
+
+ @decorators.restrict_access(scope='read', mod=True)
+ def get_banned(self, subreddit, user_only=True, *args, **kwargs):
+ """Return a get_content generator of banned users for the subreddit.
+
+ :param subreddit: The subreddit to get the banned user list for.
+ :param user_only: When False, the generator yields a dictionary of data
+ associated with the server response for that user. In such cases,
+ the Redditor will be in key 'name' (default: True).
+
+ """
+ url = self.config['banned'].format(subreddit=six.text_type(subreddit))
+ return self._get_userlist(url, user_only, *args, **kwargs)
+
+ def get_contributors(self, subreddit, *args, **kwargs):
+ """
+ Return a get_content generator of contributors for the given subreddit.
+
+ If it's a public subreddit, then authentication as a
+ moderator of the subreddit is required. For protected/private
+ subreddits only access is required. See issue #246.
+
+ """
+ # pylint: disable=W0613
+ def get_contributors_helper(self, subreddit):
+ # It is necessary to have the 'self' argument as it's needed in
+ # restrict_access to determine what class the decorator is
+ # operating on.
+ url = self.config['contributors'].format(
+ subreddit=six.text_type(subreddit))
+ return self._get_userlist(url, user_only=True, *args, **kwargs)
+
+ if self.is_logged_in():
+ if not isinstance(subreddit, objects.Subreddit):
+ subreddit = self.get_subreddit(subreddit)
+ if subreddit.subreddit_type == "public":
+ decorator = decorators.restrict_access(scope='read', mod=True)
+ return decorator(get_contributors_helper)(self, subreddit)
+ return get_contributors_helper(self, subreddit)
+
+ @decorators.restrict_access(scope='read', mod=True)
+ def get_edited(self, subreddit='mod', *args, **kwargs):
+ """Return a get_content generator of edited items.
+
+ :param subreddit: Either a Subreddit object or the name of the
+ subreddit to return the edited items for. Defaults to `mod` which
+ includes items for all the subreddits you moderate.
+
+ The additional parameters are passed directly into
+ :meth:`.get_content`. Note: the `url` parameter cannot be altered.
+
+ """
+ url = self.config['edited'].format(subreddit=six.text_type(subreddit))
+ return self.get_content(url, *args, **kwargs)
+
+ @decorators.restrict_access(scope='privatemessages', mod=True)
+ def get_mod_mail(self, subreddit='mod', *args, **kwargs):
+ """Return a get_content generator for moderator messages.
+
+ :param subreddit: Either a Subreddit object or the name of the
+ subreddit to return the moderator mail from. Defaults to `mod`
+ which includes items for all the subreddits you moderate.
+
+ The additional parameters are passed directly into
+ :meth:`.get_content`. Note: the `url` parameter cannot be altered.
+
+ """
+ url = self.config['mod_mail'].format(
+ subreddit=six.text_type(subreddit))
+ return self.get_content(url, *args, **kwargs)
+
+ @decorators.restrict_access(scope='read', mod=True)
+ def get_mod_queue(self, subreddit='mod', *args, **kwargs):
+ """Return a get_content generator for the moderator queue.
+
+ :param subreddit: Either a Subreddit object or the name of the
+ subreddit to return the modqueue for. Defaults to `mod` which
+ includes items for all the subreddits you moderate.
+
+ The additional parameters are passed directly into
+ :meth:`.get_content`. Note: the `url` parameter cannot be altered.
+
+ """
+ url = self.config['modqueue'].format(
+ subreddit=six.text_type(subreddit))
+ return self.get_content(url, *args, **kwargs)
+
+ @decorators.restrict_access(scope='read', mod=True)
+ def get_muted(self, subreddit, user_only=True, *args, **kwargs):
+ """Return a get_content generator for modmail-muted users.
+
+ :param subreddit: Either a Subreddit object or the name of a subreddit
+ to get the list of muted users from.
+
+ The additional parameters are passed directly into
+ :meth:`.get_content`. Note: the `url` parameter cannot be altered.
+
+ """
+ url = self.config['muted'].format(subreddit=six.text_type(subreddit))
+ return self._get_userlist(url, user_only, *args, **kwargs)
+
+ @decorators.restrict_access(scope='read', mod=True)
+ def get_reports(self, subreddit='mod', *args, **kwargs):
+ """Return a get_content generator of reported items.
+
+ :param subreddit: Either a Subreddit object or the name of the
+ subreddit to return the reported items. Defaults to `mod` which
+ includes items for all the subreddits you moderate.
+
+ The additional parameters are passed directly into
+ :meth:`.get_content`. Note: the `url` parameter cannot be altered.
+
+ """
+ url = self.config['reports'].format(subreddit=six.text_type(subreddit))
+ return self.get_content(url, *args, **kwargs)
+
+ @decorators.restrict_access(scope='read', mod=True)
+ def get_spam(self, subreddit='mod', *args, **kwargs):
+ """Return a get_content generator of spam-filtered items.
+
+ :param subreddit: Either a Subreddit object or the name of the
+ subreddit to return the spam-filtered items for. Defaults to `mod`
+ which includes items for all the subreddits you moderate.
+
+ The additional parameters are passed directly into
+ :meth:`.get_content`. Note: the `url` parameter cannot be altered.
+
+ """
+ url = self.config['spam'].format(subreddit=six.text_type(subreddit))
+ return self.get_content(url, *args, **kwargs)
+
+ @decorators.restrict_access('modconfig', mod=False, login=False)
+ def get_stylesheet(self, subreddit, **params):
+ """Return the stylesheet and images for the given subreddit."""
+ url = self.config['stylesheet'].format(
+ subreddit=six.text_type(subreddit))
+ return self.request_json(url, params=params)['data']
+
+ @decorators.restrict_access(scope='read', mod=True)
+ def get_unmoderated(self, subreddit='mod', *args, **kwargs):
+ """Return a get_content generator of unmoderated submissions.
+
+ :param subreddit: Either a Subreddit object or the name of the
+ subreddit to return the unmoderated submissions for. Defaults to
+ `mod` which includes items for all the subreddits you moderate.
+
+ The additional parameters are passed directly into
+ :meth:`.get_content`. Note: the `url` parameter cannot be altered.
+
+ """
+ url = self.config['unmoderated'].format(
+ subreddit=six.text_type(subreddit))
+ return self.get_content(url, *args, **kwargs)
+
+ @decorators.restrict_access(scope='read', mod=True)
+ def get_wiki_banned(self, subreddit, *args, **kwargs):
+ """Return a get_content generator of users banned from the wiki."""
+ url = self.config['wiki_banned'].format(
+ subreddit=six.text_type(subreddit))
+ return self._get_userlist(url, user_only=True, *args, **kwargs)
+
+ @decorators.restrict_access(scope='read', mod=True)
+ def get_wiki_contributors(self, subreddit, *args, **kwargs):
+ """Return a get_content generator of wiki contributors.
+
+ The returned users are those who have been approved as a wiki
+ contributor by the moderators of the subreddit, Whether or not they've
+ actually contributed to the wiki is irrellevant, their approval as wiki
+ contributors is all that matters.
+
+ """
+ url = self.config['wiki_contributors'].format(
+ subreddit=six.text_type(subreddit))
+ return self._get_userlist(url, user_only=True, *args, **kwargs)
+
+
+class ModSelfMixin(AuthenticatedReddit):
+ """Adds methods pertaining to the 'modself' OAuth scope (or login).
+
+ You should **not** directly instantiate instances of this class. Use
+ :class:`.Reddit` instead.
+
+ """
+
+ def leave_contributor(self, subreddit):
+ """Abdicate approved submitter status in a subreddit. Use with care.
+
+ :param subreddit: The name of the subreddit to leave `status` from.
+
+ :returns: the json response from the server.
+ """
+ return self._leave_status(subreddit, self.config['leavecontributor'])
+
+ def leave_moderator(self, subreddit):
+ """Abdicate moderator status in a subreddit. Use with care.
+
+ :param subreddit: The name of the subreddit to leave `status` from.
+
+ :returns: the json response from the server.
+ """
+ self.evict(self.config['my_mod_subreddits'])
+ return self._leave_status(subreddit, self.config['leavemoderator'])
+
+ @decorators.restrict_access(scope='modself', mod=False)
+ def _leave_status(self, subreddit, statusurl):
+ """Abdicate status in a subreddit.
+
+ :param subreddit: The name of the subreddit to leave `status` from.
+ :param statusurl: The API URL which will be used in the leave request.
+ Please use :meth:`leave_contributor` or :meth:`leave_moderator`
+ rather than setting this directly.
+
+ :returns: the json response from the server.
+ """
+ if isinstance(subreddit, six.string_types):
+ subreddit = self.get_subreddit(subreddit)
+
+ data = {'id': subreddit.fullname}
+ return self.request_json(statusurl, data=data)
+
+
+class MultiredditMixin(AuthenticatedReddit):
+ """Adds methods pertaining to multireddits.
+
+ You should **not** directly instantiate instances of this class. Use
+ :class:`.Reddit` instead.
+
+ """
+
+ MULTI_PATH = '/user/{0}/m/{1}'
+
+ @decorators.restrict_access(scope='subscribe')
+ def copy_multireddit(self, from_redditor, from_name, to_name=None,
+ *args, **kwargs):
+ """Copy a multireddit.
+
+ :param from_redditor: The username or Redditor object for the user
+ who owns the original multireddit
+ :param from_name: The name of the multireddit, belonging to
+ from_redditor
+ :param to_name: The name to copy the multireddit as. If None, uses
+ the name of the original
+
+ The additional parameters are passed directly into
+ :meth:`~praw.__init__.BaseReddit.request_json`
+
+ """
+ if to_name is None:
+ to_name = from_name
+
+ from_multipath = self.MULTI_PATH.format(from_redditor, from_name)
+ to_multipath = self.MULTI_PATH.format(self.user.name, to_name)
+ data = {'display_name': to_name,
+ 'from': from_multipath,
+ 'to': to_multipath}
+ return self.request_json(self.config['multireddit_copy'], data=data,
+ *args, **kwargs)
+
+ @decorators.restrict_access(scope='subscribe')
+ def create_multireddit(self, name, description_md=None, icon_name=None,
+ key_color=None, subreddits=None, visibility=None,
+ weighting_scheme=None, overwrite=False,
+ *args, **kwargs): # pylint: disable=W0613
+ """Create a new multireddit.
+
+ :param name: The name of the new multireddit.
+ :param description_md: Optional description for the multireddit,
+ formatted in markdown.
+ :param icon_name: Optional, choose an icon name from this list: ``art
+ and design``, ``ask``, ``books``, ``business``, ``cars``,
+ ``comics``, ``cute animals``, ``diy``, ``entertainment``, ``food
+ and drink``, ``funny``, ``games``, ``grooming``, ``health``, ``life
+ advice``, ``military``, ``models pinup``, ``music``, ``news``,
+ ``philosophy``, ``pictures and gifs``, ``science``, ``shopping``,
+ ``sports``, ``style``, ``tech``, ``travel``, ``unusual stories``,
+ ``video``, or ``None``.
+ :param key_color: Optional rgb hex color code of the form `#xxxxxx`.
+ :param subreddits: Optional list of subreddit names or Subreddit
+ objects to initialize the Multireddit with. You can always
+ add more later with
+ :meth:`~praw.objects.Multireddit.add_subreddit`.
+ :param visibility: Choose a privacy setting from this list:
+ ``public``, ``private``, ``hidden``. Defaults to private if blank.
+ :param weighting_scheme: Choose a weighting scheme from this list:
+ ``classic``, ``fresh``. Defaults to classic if blank.
+ :param overwrite: Allow for overwriting / updating multireddits.
+ If False, and the multi name already exists, throw 409 error.
+ If True, and the multi name already exists, use the given
+ properties to update that multi.
+ If True, and the multi name does not exist, create it normally.
+
+ :returns: The newly created Multireddit object.
+
+ The additional parameters are passed directly into
+ :meth:`~praw.__init__.BaseReddit.request_json`
+
+ """
+ url = self.config['multireddit_about'].format(user=self.user.name,
+ multi=name)
+ if subreddits:
+ subreddits = [{'name': six.text_type(sr)} for sr in subreddits]
+ model = {}
+ for key in ('description_md', 'icon_name', 'key_color', 'subreddits',
+ 'visibility', 'weighting_scheme'):
+ value = locals()[key]
+ if value:
+ model[key] = value
+
+ method = 'PUT' if overwrite else 'POST'
+ return self.request_json(url, data={'model': json.dumps(model)},
+ method=method, *args, **kwargs)
+
+ @decorators.restrict_access(scope='subscribe')
+ def delete_multireddit(self, name, *args, **kwargs):
+ """Delete a Multireddit.
+
+ Any additional parameters are passed directly into
+ :meth:`~praw.__init__.BaseReddit.request`
+
+ """
+ url = self.config['multireddit_about'].format(user=self.user.name,
+ multi=name)
+
+ # The modhash isn't necessary for OAuth requests
+ if not self._use_oauth:
+ self.http.headers['x-modhash'] = self.modhash
+
+ try:
+ self.request(url, data={}, method='DELETE', *args, **kwargs)
+ finally:
+ if not self._use_oauth:
+ del self.http.headers['x-modhash']
+
+ def edit_multireddit(self, *args, **kwargs):
+ """Edit a multireddit, or create one if it doesn't already exist.
+
+ See :meth:`create_multireddit` for accepted parameters.
+
+ """
+ return self.create_multireddit(*args, overwrite=True, **kwargs)
+
+ def get_multireddit(self, redditor, multi, *args, **kwargs):
+ """Return a Multireddit object for the author and name specified.
+
+ :param redditor: The username or Redditor object of the user
+ who owns the multireddit.
+ :param multi: The name of the multireddit to fetch.
+
+ The additional parameters are passed directly into the
+ :class:`.Multireddit` constructor.
+
+ """
+ return objects.Multireddit(self, six.text_type(redditor), multi,
+ *args, **kwargs)
+
+ def get_multireddits(self, redditor, *args, **kwargs):
+ """Return a list of multireddits belonging to a redditor.
+
+ :param redditor: The username or Redditor object to find multireddits
+ from.
+ :returns: The json response from the server
+
+ The additional parameters are passed directly into
+ :meth:`~praw.__init__.BaseReddit.request_json`
+
+ If the requested redditor is the current user, all multireddits
+ are visible. Otherwise, only public multireddits are returned.
+
+ """
+ redditor = six.text_type(redditor)
+ url = self.config['multireddit_user'].format(user=redditor)
+ return self.request_json(url, *args, **kwargs)
+
+ @decorators.restrict_access(scope='subscribe')
+ def rename_multireddit(self, current_name, new_name, *args, **kwargs):
+ """Rename a Multireddit.
+
+ :param current_name: The name of the multireddit to rename
+ :param new_name: The new name to assign to this multireddit
+
+ The additional parameters are passed directly into
+ :meth:`~praw.__init__.BaseReddit.request_json`
+
+ """
+ current_path = self.MULTI_PATH.format(self.user.name, current_name)
+ new_path = self.MULTI_PATH.format(self.user.name, new_name)
+ data = {'from': current_path,
+ 'to': new_path}
+ return self.request_json(self.config['multireddit_rename'], data=data,
+ *args, **kwargs)
+
+
+class MySubredditsMixin(AuthenticatedReddit):
+ """Adds methods requiring the 'mysubreddits' scope (or login).
+
+ You should **not** directly instantiate instances of this class. Use
+ :class:`.Reddit` instead.
+
+ """
+
+ @decorators.restrict_access(scope='mysubreddits')
+ def get_my_contributions(self, *args, **kwargs):
+ """Return a get_content generator of subreddits.
+
+ The Subreddits generated are those where the session's user is a
+ contributor.
+
+ The additional parameters are passed directly into
+ :meth:`.get_content`. Note: the `url` parameter cannot be altered.
+
+ """
+ return self.get_content(self.config['my_con_subreddits'], *args,
+ **kwargs)
+
+ @decorators.restrict_access(scope='mysubreddits')
+ def get_my_moderation(self, *args, **kwargs):
+ """Return a get_content generator of subreddits.
+
+ The Subreddits generated are those where the session's user is a
+ moderator.
+
+ The additional parameters are passed directly into
+ :meth:`.get_content`. Note: the `url` parameter cannot be altered.
+
+ """
+ return self.get_content(self.config['my_mod_subreddits'], *args,
+ **kwargs)
+
+ @decorators.restrict_access(scope='mysubreddits')
+ def get_my_multireddits(self):
+ """Return a list of the authenticated Redditor's Multireddits."""
+ # The JSON data for multireddits is returned from Reddit as a list
+ # Therefore, we cannot use :meth:`get_content` to retrieve the objects
+ return self.request_json(self.config['my_multis'])
+
+ @decorators.restrict_access(scope='mysubreddits')
+ def get_my_subreddits(self, *args, **kwargs):
+ """Return a get_content generator of subreddits.
+
+ The subreddits generated are those that hat the session's user is
+ subscribed to.
+
+ The additional parameters are passed directly into
+ :meth:`.get_content`. Note: the `url` parameter cannot be altered.
+
+ """
+ return self.get_content(self.config['my_subreddits'], *args, **kwargs)
+
+
+class PrivateMessagesMixin(AuthenticatedReddit):
+ """Adds methods requiring the 'privatemessages' scope (or login).
+
+ You should **not** directly instantiate instances of this class. Use
+ :class:`.Reddit` instead.
+
+ """
+
+ @decorators.restrict_access(scope='privatemessages')
+ def _mark_as_read(self, thing_ids, unread=False):
+ """Mark each of the supplied thing_ids as (un)read.
+
+ :returns: The json response from the server.
+
+ """
+ data = {'id': ','.join(thing_ids)}
+ key = 'unread_message' if unread else 'read_message'
+ response = self.request_json(self.config[key], data=data)
+ self.evict([self.config[x] for x in ['inbox', 'messages',
+ 'mod_mail', 'unread']])
+ return response
+
+ @decorators.restrict_access(scope='privatemessages')
+ def get_comment_replies(self, *args, **kwargs):
+ """Return a get_content generator for inboxed comment replies.
+
+ The additional parameters are passed directly into
+ :meth:`.get_content`. Note: the `url` parameter cannot be altered.
+
+ """
+ return self.get_content(self.config['comment_replies'],
+ *args, **kwargs)
+
+ @decorators.restrict_access(scope='privatemessages')
+ def get_inbox(self, *args, **kwargs):
+ """Return a get_content generator for inbox (messages and comments).
+
+ The additional parameters are passed directly into
+ :meth:`.get_content`. Note: the `url` parameter cannot be altered.
+
+ """
+ return self.get_content(self.config['inbox'], *args, **kwargs)
+
+ def get_message(self, message_id, *args, **kwargs):
+ """Return a Message object corresponding to the given ID.
+
+ :param message_id: The ID or Fullname for a Message
+
+ The additional parameters are passed directly into
+ :meth:`~praw.objects.Message.from_id` of Message, and subsequently into
+ :meth:`.request_json`.
+
+ """
+ return objects.Message.from_id(self, message_id, *args, **kwargs)
+
+ @decorators.restrict_access(scope='privatemessages')
+ def get_messages(self, *args, **kwargs):
+ """Return a get_content generator for inbox (messages only).
+
+ The additional parameters are passed directly into
+ :meth:`.get_content`. Note: the `url` parameter cannot be altered.
+
+ """
+ return self.get_content(self.config['messages'], *args, **kwargs)
+
+ @decorators.restrict_access(scope='privatemessages')
+ def get_post_replies(self, *args, **kwargs):
+ """Return a get_content generator for inboxed submission replies.
+
+ The additional parameters are passed directly into
+ :meth:`.get_content`. Note: the `url` parameter cannot be altered.
+
+ """
+ return self.get_content(self.config['post_replies'], *args, **kwargs)
+
+ @decorators.restrict_access(scope='privatemessages')
+ def get_sent(self, *args, **kwargs):
+ """Return a get_content generator for sent messages.
+
+ The additional parameters are passed directly into
+ :meth:`.get_content`. Note: the `url` parameter cannot be altered.
+
+ """
+ return self.get_content(self.config['sent'], *args, **kwargs)
+
+ @decorators.restrict_access(scope='privatemessages')
+ def get_unread(self, unset_has_mail=False, update_user=False, *args,
+ **kwargs):
+ """Return a get_content generator for unread messages.
+
+ :param unset_has_mail: When True, clear the has_mail flag (orangered)
+ for the user.
+ :param update_user: If both `unset_has_mail` and `update user` is True,
+ set the `has_mail` attribute of the logged-in user to False.
+
+ The additional parameters are passed directly into
+ :meth:`.get_content`. Note: the `url` parameter cannot be altered.
+
+ """
+ params = kwargs.setdefault('params', {})
+ if unset_has_mail:
+ params['mark'] = 'true'
+ if update_user: # Update the user object
+ # Use setattr to avoid pylint error
+ setattr(self.user, 'has_mail', False)
+ return self.get_content(self.config['unread'], *args, **kwargs)
+
+ @decorators.restrict_access(scope='privatemessages')
+ def get_mentions(self, *args, **kwargs):
+ """Return a get_content generator for username mentions.
+
+ The additional parameters are passed directly into
+ :meth:`.get_content`. Note: the `url` parameter cannot be altered.
+
+ """
+ return self.get_content(self.config['mentions'], *args, **kwargs)
+
+ @decorators.restrict_access(scope='privatemessages')
+ @decorators.require_captcha
+ def send_message(self, recipient, subject, message, from_sr=None,
+ captcha=None, **kwargs):
+ """Send a message to a redditor or a subreddit's moderators (mod mail).
+
+ :param recipient: A Redditor or Subreddit instance to send a message
+ to. A string can also be used in which case the string is treated
+ as a redditor unless it is prefixed with either '/r/' or '#', in
+ which case it will be treated as a subreddit.
+ :param subject: The subject of the message to send.
+ :param message: The actual message content.
+ :param from_sr: A Subreddit instance or string to send the message
+ from. When provided, messages are sent from the subreddit rather
+ than from the authenticated user. Note that the authenticated user
+ must be a moderator of the subreddit and have mail permissions.
+
+ :returns: The json response from the server.
+
+ This function may result in a captcha challenge. PRAW will
+ automatically prompt you for a response. See :ref:`handling-captchas`
+ if you want to manually handle captchas.
+
+ """
+ if isinstance(recipient, objects.Subreddit):
+ recipient = '/r/{0}'.format(six.text_type(recipient))
+ else:
+ recipient = six.text_type(recipient)
+
+ data = {'text': message,
+ 'subject': subject,
+ 'to': recipient}
+ if from_sr:
+ data['from_sr'] = six.text_type(from_sr)
+ if captcha:
+ data.update(captcha)
+ response = self.request_json(self.config['compose'], data=data,
+ retry_on_error=False)
+ self.evict(self.config['sent'])
+ return response
+
+
+class ReportMixin(AuthenticatedReddit):
+ """Adds methods requiring the 'report' scope (or login).
+
+ You should **not** directly instantiate instances of this class. Use
+ :class:`.Reddit` instead.
+
+ """
+
+ @decorators.restrict_access(scope='report')
+ def hide(self, thing_id, _unhide=False):
+ """Hide one or multiple objects in the context of the logged in user.
+
+ :param thing_id: A single fullname or list of fullnames,
+ representing objects which will be hidden.
+ :param _unhide: If True, unhide the object(s) instead. Use
+ :meth:`~praw.__init__.ReportMixin.unhide` rather than setting this
+ manually.
+
+ :returns: The json response from the server.
+
+ """
+ if isinstance(thing_id, six.string_types):
+ thing_id = [thing_id]
+ else:
+ # Guarantee a subscriptable type.
+ thing_id = list(thing_id)
+
+ if len(thing_id) == 0:
+ raise ValueError('No fullnames provided')
+
+ # Will we return a list of server responses, or just one?
+ # TODO: In future versions, change the threshold to 1 to get
+ # list-in-list-out, single-in-single-out behavior. Threshold of 50
+ # is to avoid a breaking change at this time.
+ return_list = len(thing_id) > 50
+
+ id_chunks = chunk_sequence(thing_id, 50)
+ responses = []
+ for id_chunk in id_chunks:
+ id_chunk = ','.join(id_chunk)
+
+ method = 'unhide' if _unhide else 'hide'
+ data = {'id': id_chunk,
+ 'executed': method}
+
+ response = self.request_json(self.config[method], data=data)
+ responses.append(response)
+
+ if self.user is not None:
+ self.evict(urljoin(self.user._url, # pylint: disable=W0212
+ 'hidden'))
+ if return_list:
+ return responses
+ else:
+ return responses[0]
+
+ def unhide(self, thing_id):
+ """Unhide up to 50 objects in the context of the logged in user.
+
+ :param thing_id: A single fullname or list of fullnames,
+ representing objects which will be unhidden.
+
+ :returns: The json response from the server.
+
+ """
+ return self.hide(thing_id, _unhide=True)
+
+
+class SubmitMixin(AuthenticatedReddit):
+ """Adds methods requiring the 'submit' scope (or login).
+
+ You should **not** directly instantiate instances of this class. Use
+ :class:`.Reddit` instead.
+
+ """
+
+ def _add_comment(self, thing_id, text):
+ """Comment on the given thing with the given text.
+
+ :returns: A Comment object for the newly created comment.
+
+ """
+ def add_comment_helper(self, thing_id, text):
+ data = {'thing_id': thing_id,
+ 'text': text}
+ retval = self.request_json(self.config['comment'], data=data,
+ retry_on_error=False)
+ return retval
+
+ if thing_id.startswith(self.config.by_object[objects.Message]):
+ decorator = decorators.restrict_access(scope='privatemessages')
+ else:
+ decorator = decorators.restrict_access(scope='submit')
+ retval = decorator(add_comment_helper)(self, thing_id, text)
+ # REDDIT: reddit's end should only ever return a single comment
+ return retval['data']['things'][0]
+
+ @decorators.restrict_access(scope='submit')
+ @decorators.require_captcha
+ def submit(self, subreddit, title, text=None, url=None, captcha=None,
+ save=None, send_replies=None, resubmit=None, **kwargs):
+ """Submit a new link to the given subreddit.
+
+ Accepts either a Subreddit object or a str containing the subreddit's
+ display name.
+
+ :param resubmit: If True, submit the link even if it has already been
+ submitted.
+ :param save: If True the new Submission will be saved after creation.
+ :param send_replies: If True, inbox replies will be received when
+ people comment on the submission. If set to None, the default of
+ True for text posts and False for link posts will be used.
+
+ :returns: The newly created Submission object if the reddit instance
+ can access it. Otherwise, return the url to the submission.
+
+ This function may result in a captcha challenge. PRAW will
+ automatically prompt you for a response. See :ref:`handling-captchas`
+ if you want to manually handle captchas.
+
+ """
+ if isinstance(text, six.string_types) == bool(url):
+ raise TypeError('One (and only one) of text or url is required!')
+ data = {'sr': six.text_type(subreddit),
+ 'title': title}
+ if text or text == '':
+ data['kind'] = 'self'
+ data['text'] = text
+ else:
+ data['kind'] = 'link'
+ data['url'] = url
+ if captcha:
+ data.update(captcha)
+ if resubmit is not None:
+ data['resubmit'] = resubmit
+ if save is not None:
+ data['save'] = save
+ if send_replies is not None:
+ data['sendreplies'] = send_replies
+ result = self.request_json(self.config['submit'], data=data,
+ retry_on_error=False)
+ url = result['data']['url']
+ # Clear the OAuth setting when attempting to fetch the submission
+ if self._use_oauth:
+ self._use_oauth = False
+ if url.startswith(self.config.oauth_url):
+ url = self.config.api_url + url[len(self.config.oauth_url):]
+ try:
+ return self.get_submission(url)
+ except errors.Forbidden:
+ # While the user may be able to submit to a subreddit,
+ # that does not guarantee they have read access.
+ return url
+
+
+class SubscribeMixin(AuthenticatedReddit):
+ """Adds methods requiring the 'subscribe' scope (or login).
+
+ You should **not** directly instantiate instances of this class. Use
+ :class:`.Reddit` instead.
+
+ """
+
+ @decorators.restrict_access(scope='subscribe')
+ def subscribe(self, subreddit, unsubscribe=False):
+ """Subscribe to the given subreddit.
+
+ :param subreddit: Either the subreddit name or a subreddit object.
+ :param unsubscribe: When True, unsubscribe.
+ :returns: The json response from the server.
+
+ """
+ data = {'action': 'unsub' if unsubscribe else 'sub',
+ 'sr_name': six.text_type(subreddit)}
+ response = self.request_json(self.config['subscribe'], data=data)
+ self.evict(self.config['my_subreddits'])
+ return response
+
+ def unsubscribe(self, subreddit):
+ """Unsubscribe from the given subreddit.
+
+ :param subreddit: Either the subreddit name or a subreddit object.
+ :returns: The json response from the server.
+
+ """
+ return self.subscribe(subreddit, unsubscribe=True)
+
+
+class Reddit(ModConfigMixin, ModFlairMixin, ModLogMixin, ModOnlyMixin,
+ ModSelfMixin, MultiredditMixin, MySubredditsMixin,
+ PrivateMessagesMixin, ReportMixin, SubmitMixin, SubscribeMixin):
+ """Provides access to reddit's API.
+
+ See :class:`.BaseReddit`'s documentation for descriptions of the
+ initialization parameters.
+
+ """
+
+# Prevent recursive import
+from . import objects # NOQA
diff --git a/ttrv/packages/praw/decorator_helpers.py b/ttrv/packages/praw/decorator_helpers.py
new file mode 100644
index 0000000..375dca8
--- /dev/null
+++ b/ttrv/packages/praw/decorator_helpers.py
@@ -0,0 +1,38 @@
+"""Internal helper functions used by praw.decorators."""
+import inspect
+from requests.compat import urljoin
+import six
+import sys
+
+
+def _get_captcha(reddit_session, captcha_id):
+ """Prompt user for captcha solution and return a prepared result."""
+ url = urljoin(reddit_session.config['captcha'],
+ captcha_id + '.png')
+ sys.stdout.write('Captcha URL: {0}\nCaptcha: '.format(url))
+ sys.stdout.flush()
+ raw = sys.stdin.readline()
+ if not raw: # stdin has reached the end of file
+ # Trigger exception raising next time through. The request is
+ # cached so this will not require and extra request and delay.
+ sys.stdin.close()
+ return None
+ return {'iden': captcha_id, 'captcha': raw.strip()}
+
+
+def _is_mod_of_all(user, subreddit):
+ mod_subs = user.get_cached_moderated_reddits()
+ subs = six.text_type(subreddit).lower().split('+')
+ return all(sub in mod_subs for sub in subs)
+
+
+def _make_func_args(function):
+ if six.PY3 and not hasattr(sys, 'pypy_version_info'):
+ # CPython3 uses inspect.signature(), not inspect.getargspec()
+ # see #551 and #541 for more info
+ func_items = inspect.signature(function).parameters.items()
+ func_args = [name for name, param in func_items
+ if param.kind == param.POSITIONAL_OR_KEYWORD]
+ else:
+ func_args = inspect.getargspec(function).args
+ return func_args
diff --git a/ttrv/packages/praw/decorators.py b/ttrv/packages/praw/decorators.py
new file mode 100644
index 0000000..77bffab
--- /dev/null
+++ b/ttrv/packages/praw/decorators.py
@@ -0,0 +1,294 @@
+# This file is part of PRAW.
+#
+# PRAW is free software: you can redistribute it and/or modify it under the
+# terms of the GNU General Public License as published by the Free Software
+# Foundation, either version 3 of the License, or (at your option) any later
+# version.
+#
+# PRAW is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# PRAW. If not, see .
+
+"""
+Decorators.
+
+They mainly do two things: ensure API guidelines are followed and
+prevent unnecessary failed API requests by testing that the call can be made
+first. Also, they can limit the length of output strings and parse json
+response for certain errors.
+"""
+
+from __future__ import print_function, unicode_literals
+
+import decorator
+import six
+import sys
+from functools import wraps
+from .decorator_helpers import (
+ _get_captcha,
+ _is_mod_of_all,
+ _make_func_args
+)
+from . import errors
+from warnings import filterwarnings, warn
+
+
+# Enable deprecation warnings from this module
+filterwarnings('default', category=DeprecationWarning,
+ module='^praw\.decorators$')
+
+
+def alias_function(function, class_name):
+ """Create a RedditContentObject function mapped to a BaseReddit function.
+
+ The BaseReddit classes define the majority of the API's functions. The
+ first argument for many of these functions is the RedditContentObject that
+ they operate on. This factory returns functions appropriate to be called on
+ a RedditContent object that maps to the corresponding BaseReddit function.
+
+ """
+ @wraps(function)
+ def wrapped(self, *args, **kwargs):
+ func_args = _make_func_args(function)
+ if 'subreddit' in func_args and func_args.index('subreddit') != 1:
+ # Only happens for search
+ kwargs['subreddit'] = self
+ return function(self.reddit_session, *args, **kwargs)
+ else:
+ return function(self.reddit_session, self, *args, **kwargs)
+ # Only grab the short-line doc and add a link to the complete doc
+ if wrapped.__doc__ is not None:
+ wrapped.__doc__ = wrapped.__doc__.split('\n', 1)[0]
+ wrapped.__doc__ += ('\n\nSee :meth:`.{0}.{1}` for complete usage. '
+ 'Note that you should exclude the subreddit '
+ 'parameter when calling this convenience method.'
+ .format(class_name, function.__name__))
+ # Don't hide from sphinx as this is a parameter modifying decorator
+ return wrapped
+
+
+def deprecated(msg=''):
+ """Deprecate decorated method."""
+ @decorator.decorator
+ def wrap(function, *args, **kwargs):
+ if not kwargs.pop('disable_warning', False):
+ warn(msg, DeprecationWarning)
+ return function(*args, **kwargs)
+ return wrap
+
+
+@decorator.decorator
+def limit_chars(function, *args, **kwargs):
+ """Truncate the string returned from a function and return the result."""
+ output_chars_limit = args[0].reddit_session.config.output_chars_limit
+ output_string = function(*args, **kwargs)
+ if -1 < output_chars_limit < len(output_string):
+ output_string = output_string[:output_chars_limit - 3] + '...'
+ return output_string
+
+
+@decorator.decorator
+def oauth_generator(function, *args, **kwargs):
+ """Set the _use_oauth keyword argument to True when appropriate.
+
+ This is needed because generator functions may be called at anytime, and
+ PRAW relies on the Reddit._use_oauth value at original call time to know
+ when to make OAuth requests.
+
+ Returned data is not modified.
+
+ """
+ if getattr(args[0], '_use_oauth', False):
+ kwargs['_use_oauth'] = True
+ return function(*args, **kwargs)
+
+
+@decorator.decorator
+def raise_api_exceptions(function, *args, **kwargs):
+ """Raise client side exception(s) when present in the API response.
+
+ Returned data is not modified.
+
+ """
+ try:
+ return_value = function(*args, **kwargs)
+ except errors.HTTPException as exc:
+ if exc._raw.status_code != 400: # pylint: disable=W0212
+ raise # Unhandled HTTPErrors
+ try: # Attempt to convert v1 errors into older format (for now)
+ data = exc._raw.json() # pylint: disable=W0212
+ assert len(data) == 2
+ return_value = {'errors': [(data['reason'],
+ data['explanation'], '')]}
+ except Exception:
+ raise exc
+ if isinstance(return_value, dict):
+ if return_value.get('error') == 304: # Not modified exception
+ raise errors.NotModified(return_value)
+ elif return_value.get('errors'):
+ error_list = []
+ for error_type, msg, value in return_value['errors']:
+ if error_type in errors.ERROR_MAPPING:
+ if error_type == 'RATELIMIT':
+ args[0].evict(args[1])
+ error_class = errors.ERROR_MAPPING[error_type]
+ else:
+ error_class = errors.APIException
+ error_list.append(error_class(error_type, msg, value,
+ return_value))
+ if len(error_list) == 1:
+ raise error_list[0]
+ else:
+ raise errors.ExceptionList(error_list)
+ return return_value
+
+
+@decorator.decorator
+def require_captcha(function, *args, **kwargs):
+ """Return a decorator for methods that require captchas."""
+ raise_captcha_exception = kwargs.pop('raise_captcha_exception', False)
+ captcha_id = None
+
+ # Get a handle to the reddit session
+ if hasattr(args[0], 'reddit_session'):
+ reddit_session = args[0].reddit_session
+ else:
+ reddit_session = args[0]
+
+ while True:
+ try:
+ if captcha_id:
+ captcha_answer = _get_captcha(reddit_session, captcha_id)
+
+ # When the method is being decorated, all of its default
+ # parameters become part of this *args tuple. This means that
+ # *args currently contains a None where the captcha answer
+ # needs to go. If we put the captcha in the **kwargs,
+ # we get a TypeError for having two values of the same param.
+ func_args = _make_func_args(function)
+ if 'captcha' in func_args:
+ captcha_index = func_args.index('captcha')
+ args = list(args)
+ args[captcha_index] = captcha_answer
+ else:
+ kwargs['captcha'] = captcha_answer
+ return function(*args, **kwargs)
+ except errors.InvalidCaptcha as exception:
+ if raise_captcha_exception or \
+ not hasattr(sys.stdin, 'closed') or sys.stdin.closed:
+ raise
+ captcha_id = exception.response['captcha']
+
+
+def restrict_access(scope, mod=None, login=None, oauth_only=False,
+ generator_called=False):
+ """Restrict function access unless the user has the necessary permissions.
+
+ Raises one of the following exceptions when appropriate:
+ * LoginRequired
+ * LoginOrOAuthRequired
+ * the scope attribute will provide the necessary scope name
+ * ModeratorRequired
+ * ModeratorOrOAuthRequired
+ * the scope attribute will provide the necessary scope name
+
+ :param scope: Indicate the scope that is required for the API call. None or
+ False must be passed to indicate that no scope handles the API call.
+ All scopes save for `read` imply login=True. Scopes with 'mod' in their
+ name imply mod=True.
+ :param mod: Indicate that a moderator is required. Implies login=True.
+ :param login: Indicate that a login is required.
+ :param oauth_only: Indicate that only OAuth is supported for the function.
+ :param generator_called: Indicate that the function consists solely of
+ exhausting one or more oauth_generator wrapped generators. This is
+ because the oauth_generator itself will determine whether or not to
+ use the oauth domain.
+
+ Returned data is not modified.
+
+ This decorator assumes that all mod required functions fit one of these
+ categories:
+
+ * have the subreddit as the first argument (Reddit instance functions) or
+ have a subreddit keyword argument
+ * are called upon a subreddit object (Subreddit RedditContentObject)
+ * are called upon a RedditContent object with attribute subreddit
+
+ """
+ if not scope and oauth_only:
+ raise TypeError('`scope` must be set when `oauth_only` is set')
+
+ mod = mod is not False and (mod or scope and 'mod' in scope)
+ login = login is not False and (login or mod or scope and scope != 'read')
+
+ @decorator.decorator
+ def wrap(function, *args, **kwargs):
+ if args[0] is None: # Occurs with (un)friend
+ assert login
+ raise errors.LoginRequired(function.__name__)
+ # This segment of code uses hasattr to determine what instance type
+ # the function was called on. We could use isinstance if we wanted
+ # to import the types at runtime (decorators is used by all the
+ # types).
+ if mod:
+ if hasattr(args[0], 'reddit_session'):
+ # Defer access until necessary for RedditContentObject.
+ # This is because scoped sessions may not require this
+ # attribute to exist, thus it might not be set.
+ from .objects import Subreddit
+ subreddit = args[0] if isinstance(args[0], Subreddit) \
+ else False
+ else:
+ subreddit = kwargs.get(
+ 'subreddit', args[1] if len(args) > 1 else None)
+ if subreddit is None: # Try the default value
+ defaults = six.get_function_defaults(function)
+ subreddit = defaults[0] if defaults else None
+ else:
+ subreddit = None
+
+ obj = getattr(args[0], 'reddit_session', args[0])
+ # This function sets _use_oauth for one time use only.
+ # Verify that statement is actually true.
+ assert not obj._use_oauth # pylint: disable=W0212
+
+ if scope and obj.has_scope(scope):
+ obj._use_oauth = not generator_called # pylint: disable=W0212
+ elif oauth_only:
+ raise errors.OAuthScopeRequired(function.__name__, scope)
+ elif login and obj.is_logged_in():
+ if subreddit is False:
+ # Now fetch the subreddit attribute. There is no good
+ # reason for it to not be set during a logged in session.
+ subreddit = args[0].subreddit
+ if mod and not _is_mod_of_all(obj.user, subreddit):
+ if scope:
+ raise errors.ModeratorOrScopeRequired(
+ function.__name__, scope)
+ raise errors.ModeratorRequired(function.__name__)
+ elif login:
+ if scope:
+ raise errors.LoginOrScopeRequired(function.__name__, scope)
+ raise errors.LoginRequired(function.__name__)
+ try:
+ return function(*args, **kwargs)
+ finally:
+ obj._use_oauth = False # pylint: disable=W0212
+ return wrap
+
+
+@decorator.decorator
+def require_oauth(function, *args, **kwargs):
+ """Verify that the OAuth functions can be used prior to use.
+
+ Returned data is not modified.
+
+ """
+ if not args[0].has_oauth_app_info:
+ err_msg = ("The OAuth app config parameters client_id, client_secret "
+ "and redirect_url must be specified to use this function.")
+ raise errors.OAuthAppRequired(err_msg)
+ return function(*args, **kwargs)
diff --git a/ttrv/packages/praw/errors.py b/ttrv/packages/praw/errors.py
new file mode 100644
index 0000000..ca64874
--- /dev/null
+++ b/ttrv/packages/praw/errors.py
@@ -0,0 +1,475 @@
+# This file is part of PRAW.
+#
+# PRAW is free software: you can redistribute it and/or modify it under the
+# terms of the GNU General Public License as published by the Free Software
+# Foundation, either version 3 of the License, or (at your option) any later
+# version.
+#
+# PRAW is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# PRAW. If not, see .
+
+"""
+Error classes.
+
+Includes two main exceptions: ClientException, when something goes
+wrong on our end, and APIExeception for when something goes wrong on the
+server side. A number of classes extend these two main exceptions for more
+specific exceptions.
+"""
+
+from __future__ import print_function, unicode_literals
+
+import inspect
+import six
+import sys
+
+
+class PRAWException(Exception):
+ """The base PRAW Exception class.
+
+ Ideally, this can be caught to handle any exception from PRAW.
+
+ """
+
+
+class ClientException(PRAWException):
+ """Base exception class for errors that don't involve the remote API."""
+
+ def __init__(self, message=None):
+ """Construct a ClientException.
+
+ :param message: The error message to display.
+
+ """
+ if not message:
+ message = 'Clientside error'
+ super(ClientException, self).__init__()
+ self.message = message
+
+ def __str__(self):
+ """Return the message of the error."""
+ return self.message
+
+
+class OAuthScopeRequired(ClientException):
+ """Indicates that an OAuth2 scope is required to make the function call.
+
+ The attribute `scope` will contain the name of the necessary scope.
+
+ """
+
+ def __init__(self, function, scope, message=None):
+ """Contruct an OAuthScopeRequiredClientException.
+
+ :param function: The function that requires a scope.
+ :param scope: The scope required for the function.
+ :param message: A custom message to associate with the
+ exception. Default: `function` requires the OAuth2 scope `scope`
+
+ """
+ if not message:
+ message = '`{0}` requires the OAuth2 scope `{1}`'.format(function,
+ scope)
+ super(OAuthScopeRequired, self).__init__(message)
+ self.scope = scope
+
+
+class LoginRequired(ClientException):
+ """Indicates that a logged in session is required.
+
+ This exception is raised on a preemptive basis, whereas NotLoggedIn occurs
+ in response to a lack of credentials on a privileged API call.
+
+ """
+
+ def __init__(self, function, message=None):
+ """Construct a LoginRequired exception.
+
+ :param function: The function that requires login-based authentication.
+ :param message: A custom message to associate with the exception.
+ Default: `function` requires a logged in session
+
+ """
+ if not message:
+ message = '`{0}` requires a logged in session'.format(function)
+ super(LoginRequired, self).__init__(message)
+
+
+class LoginOrScopeRequired(OAuthScopeRequired, LoginRequired):
+ """Indicates that either a logged in session or OAuth2 scope is required.
+
+ The attribute `scope` will contain the name of the necessary scope.
+
+ """
+
+ def __init__(self, function, scope, message=None):
+ """Construct a LoginOrScopeRequired exception.
+
+ :param function: The function that requires authentication.
+ :param scope: The scope that is required if not logged in.
+ :param message: A custom message to associate with the exception.
+ Default: `function` requires a logged in session or the OAuth2
+ scope `scope`
+
+ """
+ if not message:
+ message = ('`{0}` requires a logged in session or the '
+ 'OAuth2 scope `{1}`').format(function, scope)
+ super(LoginOrScopeRequired, self).__init__(function, scope, message)
+
+
+class ModeratorRequired(LoginRequired):
+ """Indicates that a moderator of the subreddit is required."""
+
+ def __init__(self, function):
+ """Construct a ModeratorRequired exception.
+
+ :param function: The function that requires moderator access.
+
+ """
+ message = ('`{0}` requires a moderator '
+ 'of the subreddit').format(function)
+ super(ModeratorRequired, self).__init__(message)
+
+
+class ModeratorOrScopeRequired(LoginOrScopeRequired, ModeratorRequired):
+ """Indicates that a moderator of the sub or OAuth2 scope is required.
+
+ The attribute `scope` will contain the name of the necessary scope.
+
+ """
+
+ def __init__(self, function, scope):
+ """Construct a ModeratorOrScopeRequired exception.
+
+ :param function: The function that requires moderator authentication or
+ a moderator scope..
+ :param scope: The scope that is required if not logged in with
+ moderator access..
+
+ """
+ message = ('`{0}` requires a moderator of the subreddit or the '
+ 'OAuth2 scope `{1}`').format(function, scope)
+ super(ModeratorOrScopeRequired, self).__init__(function, scope,
+ message)
+
+
+class OAuthAppRequired(ClientException):
+ """Raised when an OAuth client cannot be initialized.
+
+ This occurs when any one of the OAuth config values are not set.
+
+ """
+
+
+class HTTPException(PRAWException):
+ """Base class for HTTP related exceptions."""
+
+ def __init__(self, _raw, message=None):
+ """Construct a HTTPException.
+
+ :params _raw: The internal request library response object. This object
+ is mapped to attribute `_raw` whose format may change at any time.
+
+ """
+ if not message:
+ message = 'HTTP error'
+ super(HTTPException, self).__init__()
+ self._raw = _raw
+ self.message = message
+
+ def __str__(self):
+ """Return the message of the error."""
+ return self.message
+
+
+class Forbidden(HTTPException):
+ """Raised when the user does not have permission to the entity."""
+
+
+class NotFound(HTTPException):
+ """Raised when the requested entity is not found."""
+
+
+class InvalidComment(PRAWException):
+ """Indicate that the comment is no longer available on reddit."""
+
+ ERROR_TYPE = 'DELETED_COMMENT'
+
+ def __str__(self):
+ """Return the message of the error."""
+ return self.ERROR_TYPE
+
+
+class InvalidSubmission(PRAWException):
+ """Indicates that the submission is no longer available on reddit."""
+
+ ERROR_TYPE = 'DELETED_LINK'
+
+ def __str__(self):
+ """Return the message of the error."""
+ return self.ERROR_TYPE
+
+
+class InvalidSubreddit(PRAWException):
+ """Indicates that an invalid subreddit name was supplied."""
+
+ ERROR_TYPE = 'SUBREDDIT_NOEXIST'
+
+ def __str__(self):
+ """Return the message of the error."""
+ return self.ERROR_TYPE
+
+
+class RedirectException(PRAWException):
+ """Raised when a redirect response occurs that is not expected."""
+
+ def __init__(self, request_url, response_url, message=None):
+ """Construct a RedirectException.
+
+ :param request_url: The url requested.
+ :param response_url: The url being redirected to.
+ :param message: A custom message to associate with the exception.
+
+ """
+ if not message:
+ message = ('Unexpected redirect '
+ 'from {0} to {1}').format(request_url, response_url)
+ super(RedirectException, self).__init__()
+ self.request_url = request_url
+ self.response_url = response_url
+ self.message = message
+
+ def __str__(self):
+ """Return the message of the error."""
+ return self.message
+
+
+class OAuthException(PRAWException):
+ """Base exception class for OAuth API calls.
+
+ Attribute `message` contains the error message.
+ Attribute `url` contains the url that resulted in the error.
+
+ """
+
+ def __init__(self, message, url):
+ """Construct a OAuthException.
+
+ :param message: The message associated with the exception.
+ :param url: The url that resulted in error.
+
+ """
+ super(OAuthException, self).__init__()
+ self.message = message
+ self.url = url
+
+ def __str__(self):
+ """Return the message along with the url."""
+ return self.message + " on url {0}".format(self.url)
+
+
+class OAuthInsufficientScope(OAuthException):
+ """Raised when the current OAuth scope is not sufficient for the action.
+
+ This indicates the access token is valid, but not for the desired action.
+
+ """
+
+
+class OAuthInvalidGrant(OAuthException):
+ """Raised when the code to retrieve access information is not valid."""
+
+
+class OAuthInvalidToken(OAuthException):
+ """Raised when the current OAuth access token is not valid."""
+
+
+class APIException(PRAWException):
+ """Base exception class for the reddit API error message exceptions.
+
+ All exceptions of this type should have their own subclass.
+
+ """
+
+ def __init__(self, error_type, message, field='', response=None):
+ """Construct an APIException.
+
+ :param error_type: The error type set on reddit's end.
+ :param message: The associated message for the error.
+ :param field: The input field associated with the error, or ''.
+ :param response: The HTTP response that resulted in the exception.
+
+ """
+ super(APIException, self).__init__()
+ self.error_type = error_type
+ self.message = message
+ self.field = field
+ self.response = response
+
+ def __str__(self):
+ """Return a string containing the error message and field."""
+ if hasattr(self, 'ERROR_TYPE'):
+ return '`{0}` on field `{1}`'.format(self.message, self.field)
+ else:
+ return '({0}) `{1}` on field `{2}`'.format(self.error_type,
+ self.message,
+ self.field)
+
+
+class ExceptionList(APIException):
+ """Raised when more than one exception occurred."""
+
+ def __init__(self, errors):
+ """Construct an ExceptionList.
+
+ :param errors: The list of errors.
+
+ """
+ super(ExceptionList, self).__init__(None, None)
+ self.errors = errors
+
+ def __str__(self):
+ """Return a string representation for all the errors."""
+ ret = '\n'
+ for i, error in enumerate(self.errors):
+ ret += '\tError {0}) {1}\n'.format(i, six.text_type(error))
+ return ret
+
+
+class AlreadySubmitted(APIException):
+ """An exception to indicate that a URL was previously submitted."""
+
+ ERROR_TYPE = 'ALREADY_SUB'
+
+
+class AlreadyModerator(APIException):
+ """Used to indicate that a user is already a moderator of a subreddit."""
+
+ ERROR_TYPE = 'ALREADY_MODERATOR'
+
+
+class BadCSS(APIException):
+ """An exception to indicate bad CSS (such as invalid) was used."""
+
+ ERROR_TYPE = 'BAD_CSS'
+
+
+class BadCSSName(APIException):
+ """An exception to indicate a bad CSS name (such as invalid) was used."""
+
+ ERROR_TYPE = 'BAD_CSS_NAME'
+
+
+class BadUsername(APIException):
+ """An exception to indicate an invalid username was used."""
+
+ ERROR_TYPE = 'BAD_USERNAME'
+
+
+class InvalidCaptcha(APIException):
+ """An exception for when an incorrect captcha error is returned."""
+
+ ERROR_TYPE = 'BAD_CAPTCHA'
+
+
+class InvalidEmails(APIException):
+ """An exception for when invalid emails are provided."""
+
+ ERROR_TYPE = 'BAD_EMAILS'
+
+
+class InvalidFlairTarget(APIException):
+ """An exception raised when an invalid user is passed as a flair target."""
+
+ ERROR_TYPE = 'BAD_FLAIR_TARGET'
+
+
+class InvalidInvite(APIException):
+ """Raised when attempting to accept a nonexistent moderator invite."""
+
+ ERROR_TYPE = 'NO_INVITE_FOUND'
+
+
+class InvalidUser(APIException):
+ """An exception for when a user doesn't exist."""
+
+ ERROR_TYPE = 'USER_DOESNT_EXIST'
+
+
+class InvalidUserPass(APIException):
+ """An exception for failed logins."""
+
+ ERROR_TYPE = 'WRONG_PASSWORD'
+
+
+class InsufficientCreddits(APIException):
+ """Raised when there are not enough creddits to complete the action."""
+
+ ERROR_TYPE = 'INSUFFICIENT_CREDDITS'
+
+
+class NotLoggedIn(APIException):
+ """An exception for when a Reddit user isn't logged in."""
+
+ ERROR_TYPE = 'USER_REQUIRED'
+
+
+class NotModified(APIException):
+ """An exception raised when reddit returns {'error': 304}.
+
+ This error indicates that the requested content was not modified and is
+ being requested too frequently. Such an error usually occurs when multiple
+ instances of PRAW are running concurrently or in rapid succession.
+
+ """
+
+ def __init__(self, response):
+ """Construct an instance of the NotModified exception.
+
+ This error does not have an error_type, message, nor field.
+
+ """
+ super(NotModified, self).__init__(None, None, response=response)
+
+ def __str__(self):
+ """Return: That page has not been modified."""
+ return 'That page has not been modified.'
+
+
+class RateLimitExceeded(APIException):
+ """An exception for when something has happened too frequently.
+
+ Contains a `sleep_time` attribute for the number of seconds that must
+ transpire prior to the next request.
+
+ """
+
+ ERROR_TYPE = 'RATELIMIT'
+
+
+class SubredditExists(APIException):
+ """An exception to indicate that a subreddit name is not available."""
+
+ ERROR_TYPE = 'SUBREDDIT_EXISTS'
+
+
+class UsernameExists(APIException):
+ """An exception to indicate that a username is not available."""
+
+ ERROR_TYPE = 'USERNAME_TAKEN'
+
+
+def _build_error_mapping():
+ def predicate(obj):
+ return inspect.isclass(obj) and hasattr(obj, 'ERROR_TYPE')
+
+ tmp = {}
+ for _, obj in inspect.getmembers(sys.modules[__name__], predicate):
+ tmp[obj.ERROR_TYPE] = obj
+ return tmp
+ERROR_MAPPING = _build_error_mapping()
diff --git a/ttrv/packages/praw/handlers.py b/ttrv/packages/praw/handlers.py
new file mode 100644
index 0000000..1661b8c
--- /dev/null
+++ b/ttrv/packages/praw/handlers.py
@@ -0,0 +1,243 @@
+"""Provides classes that handle request dispatching."""
+
+from __future__ import print_function, unicode_literals
+
+import socket
+import sys
+import time
+from functools import wraps
+from .errors import ClientException
+from .helpers import normalize_url
+from requests import Session
+from six import text_type
+from six.moves import cPickle # pylint: disable=F0401
+from threading import Lock
+from timeit import default_timer as timer
+
+
+class RateLimitHandler(object):
+ """The base handler that provides thread-safe rate limiting enforcement.
+
+ While this handler is threadsafe, PRAW is not thread safe when the same
+ `Reddit` instance is being utilized from multiple threads.
+
+ """
+
+ last_call = {} # Stores a two-item list: [lock, previous_call_time]
+ rl_lock = Lock() # lock used for adding items to last_call
+
+ @staticmethod
+ def rate_limit(function):
+ """Return a decorator that enforces API request limit guidelines.
+
+ We are allowed to make a API request every api_request_delay seconds as
+ specified in praw.ini. This value may differ from reddit to reddit. For
+ reddit.com it is 2. Any function decorated with this will be forced to
+ delay _rate_delay seconds from the calling of the last function
+ decorated with this before executing.
+
+ This decorator must be applied to a RateLimitHandler class method or
+ instance method as it assumes `rl_lock` and `last_call` are available.
+
+ """
+ @wraps(function)
+ def wrapped(cls, _rate_domain, _rate_delay, **kwargs):
+ cls.rl_lock.acquire()
+ lock_last = cls.last_call.setdefault(_rate_domain, [Lock(), 0])
+ with lock_last[0]: # Obtain the domain specific lock
+ cls.rl_lock.release()
+ # Sleep if necessary, then perform the request
+ now = timer()
+ delay = lock_last[1] + _rate_delay - now
+ if delay > 0:
+ now += delay
+ time.sleep(delay)
+ lock_last[1] = now
+ return function(cls, **kwargs)
+ return wrapped
+
+ @classmethod
+ def evict(cls, urls): # pylint: disable=W0613
+ """Method utilized to evict entries for the given urls.
+
+ :param urls: An iterable containing normalized urls.
+ :returns: The number of items removed from the cache.
+
+ By default this method returns False as a cache need not be present.
+
+ """
+ return 0
+
+ def __del__(self):
+ """Cleanup the HTTP session."""
+ if self.http:
+ try:
+ self.http.close()
+ except: # Never fail pylint: disable=W0702
+ pass
+
+ def __init__(self):
+ """Establish the HTTP session."""
+ self.http = Session() # Each instance should have its own session
+
+ def request(self, request, proxies, timeout, verify, **_):
+ """Responsible for dispatching the request and returning the result.
+
+ Network level exceptions should be raised and only
+ ``requests.Response`` should be returned.
+
+ :param request: A ``requests.PreparedRequest`` object containing all
+ the data necessary to perform the request.
+ :param proxies: A dictionary of proxy settings to be utilized for the
+ request.
+ :param timeout: Specifies the maximum time that the actual HTTP request
+ can take.
+ :param verify: Specifies if SSL certificates should be validated.
+
+ ``**_`` should be added to the method call to ignore the extra
+ arguments intended for the cache handler.
+
+ """
+ settings = self.http.merge_environment_settings(
+ request.url, proxies, False, verify, None
+ )
+ return self.http.send(request, timeout=timeout, allow_redirects=False,
+ **settings)
+
+RateLimitHandler.request = RateLimitHandler.rate_limit(
+ RateLimitHandler.request)
+
+
+class DefaultHandler(RateLimitHandler):
+ """Extends the RateLimitHandler to add thread-safe caching support."""
+
+ ca_lock = Lock()
+ cache = {}
+ cache_hit_callback = None
+ timeouts = {}
+
+ @staticmethod
+ def with_cache(function):
+ """Return a decorator that interacts with a handler's cache.
+
+ This decorator must be applied to a DefaultHandler class method or
+ instance method as it assumes `cache`, `ca_lock` and `timeouts` are
+ available.
+
+ """
+ @wraps(function)
+ def wrapped(cls, _cache_key, _cache_ignore, _cache_timeout, **kwargs):
+ def clear_timeouts():
+ """Clear the cache of timed out results."""
+ for key in list(cls.timeouts):
+ if timer() - cls.timeouts[key] > _cache_timeout:
+ del cls.timeouts[key]
+ del cls.cache[key]
+
+ if _cache_ignore:
+ return function(cls, **kwargs)
+ with cls.ca_lock:
+ clear_timeouts()
+ if _cache_key in cls.cache:
+ if cls.cache_hit_callback:
+ cls.cache_hit_callback(_cache_key)
+ return cls.cache[_cache_key]
+ # Releasing the lock before actually making the request allows for
+ # the possibility of more than one thread making the same request
+ # to get through. Without having domain-specific caching (under the
+ # assumption only one request to a domain can be made at a
+ # time), there isn't a better way to handle this.
+ result = function(cls, **kwargs)
+ # The handlers don't call `raise_for_status` so we need to ignore
+ # status codes that will result in an exception that should not be
+ # cached.
+ if result.status_code not in (200, 302):
+ return result
+ with cls.ca_lock:
+ cls.timeouts[_cache_key] = timer()
+ cls.cache[_cache_key] = result
+ return result
+ return wrapped
+
+ @classmethod
+ def clear_cache(cls):
+ """Remove all items from the cache."""
+ with cls.ca_lock:
+ cls.cache = {}
+ cls.timeouts = {}
+
+ @classmethod
+ def evict(cls, urls):
+ """Remove items from cache matching URLs.
+
+ Return the number of items removed.
+
+ """
+ if isinstance(urls, text_type):
+ urls = [urls]
+ urls = set(normalize_url(url) for url in urls)
+ retval = 0
+ with cls.ca_lock:
+ for key in list(cls.cache):
+ if key[0] in urls:
+ retval += 1
+ del cls.cache[key]
+ del cls.timeouts[key]
+ return retval
+DefaultHandler.request = DefaultHandler.with_cache(RateLimitHandler.request)
+
+
+class MultiprocessHandler(object):
+ """A PRAW handler to interact with the PRAW multi-process server."""
+
+ def __init__(self, host='localhost', port=10101):
+ """Construct an instance of the MultiprocessHandler."""
+ self.host = host
+ self.port = port
+
+ def _relay(self, **kwargs):
+ """Send the request through the server and return the HTTP response."""
+ retval = None
+ delay_time = 2 # For connection retries
+ read_attempts = 0 # For reading from socket
+ while retval is None: # Evict can return False
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ sock_fp = sock.makefile('rwb') # Used for pickle
+ try:
+ sock.connect((self.host, self.port))
+ cPickle.dump(kwargs, sock_fp, cPickle.HIGHEST_PROTOCOL)
+ sock_fp.flush()
+ retval = cPickle.load(sock_fp)
+ except: # pylint: disable=W0702
+ exc_type, exc, _ = sys.exc_info()
+ socket_error = exc_type is socket.error
+ if socket_error and exc.errno == 111: # Connection refused
+ sys.stderr.write('Cannot connect to multiprocess server. I'
+ 's it running? Retrying in {0} seconds.\n'
+ .format(delay_time))
+ time.sleep(delay_time)
+ delay_time = min(64, delay_time * 2)
+ elif exc_type is EOFError or socket_error and exc.errno == 104:
+ # Failure during socket READ
+ if read_attempts >= 3:
+ raise ClientException('Successive failures reading '
+ 'from the multiprocess server.')
+ sys.stderr.write('Lost connection with multiprocess server'
+ ' during read. Trying again.\n')
+ read_attempts += 1
+ else:
+ raise
+ finally:
+ sock_fp.close()
+ sock.close()
+ if isinstance(retval, Exception):
+ raise retval # pylint: disable=E0702
+ return retval
+
+ def evict(self, urls):
+ """Forward the eviction to the server and return its response."""
+ return self._relay(method='evict', urls=urls)
+
+ def request(self, **kwargs):
+ """Forward the request to the server and return its HTTP response."""
+ return self._relay(method='request', **kwargs)
diff --git a/ttrv/packages/praw/helpers.py b/ttrv/packages/praw/helpers.py
new file mode 100644
index 0000000..8d5f2a3
--- /dev/null
+++ b/ttrv/packages/praw/helpers.py
@@ -0,0 +1,481 @@
+# This file is part of PRAW.
+#
+# PRAW is free software: you can redistribute it and/or modify it under the
+# terms of the GNU General Public License as published by the Free Software
+# Foundation, either version 3 of the License, or (at your option) any later
+# version.
+#
+# PRAW is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# PRAW. If not, see .
+
+"""
+Helper functions.
+
+The functions here provide functionality that is often needed by programs using
+PRAW, but which isn't part of reddit's API.
+"""
+
+from __future__ import unicode_literals
+
+import six
+import sys
+import time
+from collections import deque
+from functools import partial
+from timeit import default_timer as timer
+from .errors import HTTPException, PRAWException
+from operator import attrgetter
+
+BACKOFF_START = 4 # Minimum number of seconds to sleep during errors
+KEEP_ITEMS = 128 # On each iteration only remember the first # items
+
+# for conversion between broken reddit timestamps and unix timestamps
+REDDIT_TIMESTAMP_OFFSET = 28800
+
+
+def comment_stream(reddit_session, subreddit, limit=None, verbosity=1):
+ """Indefinitely yield new comments from the provided subreddit.
+
+ Comments are yielded from oldest to newest.
+
+ :param reddit_session: The reddit_session to make requests from. In all the
+ examples this is assigned to the variable ``r``.
+ :param subreddit: Either a subreddit object, or the name of a
+ subreddit. Use `all` to get the comment stream for all comments made to
+ reddit.
+ :param limit: The maximum number of comments to fetch in a single
+ iteration. When None, fetch all available comments (reddit limits this
+ to 1000 (or multiple of 1000 for multi-subreddits). If this number is
+ too small, comments may be missed.
+ :param verbosity: A number that controls the amount of output produced to
+ stderr. <= 0: no output; >= 1: output the total number of comments
+ processed and provide the short-term number of comments processed per
+ second; >= 2: output when additional delays are added in order to avoid
+ subsequent unexpected http errors. >= 3: output debugging information
+ regarding the comment stream. (Default: 1)
+
+ """
+ get_function = partial(reddit_session.get_comments,
+ six.text_type(subreddit))
+ return _stream_generator(get_function, limit, verbosity)
+
+
+def submission_stream(reddit_session, subreddit, limit=None, verbosity=1):
+ """Indefinitely yield new submissions from the provided subreddit.
+
+ Submissions are yielded from oldest to newest.
+
+ :param reddit_session: The reddit_session to make requests from. In all the
+ examples this is assigned to the variable ``r``.
+ :param subreddit: Either a subreddit object, or the name of a
+ subreddit. Use `all` to get the submissions stream for all submissions
+ made to reddit.
+ :param limit: The maximum number of submissions to fetch in a single
+ iteration. When None, fetch all available submissions (reddit limits
+ this to 1000 (or multiple of 1000 for multi-subreddits). If this number
+ is too small, submissions may be missed. Since there isn't a limit to
+ the number of submissions that can be retrieved from r/all, the limit
+ will be set to 1000 when limit is None.
+ :param verbosity: A number that controls the amount of output produced to
+ stderr. <= 0: no output; >= 1: output the total number of submissions
+ processed and provide the short-term number of submissions processed
+ per second; >= 2: output when additional delays are added in order to
+ avoid subsequent unexpected http errors. >= 3: output debugging
+ information regarding the submission stream. (Default: 1)
+
+ """
+ if six.text_type(subreddit).lower() == "all":
+ if limit is None:
+ limit = 1000
+ if not hasattr(subreddit, 'reddit_session'):
+ subreddit = reddit_session.get_subreddit(subreddit)
+ return _stream_generator(subreddit.get_new, limit, verbosity)
+
+
+def valid_redditors(redditors, sub):
+ """Return a verified list of valid Redditor instances.
+
+ :param redditors: A list comprised of Redditor instances and/or strings
+ that are to be verified as actual redditor accounts.
+ :param sub: A Subreddit instance that the authenticated account has
+ flair changing permission on.
+
+ Note: Flair will be unset for all valid redditors in `redditors` on the
+ subreddit `sub`. A valid redditor is defined as a redditor that is
+ registered on reddit.
+
+ """
+ simplified = list(set(six.text_type(x).lower() for x in redditors))
+ return [sub.reddit_session.get_redditor(simplified[i], fetch=False)
+ for (i, resp) in enumerate(sub.set_flair_csv(
+ ({'user': x, 'flair_text': x} for x in simplified)))
+ if resp['ok']]
+
+
+def submissions_between(reddit_session,
+ subreddit,
+ lowest_timestamp=None,
+ highest_timestamp=None,
+ newest_first=True,
+ extra_cloudsearch_fields=None,
+ verbosity=1):
+ """Yield submissions between two timestamps.
+
+ If both ``highest_timestamp`` and ``lowest_timestamp`` are unspecified,
+ yields all submissions in the ``subreddit``.
+
+ Submissions are yielded from newest to oldest(like in the "new" queue).
+
+ :param reddit_session: The reddit_session to make requests from. In all the
+ examples this is assigned to the variable ``r``.
+ :param subreddit: Either a subreddit object, or the name of a
+ subreddit. Use `all` to get the submissions stream for all submissions
+ made to reddit.
+ :param lowest_timestamp: The lower bound for ``created_utc`` atributed of
+ submissions.
+ (Default: subreddit's created_utc or 0 when subreddit == "all").
+ :param highest_timestamp: The upper bound for ``created_utc`` attribute
+ of submissions. (Default: current unix time)
+ NOTE: both highest_timestamp and lowest_timestamp are proper
+ unix timestamps(just like ``created_utc`` attributes)
+ :param newest_first: If set to true, yields submissions
+ from newest to oldest. Otherwise yields submissions
+ from oldest to newest
+ :param extra_cloudsearch_fields: Allows extra filtering of results by
+ parameters like author, self. Full list is available here:
+ https://www.reddit.com/wiki/search
+ :param verbosity: A number that controls the amount of output produced to
+ stderr. <= 0: no output; >= 1: output the total number of submissions
+ processed; >= 2: output debugging information regarding
+ the search queries. (Default: 1)
+ """
+ def debug(msg, level):
+ if verbosity >= level:
+ sys.stderr.write(msg + '\n')
+
+ def format_query_field(k, v):
+ if k in ["nsfw", "self"]:
+ # even though documentation lists "no" and "yes"
+ # as possible values, in reality they don't work
+ if v not in [0, 1, "0", "1"]:
+ raise PRAWException("Invalid value for the extra"
+ "field {}. Only '0' and '1' are"
+ "valid values.".format(k))
+ return "{}:{}".format(k, v)
+ return "{}:'{}'".format(k, v)
+
+ if extra_cloudsearch_fields is None:
+ extra_cloudsearch_fields = {}
+
+ extra_query_part = " ".join(
+ [format_query_field(k, v) for (k, v)
+ in sorted(extra_cloudsearch_fields.items())]
+ )
+
+ if highest_timestamp is None:
+ highest_timestamp = int(time.time()) + REDDIT_TIMESTAMP_OFFSET
+ else:
+ highest_timestamp = int(highest_timestamp) + REDDIT_TIMESTAMP_OFFSET
+
+ if lowest_timestamp is not None:
+ lowest_timestamp = int(lowest_timestamp) + REDDIT_TIMESTAMP_OFFSET
+ elif not isinstance(subreddit, six.string_types):
+ lowest_timestamp = int(subreddit.created)
+ elif subreddit not in ("all", "contrib", "mod", "friend"):
+ lowest_timestamp = int(reddit_session.get_subreddit(subreddit).created)
+ else:
+ lowest_timestamp = 0
+
+ original_highest_timestamp = highest_timestamp
+ original_lowest_timestamp = lowest_timestamp
+
+ # When making timestamp:X..Y queries, reddit misses submissions
+ # inside X..Y range, but they can be found inside Y..Z range
+ # It is not clear what is the value of Z should be, but it seems
+ # like the difference is usually about ~1 hour or less
+ # To be sure, let's set the workaround offset to 2 hours
+ out_of_order_submissions_workaround_offset = 7200
+ highest_timestamp += out_of_order_submissions_workaround_offset
+ lowest_timestamp -= out_of_order_submissions_workaround_offset
+
+ # Those parameters work ok, but there may be a better set of parameters
+ window_size = 60 * 60
+ search_limit = 100
+ min_search_results_in_window = 50
+ window_adjustment_ratio = 1.25
+ backoff = BACKOFF_START
+
+ processed_submissions = 0
+ prev_win_increased = False
+ prev_win_decreased = False
+
+ while highest_timestamp >= lowest_timestamp:
+ try:
+ if newest_first:
+ t1 = max(highest_timestamp - window_size, lowest_timestamp)
+ t2 = highest_timestamp
+ else:
+ t1 = lowest_timestamp
+ t2 = min(lowest_timestamp + window_size, highest_timestamp)
+
+ search_query = 'timestamp:{}..{}'.format(t1, t2)
+ if extra_query_part:
+ search_query = "(and {} {})".format(search_query,
+ extra_query_part)
+
+ debug(search_query, 3)
+ search_results = list(reddit_session.search(search_query,
+ subreddit=subreddit,
+ limit=search_limit,
+ syntax='cloudsearch',
+ sort='new'))
+
+ debug("Received {0} search results for query {1}"
+ .format(len(search_results), search_query),
+ 2)
+
+ backoff = BACKOFF_START
+ except HTTPException as exc:
+ debug("{0}. Sleeping for {1} seconds".format(exc, backoff), 2)
+ time.sleep(backoff)
+ backoff *= 2
+ continue
+
+ if len(search_results) >= search_limit:
+ power = 2 if prev_win_decreased else 1
+ window_size = int(window_size / window_adjustment_ratio**power)
+ prev_win_decreased = True
+ debug("Decreasing window size to {0} seconds".format(window_size),
+ 2)
+ # Since it is possible that there are more submissions
+ # in the current window, we have to re-do the request
+ # with reduced window
+ continue
+ else:
+ prev_win_decreased = False
+
+ search_results = [s for s in search_results
+ if original_lowest_timestamp <= s.created and
+ s.created <= original_highest_timestamp]
+
+ for submission in sorted(search_results,
+ key=attrgetter('created_utc', 'id'),
+ reverse=newest_first):
+ yield submission
+
+ processed_submissions += len(search_results)
+ debug('Total processed submissions: {}'
+ .format(processed_submissions), 1)
+
+ if newest_first:
+ highest_timestamp -= (window_size + 1)
+ else:
+ lowest_timestamp += (window_size + 1)
+
+ if len(search_results) < min_search_results_in_window:
+ power = 2 if prev_win_increased else 1
+ window_size = int(window_size * window_adjustment_ratio**power)
+ prev_win_increased = True
+ debug("Increasing window size to {0} seconds"
+ .format(window_size), 2)
+ else:
+ prev_win_increased = False
+
+
+def _stream_generator(get_function, limit=None, verbosity=1):
+ def debug(msg, level):
+ if verbosity >= level:
+ sys.stderr.write(msg + '\n')
+
+ def b36_id(item):
+ return int(item.id, 36)
+
+ seen = BoundedSet(KEEP_ITEMS * 16)
+ before = None
+ count = 0 # Count is incremented to bypass the cache
+ processed = 0
+ backoff = BACKOFF_START
+ while True:
+ items = []
+ sleep = None
+ start = timer()
+ try:
+ i = None
+ params = {'uniq': count}
+ count = (count + 1) % 100
+ if before:
+ params['before'] = before
+ gen = enumerate(get_function(limit=limit, params=params))
+ for i, item in gen:
+ if b36_id(item) in seen:
+ if i == 0:
+ if before is not None:
+ # reddit sent us out of order data -- log it
+ debug('(INFO) {0} already seen with before of {1}'
+ .format(item.fullname, before), 3)
+ before = None
+ break
+ if i == 0: # Always the first item in the generator
+ before = item.fullname
+ if b36_id(item) not in seen:
+ items.append(item)
+ processed += 1
+ if verbosity >= 1 and processed % 100 == 0:
+ sys.stderr.write(' Items: {0} \r'
+ .format(processed))
+ sys.stderr.flush()
+ if i < KEEP_ITEMS:
+ seen.add(b36_id(item))
+ else: # Generator exhausted
+ if i is None: # Generator yielded no items
+ assert before is not None
+ # Try again without before as the before item may be too
+ # old or no longer exist.
+ before = None
+ backoff = BACKOFF_START
+ except HTTPException as exc:
+ sleep = (backoff, '{0}. Sleeping for {{0}} seconds.'.format(exc),
+ 2)
+ backoff *= 2
+ # Provide rate limit
+ if verbosity >= 1:
+ rate = len(items) / (timer() - start)
+ sys.stderr.write(' Items: {0} ({1:.2f} ips) \r'
+ .format(processed, rate))
+ sys.stderr.flush()
+ # Yield items from oldest to newest
+ for item in items[::-1]:
+ yield item
+ # Sleep if necessary
+ if sleep:
+ sleep_time, msg, msg_level = sleep # pylint: disable=W0633
+ debug(msg.format(sleep_time), msg_level)
+ time.sleep(sleep_time)
+
+
+def chunk_sequence(sequence, chunk_length, allow_incomplete=True):
+ """Given a sequence, divide it into sequences of length `chunk_length`.
+
+ :param allow_incomplete: If True, allow final chunk to be shorter if the
+ given sequence is not an exact multiple of `chunk_length`.
+ If False, the incomplete chunk will be discarded.
+ """
+ (complete, leftover) = divmod(len(sequence), chunk_length)
+ if not allow_incomplete:
+ leftover = 0
+
+ chunk_count = complete + min(leftover, 1)
+
+ chunks = []
+ for x in range(chunk_count):
+ left = chunk_length * x
+ right = left + chunk_length
+ chunks.append(sequence[left:right])
+
+ return chunks
+
+
+def convert_id36_to_numeric_id(id36):
+ """Convert strings representing base36 numbers into an integer."""
+ if not isinstance(id36, six.string_types) or id36.count("_") > 0:
+ raise ValueError("must supply base36 string, not fullname (e.g. use "
+ "xxxxx, not t3_xxxxx)")
+ return int(id36, 36)
+
+
+def convert_numeric_id_to_id36(numeric_id):
+ """Convert an integer into its base36 string representation.
+
+ This method has been cleaned up slightly to improve readability. For more
+ info see:
+
+ https://github.com/reddit/reddit/blob/master/r2/r2/lib/utils/_utils.pyx
+
+ https://www.reddit.com/r/redditdev/comments/n624n/submission_ids_question/
+
+ https://en.wikipedia.org/wiki/Base36
+ """
+ # base36 allows negative numbers, but reddit does not
+ if not isinstance(numeric_id, six.integer_types) or numeric_id < 0:
+ raise ValueError("must supply a positive int/long")
+
+ # Alphabet used for base 36 conversion
+ alphabet = '0123456789abcdefghijklmnopqrstuvwxyz'
+ alphabet_len = len(alphabet)
+
+ # Temp assign
+ current_number = numeric_id
+ base36 = []
+
+ # Current_number must be greater than alphabet length to while/divmod
+ if 0 <= current_number < alphabet_len:
+ return alphabet[current_number]
+
+ # Break up into chunks
+ while current_number != 0:
+ current_number, rem = divmod(current_number, alphabet_len)
+ base36.append(alphabet[rem])
+
+ # String is built in reverse order
+ return ''.join(reversed(base36))
+
+
+def flatten_tree(tree, nested_attr='replies', depth_first=False):
+ """Return a flattened version of the passed in tree.
+
+ :param nested_attr: The attribute name that contains the nested items.
+ Defaults to ``replies`` which is suitable for comments.
+ :param depth_first: When true, add to the list in a depth-first manner
+ rather than the default breadth-first manner.
+
+ """
+ stack = deque(tree)
+ extend = stack.extend if depth_first else stack.extendleft
+ retval = []
+ while stack:
+ item = stack.popleft()
+ nested = getattr(item, nested_attr, None)
+ if nested:
+ extend(nested)
+ retval.append(item)
+ return retval
+
+
+def normalize_url(url):
+ """Return url after stripping trailing .json and trailing slashes."""
+ if url.endswith('.json'):
+ url = url[:-5]
+ if url.endswith('/'):
+ url = url[:-1]
+ return url
+
+
+class BoundedSet(object):
+ """A set with a maximum size that evicts the oldest items when necessary.
+
+ This class does not implement the complete set interface.
+
+ """
+
+ def __init__(self, max_items):
+ """Construct an instance of the BoundedSet."""
+ self.max_items = max_items
+ self._fifo = []
+ self._set = set()
+
+ def __contains__(self, item):
+ """Test if the BoundedSet contains item."""
+ return item in self._set
+
+ def add(self, item):
+ """Add an item to the set discarding the oldest item if necessary."""
+ if item in self._set:
+ self._fifo.remove(item)
+ elif len(self._set) == self.max_items:
+ self._set.remove(self._fifo.pop(0))
+ self._fifo.append(item)
+ self._set.add(item)
diff --git a/ttrv/packages/praw/internal.py b/ttrv/packages/praw/internal.py
new file mode 100644
index 0000000..92c5e52
--- /dev/null
+++ b/ttrv/packages/praw/internal.py
@@ -0,0 +1,271 @@
+# This file is part of PRAW.
+#
+# PRAW is free software: you can redistribute it and/or modify it under the
+# terms of the GNU General Public License as published by the Free Software
+# Foundation, either version 3 of the License, or (at your option) any later
+# version.
+#
+# PRAW is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# PRAW. If not, see .
+
+"""Internal helper functions.
+
+The functions in this module are not to be relied upon by third-parties.
+
+"""
+
+from __future__ import print_function, unicode_literals
+import os
+import re
+import six
+import sys
+from requests import Request, codes, exceptions
+from requests.compat import urljoin
+from .decorators import restrict_access
+from .errors import (ClientException, HTTPException, Forbidden, NotFound,
+ InvalidSubreddit, OAuthException,
+ OAuthInsufficientScope, OAuthInvalidToken,
+ RedirectException)
+from warnings import warn
+try:
+ from OpenSSL import __version__ as _opensslversion
+ _opensslversionlist = [int(minor) if minor.isdigit() else minor
+ for minor in _opensslversion.split('.')]
+except ImportError:
+ _opensslversionlist = [0, 15]
+
+MIN_PNG_SIZE = 67
+MIN_JPEG_SIZE = 128
+MAX_IMAGE_SIZE = 512000
+JPEG_HEADER = b'\xff\xd8\xff'
+PNG_HEADER = b'\x89\x50\x4e\x47\x0d\x0a\x1a\x0a'
+RE_REDIRECT = re.compile('(rand(om|nsfw))|about/sticky')
+
+
+def _get_redditor_listing(subpath=''):
+ """Return function to generate Redditor listings."""
+ def _listing(self, sort='new', time='all', *args, **kwargs):
+ """Return a get_content generator for some RedditContentObject type.
+
+ :param sort: Specify the sort order of the results if applicable
+ (one of ``'hot'``, ``'new'``, ``'top'``, ``'controversial'``).
+ :param time: Specify the time-period to return submissions if
+ applicable (one of ``'hour'``, ``'day'``, ``'week'``,
+ ``'month'``, ``'year'``, ``'all'``).
+
+ The additional parameters are passed directly into
+ :meth:`.get_content`. Note: the `url` parameter cannot be altered.
+
+ """
+ kwargs.setdefault('params', {})
+ kwargs['params'].setdefault('sort', sort)
+ kwargs['params'].setdefault('t', time)
+ url = urljoin(self._url, subpath) # pylint: disable=W0212
+ return self.reddit_session.get_content(url, *args, **kwargs)
+ return _listing
+
+
+def _get_sorter(subpath='', **defaults):
+ """Return function to generate specific subreddit Submission listings."""
+ @restrict_access(scope='read')
+ def _sorted(self, *args, **kwargs):
+ """Return a get_content generator for some RedditContentObject type.
+
+ The additional parameters are passed directly into
+ :meth:`.get_content`. Note: the `url` parameter cannot be altered.
+
+ """
+ if not kwargs.get('params'):
+ kwargs['params'] = {}
+ for key, value in six.iteritems(defaults):
+ kwargs['params'].setdefault(key, value)
+ url = urljoin(self._url, subpath) # pylint: disable=W0212
+ return self.reddit_session.get_content(url, *args, **kwargs)
+ return _sorted
+
+
+def _image_type(image):
+ size = os.path.getsize(image.name)
+ if size < MIN_PNG_SIZE:
+ raise ClientException('png image is too small.')
+ if size > MAX_IMAGE_SIZE:
+ raise ClientException('`image` is too big. Max: {0} bytes'
+ .format(MAX_IMAGE_SIZE))
+ first_bytes = image.read(MIN_PNG_SIZE)
+ image.seek(0)
+ if first_bytes.startswith(PNG_HEADER):
+ return 'png'
+ elif first_bytes.startswith(JPEG_HEADER):
+ if size < MIN_JPEG_SIZE:
+ raise ClientException('jpeg image is too small.')
+ return 'jpg'
+ raise ClientException('`image` must be either jpg or png.')
+
+
+def _modify_relationship(relationship, unlink=False, is_sub=False):
+ """Return a function for relationship modification.
+
+ Used to support friending (user-to-user), as well as moderating,
+ contributor creating, and banning (user-to-subreddit).
+
+ """
+ # The API uses friend and unfriend to manage all of these relationships.
+ url_key = 'unfriend' if unlink else 'friend'
+
+ if relationship == 'friend':
+ access = {'scope': None, 'login': True}
+ elif relationship == 'moderator':
+ access = {'scope': 'modothers'}
+ elif relationship in ['banned', 'contributor', 'muted']:
+ access = {'scope': 'modcontributors'}
+ elif relationship in ['wikibanned', 'wikicontributor']:
+ access = {'scope': ['modcontributors', 'modwiki']}
+ else:
+ access = {'scope': None, 'mod': True}
+
+ @restrict_access(**access)
+ def do_relationship(thing, user, **kwargs):
+ data = {'name': six.text_type(user),
+ 'type': relationship}
+ data.update(kwargs)
+ if is_sub:
+ data['r'] = six.text_type(thing)
+ else:
+ data['container'] = thing.fullname
+
+ session = thing.reddit_session
+ if relationship == 'moderator':
+ session.evict(session.config['moderators'].format(
+ subreddit=six.text_type(thing)))
+ url = session.config[url_key]
+ return session.request_json(url, data=data)
+ return do_relationship
+
+
+def _prepare_request(reddit_session, url, params, data, auth, files,
+ method=None):
+ """Return a requests Request object that can be "prepared"."""
+ # Requests using OAuth for authorization must switch to using the oauth
+ # domain.
+ if getattr(reddit_session, '_use_oauth', False):
+ bearer = 'bearer {0}'.format(reddit_session.access_token)
+ headers = {'Authorization': bearer}
+ config = reddit_session.config
+ for prefix in (config.api_url, config.permalink_url):
+ if url.startswith(prefix):
+ if config.log_requests >= 1:
+ msg = 'substituting {0} for {1} in url\n'.format(
+ config.oauth_url, prefix)
+ sys.stderr.write(msg)
+ url = config.oauth_url + url[len(prefix):]
+ break
+ else:
+ headers = {}
+ headers.update(reddit_session.http.headers)
+
+ if method:
+ pass
+ elif data or files:
+ method = 'POST'
+ else:
+ method = 'GET'
+
+ # Log the request if logging is enabled
+ if reddit_session.config.log_requests >= 1:
+ sys.stderr.write('{0}: {1}\n'.format(method, url))
+ if reddit_session.config.log_requests >= 2:
+ if params:
+ sys.stderr.write('params: {0}\n'.format(params))
+ if data:
+ sys.stderr.write('data: {0}\n'.format(data))
+ if auth:
+ sys.stderr.write('auth: {0}\n'.format(auth))
+ # Prepare request
+ request = Request(method=method, url=url, headers=headers, params=params,
+ auth=auth, cookies=reddit_session.http.cookies)
+ if method == 'GET':
+ return request
+ # Most POST requests require adding `api_type` and `uh` to the data.
+ if data is True:
+ data = {}
+
+ if isinstance(data, dict):
+ if not auth:
+ data.setdefault('api_type', 'json')
+ if reddit_session.modhash:
+ data.setdefault('uh', reddit_session.modhash)
+ else:
+ request.headers.setdefault('Content-Type', 'application/json')
+
+ request.data = data
+ request.files = files
+ return request
+
+
+def _raise_redirect_exceptions(response):
+ """Return the new url or None if there are no redirects.
+
+ Raise exceptions if appropriate.
+
+ """
+ if response.status_code not in [301, 302, 307]:
+ return None
+ new_url = urljoin(response.url, response.headers['location'])
+ if 'reddits/search' in new_url: # Handle non-existent subreddit
+ subreddit = new_url.rsplit('=', 1)[1]
+ raise InvalidSubreddit('`{0}` is not a valid subreddit'
+ .format(subreddit))
+ elif not RE_REDIRECT.search(response.url):
+ raise RedirectException(response.url, new_url)
+ return new_url
+
+
+def _raise_response_exceptions(response):
+ """Raise specific errors on some status codes."""
+ if not response.ok and 'www-authenticate' in response.headers:
+ msg = response.headers['www-authenticate']
+ if 'insufficient_scope' in msg:
+ raise OAuthInsufficientScope('insufficient_scope', response.url)
+ elif 'invalid_token' in msg:
+ raise OAuthInvalidToken('invalid_token', response.url)
+ else:
+ raise OAuthException(msg, response.url)
+
+ if response.status_code == codes.forbidden: # pylint: disable=E1101
+ raise Forbidden(_raw=response)
+ elif response.status_code == codes.not_found: # pylint: disable=E1101
+ raise NotFound(_raw=response)
+ else:
+ try:
+ response.raise_for_status() # These should all be directly mapped
+ except exceptions.HTTPError as exc:
+ raise HTTPException(_raw=exc.response)
+
+
+def _to_reddit_list(arg):
+ """Return an argument converted to a reddit-formatted list.
+
+ The returned format is a comma deliminated list. Each element is a string
+ representation of an object. Either given as a string or as an object that
+ is then converted to its string representation.
+ """
+ if (isinstance(arg, six.string_types) or not (
+ hasattr(arg, "__getitem__") or hasattr(arg, "__iter__"))):
+ return six.text_type(arg)
+ else:
+ return ','.join(six.text_type(a) for a in arg)
+
+
+def _warn_pyopenssl():
+ """Warn the user against faulty versions of pyOpenSSL."""
+ if _opensslversionlist < [0, 15]: # versions >= 0.15 are fine
+ warn(RuntimeWarning(
+ "pyOpenSSL {0} may be incompatible with praw if validating"
+ "ssl certificates, which is on by default.\nSee https://"
+ "github.com/praw/pull/625 for more information".format(
+ _opensslversion)
+ ))
diff --git a/ttrv/packages/praw/multiprocess.py b/ttrv/packages/praw/multiprocess.py
new file mode 100644
index 0000000..c2c7ee2
--- /dev/null
+++ b/ttrv/packages/praw/multiprocess.py
@@ -0,0 +1,102 @@
+"""Provides a request server to be used with the multiprocess handler."""
+
+from __future__ import print_function, unicode_literals
+
+import socket
+import sys
+from optparse import OptionParser
+from . import __version__
+from .handlers import DefaultHandler
+from requests import Session
+from six.moves import cPickle, socketserver # pylint: disable=F0401
+from threading import Lock
+
+
+class ThreadingTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
+ # pylint: disable=R0903,W0232
+ """A TCP server that creates new threads per connection."""
+
+ allow_reuse_address = True
+
+ @staticmethod
+ def handle_error(_, client_addr):
+ """Mute tracebacks of common errors."""
+ exc_type, exc_value, _ = sys.exc_info()
+ if exc_type is socket.error and exc_value[0] == 32:
+ pass
+ elif exc_type is cPickle.UnpicklingError:
+ sys.stderr.write('Invalid connection from {0}\n'
+ .format(client_addr[0]))
+ else:
+ raise
+
+
+class RequestHandler(socketserver.StreamRequestHandler):
+ # pylint: disable=W0232
+ """A class that handles incoming requests.
+
+ Requests to the same domain are cached and rate-limited.
+
+ """
+
+ ca_lock = Lock() # lock around cache and timeouts
+ cache = {} # caches requests
+ http = Session() # used to make requests
+ last_call = {} # Stores a two-item list: [lock, previous_call_time]
+ rl_lock = Lock() # lock used for adding items to last_call
+ timeouts = {} # store the time items in cache were entered
+
+ do_evict = DefaultHandler.evict # Add in the evict method
+
+ @staticmethod
+ def cache_hit_callback(key):
+ """Output when a cache hit occurs."""
+ print('HIT {0} {1}'.format('POST' if key[1][1] else 'GET', key[0]))
+
+ @DefaultHandler.with_cache
+ @DefaultHandler.rate_limit
+ def do_request(self, request, proxies, timeout, **_):
+ """Dispatch the actual request and return the result."""
+ print('{0} {1}'.format(request.method, request.url))
+ response = self.http.send(request, proxies=proxies, timeout=timeout,
+ allow_redirects=False)
+ response.raw = None # Make pickleable
+ return response
+
+ def handle(self):
+ """Parse the RPC, make the call, and pickle up the return value."""
+ data = cPickle.load(self.rfile) # pylint: disable=E1101
+ method = data.pop('method')
+ try:
+ retval = getattr(self, 'do_{0}'.format(method))(**data)
+ except Exception as e:
+ # All exceptions should be passed to the client
+ retval = e
+ cPickle.dump(retval, self.wfile, # pylint: disable=E1101
+ cPickle.HIGHEST_PROTOCOL)
+
+
+def run():
+ """The entry point from the praw-multiprocess utility."""
+ parser = OptionParser(version='%prog {0}'.format(__version__))
+ parser.add_option('-a', '--addr', default='localhost',
+ help=('The address or host to listen on. Specify -a '
+ '0.0.0.0 to listen on all addresses. '
+ 'Default: localhost'))
+ parser.add_option('-p', '--port', type='int', default='10101',
+ help=('The port to listen for requests on. '
+ 'Default: 10101'))
+ options, _ = parser.parse_args()
+ try:
+ server = ThreadingTCPServer((options.addr, options.port),
+ RequestHandler)
+ except (socket.error, socket.gaierror) as exc: # Handle bind errors
+ print(exc)
+ sys.exit(1)
+ print('Listening on {0} port {1}'.format(options.addr, options.port))
+ try:
+ server.serve_forever() # pylint: disable=E1101
+ except KeyboardInterrupt:
+ server.socket.close() # pylint: disable=E1101
+ RequestHandler.http.close()
+ print('Goodbye!')
diff --git a/ttrv/packages/praw/objects.py b/ttrv/packages/praw/objects.py
new file mode 100644
index 0000000..9d50515
--- /dev/null
+++ b/ttrv/packages/praw/objects.py
@@ -0,0 +1,2026 @@
+# This file is part of PRAW.
+#
+# PRAW is free software: you can redistribute it and/or modify it under the
+# terms of the GNU General Public License as published by the Free Software
+# Foundation, either version 3 of the License, or (at your option) any later
+# version.
+#
+# PRAW is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# PRAW. If not, see .
+
+"""
+Contains code about objects such as Submissions, Redditors or Commments.
+
+There are two main groups of objects in this file. The first are objects that
+correspond to a Thing or part of a Thing as specified in reddit's API overview,
+https://github.com/reddit/reddit/wiki/API. The second gives functionality that
+extends over multiple Things. An object that extends from Saveable indicates
+that it can be saved and unsaved in the context of a logged in user.
+"""
+
+from __future__ import print_function, unicode_literals
+import six
+from six.moves.urllib.parse import ( # pylint: disable=F0401
+ parse_qs, urlparse, urlunparse)
+from heapq import heappop, heappush
+from json import dumps
+from requests.compat import urljoin
+from warnings import warn, warn_explicit
+from . import (AuthenticatedReddit as AR, ModConfigMixin as MCMix,
+ ModFlairMixin as MFMix, ModLogMixin as MLMix,
+ ModOnlyMixin as MOMix, ModSelfMixin as MSMix,
+ MultiredditMixin as MultiMix, PrivateMessagesMixin as PMMix,
+ SubmitMixin, SubscribeMixin, UnauthenticatedReddit as UR)
+from .decorators import (alias_function, limit_chars, restrict_access,
+ deprecated)
+from .errors import ClientException
+from .internal import (_get_redditor_listing, _get_sorter,
+ _modify_relationship)
+
+
+REDDITOR_KEYS = ('approved_by', 'author', 'banned_by', 'redditor',
+ 'revision_by')
+
+
+class RedditContentObject(object):
+ """Base class that represents actual reddit objects."""
+
+ @classmethod
+ def from_api_response(cls, reddit_session, json_dict):
+ """Return an instance of the appropriate class from the json_dict."""
+ return cls(reddit_session, json_dict=json_dict)
+
+ def __init__(self, reddit_session, json_dict=None, fetch=True,
+ info_url=None, underscore_names=None, uniq=None):
+ """Create a new object from the dict of attributes returned by the API.
+
+ The fetch parameter specifies whether to retrieve the object's
+ information from the API (only matters when it isn't provided using
+ json_dict).
+
+ """
+ self._info_url = info_url or reddit_session.config['info']
+ self.reddit_session = reddit_session
+ self._underscore_names = underscore_names
+ self._uniq = uniq
+ self._has_fetched = self._populate(json_dict, fetch)
+
+ def __eq__(self, other):
+ """Return whether the other instance equals the current."""
+ return (isinstance(other, RedditContentObject) and
+ self.fullname == other.fullname)
+
+ def __hash__(self):
+ """Return the hash of the current instance."""
+ return hash(self.fullname)
+
+ def __getattr__(self, attr):
+ """Return the value of the `attr` attribute."""
+ # Because this method may perform web requests, there are certain
+ # attributes we must blacklist to prevent accidental requests:
+ # __members__, __methods__: Caused by `dir(obj)` in Python 2.
+ # __setstate__: Caused by Pickle deserialization.
+ blacklist = ('__members__', '__methods__', '__setstate__')
+ if attr not in blacklist and not self._has_fetched:
+ self._has_fetched = self._populate(None, True)
+ return getattr(self, attr)
+ msg = '\'{0}\' has no attribute \'{1}\''.format(type(self), attr)
+ raise AttributeError(msg)
+
+ def __getstate__(self):
+ """Needed for `pickle`.
+
+ Without this, pickle protocol version 0 will make HTTP requests
+ upon serialization, hence slowing it down significantly.
+ """
+ return self.__dict__
+
+ def __ne__(self, other):
+ """Return whether the other instance differs from the current."""
+ return not self == other
+
+ def __reduce_ex__(self, _):
+ """Needed for `pickle`.
+
+ Without this, `pickle` protocol version 2 will make HTTP requests
+ upon serialization, hence slowing it down significantly.
+ """
+ return self.__reduce__()
+
+ def __setattr__(self, name, value):
+ """Set the `name` attribute to `value."""
+ if value and name == 'subreddit' and isinstance(value, six.string_types):
+ value = Subreddit(self.reddit_session, value, fetch=False)
+ elif name == 'permalink' and isinstance(self, Comment):
+ # The Reddit API now returns the permalink field for comments. This
+ # will unfortunately break PRAW because permalink is a @property on the
+ # Comment object. I need to investigate if the property can be removed,
+ # for now this is a quick hack to get things working again.
+ # https://github.com/tildeclub/ttrv/issues/462
+ return
+ elif value and name in REDDITOR_KEYS:
+ if isinstance(value, bool):
+ pass
+ elif isinstance(value, dict):
+ value = Redditor(self.reddit_session, json_dict=value['data'])
+ elif not value or value == '[deleted]':
+ value = None
+ else:
+ value = Redditor(self.reddit_session, value, fetch=False)
+ object.__setattr__(self, name, value)
+
+ def __str__(self):
+ """Return a string representation of the RedditContentObject."""
+ retval = self.__unicode__()
+ if not six.PY3:
+ retval = retval.encode('utf-8')
+ return retval
+
+ def _get_json_dict(self):
+ # (disabled for entire function) pylint: disable=W0212
+
+ # OAuth handling needs to be special cased here. For instance, the user
+ # might be calling a method on a Subreddit object that requires first
+ # loading the information about the subreddit. This method should try
+ # to obtain the information in a scope-less manner unless either:
+ # a) The object is a WikiPage and the reddit_session has the `wikiread`
+ # scope.
+ # b) The object is not a WikiPage and the reddit_session has the
+ # `read` scope.
+ prev_use_oauth = self.reddit_session._use_oauth
+
+ wiki_page = isinstance(self, WikiPage)
+ scope = self.reddit_session.has_scope
+
+ self.reddit_session._use_oauth = wiki_page and scope('wikiread') or \
+ not wiki_page and scope('read')
+
+ try:
+ params = {'uniq': self._uniq} if self._uniq else {}
+ response = self.reddit_session.request_json(
+ self._info_url, params=params, as_objects=False)
+ finally:
+ self.reddit_session._use_oauth = prev_use_oauth
+ return response['data']
+
+ def _populate(self, json_dict, fetch):
+ if json_dict is None:
+ json_dict = self._get_json_dict() if fetch else {}
+
+ if self.reddit_session.config.store_json_result is True:
+ self.json_dict = json_dict
+ else:
+ self.json_dict = None
+
+ # TODO: Remove this wikipagelisting hack
+ if isinstance(json_dict, list):
+ json_dict = {'_tmp': json_dict}
+
+ for name, value in six.iteritems(json_dict):
+ if self._underscore_names and name in self._underscore_names:
+ name = '_' + name
+ setattr(self, name, value)
+
+ self._post_populate(fetch)
+ return bool(json_dict) or fetch
+
+ def _post_populate(self, fetch):
+ """Called after populating the attributes of the instance."""
+
+ @property
+ def fullname(self):
+ """Return the object's fullname.
+
+ A fullname is an object's kind mapping like `t3` followed by an
+ underscore and the object's base36 id, e.g., `t1_c5s96e0`.
+
+ """
+ by_object = self.reddit_session.config.by_object
+ return '{0}_{1}'.format(by_object[self.__class__], self.id)
+
+ @property
+ @deprecated('``has_fetched`` will not be a public attribute in PRAW4.')
+ def has_fetched(self):
+ """Return whether the object has been fully fetched from reddit."""
+ return self._has_fetched
+
+
+class Moderatable(RedditContentObject):
+ """Interface for Reddit content objects that have can be moderated."""
+
+ @restrict_access(scope='modposts')
+ def approve(self):
+ """Approve object.
+
+ This reverts a removal, resets the report counter, marks it with a
+ green check mark (only visible to other moderators) on the website view
+ and sets the approved_by attribute to the logged in user.
+
+ :returns: The json response from the server.
+
+ """
+ url = self.reddit_session.config['approve']
+ data = {'id': self.fullname}
+ response = self.reddit_session.request_json(url, data=data)
+ urls = [self.reddit_session.config[x] for x in ['modqueue', 'spam']]
+ if isinstance(self, Submission):
+ urls += self.subreddit._listing_urls # pylint: disable=W0212
+ self.reddit_session.evict(urls)
+ return response
+
+ @restrict_access(scope='modposts')
+ def distinguish(self, as_made_by='mod', sticky=False):
+ """Distinguish object as made by mod, admin or special.
+
+ Distinguished objects have a different author color. With Reddit
+ Enhancement Suite it is the background color that changes.
+
+ `sticky` argument only used for top-level Comments.
+
+ :returns: The json response from the server.
+
+ """
+ url = self.reddit_session.config['distinguish']
+ data = {'id': self.fullname,
+ 'how': 'yes' if as_made_by == 'mod' else as_made_by}
+ if isinstance(self, Comment) and self.is_root:
+ data['sticky'] = sticky
+ return self.reddit_session.request_json(url, data=data)
+
+ @restrict_access(scope='modposts')
+ def ignore_reports(self):
+ """Ignore future reports on this object.
+
+ This prevents future reports from causing notifications or appearing
+ in the various moderation listing. The report count will still
+ increment.
+
+ """
+ url = self.reddit_session.config['ignore_reports']
+ data = {'id': self.fullname}
+ return self.reddit_session.request_json(url, data=data)
+
+ @restrict_access(scope='modposts')
+ def remove(self, spam=False):
+ """Remove object. This is the moderator version of delete.
+
+ The object is removed from the subreddit listings and placed into the
+ spam listing. If spam is set to True, then the automatic spam filter
+ will try to remove objects with similar attributes in the future.
+
+ :returns: The json response from the server.
+
+ """
+ url = self.reddit_session.config['remove']
+ data = {'id': self.fullname,
+ 'spam': 'True' if spam else 'False'}
+ response = self.reddit_session.request_json(url, data=data)
+ urls = [self.reddit_session.config[x] for x in ['modqueue', 'spam']]
+ if isinstance(self, Submission) and hasattr(self, 'subreddit'):
+ urls += self.subreddit._listing_urls # pylint: disable=W0212
+ self.reddit_session.evict(urls)
+ return response
+
+ def undistinguish(self):
+ """Remove mod, admin or special distinguishing on object.
+
+ :returns: The json response from the server.
+
+ """
+ return self.distinguish(as_made_by='no')
+
+ @restrict_access(scope='modposts')
+ def unignore_reports(self):
+ """Remove ignoring of future reports on this object.
+
+ Undoes 'ignore_reports'. Future reports will now cause notifications
+ and appear in the various moderation listings.
+
+ """
+ url = self.reddit_session.config['unignore_reports']
+ data = {'id': self.fullname}
+ return self.reddit_session.request_json(url, data=data)
+
+
+class Editable(RedditContentObject):
+ """Interface for Reddit content objects that can be edited and deleted."""
+
+ @restrict_access(scope='edit')
+ def delete(self):
+ """Delete this object.
+
+ :returns: The json response from the server.
+
+ """
+ url = self.reddit_session.config['del']
+ data = {'id': self.fullname}
+ response = self.reddit_session.request_json(url, data=data)
+ self.reddit_session.evict(self.reddit_session.config['user'])
+ return response
+
+ @restrict_access(scope='edit')
+ def edit(self, text):
+ """Replace the body of the object with `text`.
+
+ :returns: The updated object.
+
+ """
+ url = self.reddit_session.config['edit']
+ data = {'thing_id': self.fullname,
+ 'text': text}
+ response = self.reddit_session.request_json(url, data=data)
+ self.reddit_session.evict(self.reddit_session.config['user'])
+ return response['data']['things'][0]
+
+
+class Gildable(RedditContentObject):
+ """Interface for RedditContentObjects that can be gilded."""
+
+ @restrict_access(scope='creddits', oauth_only=True)
+ def gild(self, months=None):
+ """Gild the Redditor or author of the content.
+
+ :param months: Specifies the number of months to gild. This parameter
+ is Only valid when the instance called upon is of type
+ Redditor. When not provided, the value defaults to 1.
+ :returns: True on success, otherwise raises an exception.
+
+ """
+ if isinstance(self, Redditor):
+ months = int(months) if months is not None else 1
+ if months < 1:
+ raise TypeError('months must be at least 1')
+ if months > 36:
+ raise TypeError('months must be no more than 36')
+ response = self.reddit_session.request(
+ self.reddit_session.config['gild_user'].format(
+ username=six.text_type(self)), data={'months': months})
+ elif months is not None:
+ raise TypeError('months is not a valid parameter for {0}'
+ .format(type(self)))
+ else:
+ response = self.reddit_session.request(
+ self.reddit_session.config['gild_thing']
+ .format(fullname=self.fullname), data=True)
+ return response.status_code == 200
+
+
+class Hideable(RedditContentObject):
+ """Interface for objects that can be hidden."""
+
+ def hide(self, _unhide=False):
+ """Hide object in the context of the logged in user.
+
+ :param _unhide: If True, unhide the item instead. Use
+ :meth:`~praw.objects.Hideable.unhide` instead of setting this
+ manually.
+
+ :returns: The json response from the server.
+
+ """
+ return self.reddit_session.hide(self.fullname, _unhide=_unhide)
+
+ def unhide(self):
+ """Unhide object in the context of the logged in user.
+
+ :returns: The json response from the server.
+
+ """
+ return self.hide(_unhide=True)
+
+
+class Inboxable(RedditContentObject):
+ """Interface for objects that appear in the inbox (orangereds)."""
+
+ def mark_as_read(self):
+ """Mark object as read.
+
+ :returns: The json response from the server.
+
+ """
+ return self.reddit_session._mark_as_read([self.fullname])
+
+ def mark_as_unread(self):
+ """Mark object as unread.
+
+ :returns: The json response from the server.
+
+ """
+ return self.reddit_session._mark_as_read([self.fullname], unread=True)
+
+ def reply(self, text):
+ """Reply to object with the specified text.
+
+ :returns: A Comment object for the newly created comment (reply).
+
+ """
+ # pylint: disable=W0212
+ response = self.reddit_session._add_comment(self.fullname, text)
+ # pylint: enable=W0212
+ urls = [self.reddit_session.config['inbox']]
+ if isinstance(self, Comment):
+ urls.append(self.submission._api_link) # pylint: disable=W0212
+ elif isinstance(self, Message):
+ urls.append(self.reddit_session.config['sent'])
+ self.reddit_session.evict(urls)
+ return response
+
+
+class Messageable(RedditContentObject):
+ """Interface for RedditContentObjects that can be messaged."""
+
+ _methods = (('send_message', PMMix),)
+
+
+class Refreshable(RedditContentObject):
+ """Interface for objects that can be refreshed."""
+
+ def refresh(self):
+ """Re-query to update object with latest values. Return the object.
+
+ Any listing, such as the submissions on a subreddits top page, will
+ automatically be refreshed serverside. Refreshing a submission will
+ also refresh all its comments.
+
+ In the rare case of a comment being deleted or removed when it had
+ no replies, a second request will be made, not all information will
+ be updated and a warning will list the attributes that could not be
+ retrieved if there were any.
+
+ """
+ unique = self.reddit_session._unique_count # pylint: disable=W0212
+ self.reddit_session._unique_count += 1 # pylint: disable=W0212
+
+ if isinstance(self, Redditor):
+ other = Redditor(self.reddit_session, self._case_name, fetch=True,
+ uniq=unique)
+ elif isinstance(self, Comment):
+ sub = Submission.from_url(self.reddit_session, self.permalink,
+ params={'uniq': unique})
+ if sub.comments:
+ other = sub.comments[0]
+ else:
+ # comment is "specially deleted", a reddit inconsistency;
+ # see #519, #524, #535, #537, and #552 it needs to be
+ # retreived via /api/info, but that's okay since these
+ # specially deleted comments always have the same json
+ # structure. The unique count needs to be updated
+ # in case the comment originally came from /api/info
+ msg = ("Comment {0} was deleted or removed, and had "
+ "no replies when such happened, so a second "
+ "request was made to /api/info.".format(self.name))
+ unique = self.reddit_session._unique_count
+ self.reddit_session._unique_count += 1
+ other = self.reddit_session.get_info(thing_id=self.name,
+ params={'uniq': unique})
+ oldkeys = set(self.__dict__.keys())
+ newkeys = set(other.__dict__.keys())
+ keydiff = ", ".join(oldkeys - newkeys)
+ if keydiff:
+ msg += "\nCould not retrieve:\n{0}".format(keydiff)
+ self.__dict__.update(other.__dict__) # pylint: disable=W0201
+ warn(msg, RuntimeWarning)
+ return self
+ elif isinstance(self, Multireddit):
+ other = Multireddit(self.reddit_session, author=self._author,
+ name=self.name, uniq=unique, fetch=True)
+ elif isinstance(self, Submission):
+ params = self._params.copy()
+ params['uniq'] = unique
+ other = Submission.from_url(self.reddit_session, self.permalink,
+ comment_sort=self._comment_sort,
+ params=params)
+ elif isinstance(self, Subreddit):
+ other = Subreddit(self.reddit_session, self._case_name, fetch=True,
+ uniq=unique)
+ elif isinstance(self, WikiPage):
+ other = WikiPage(self.reddit_session,
+ six.text_type(self.subreddit), self.page,
+ fetch=True, uniq=unique)
+
+ self.__dict__ = other.__dict__ # pylint: disable=W0201
+ return self
+
+
+class Reportable(RedditContentObject):
+ """Interface for RedditContentObjects that can be reported."""
+
+ @restrict_access(scope='report')
+ def report(self, reason=None):
+ """Report this object to the moderators.
+
+ :param reason: The user-supplied reason for reporting a comment
+ or submission. Default: None (blank reason)
+ :returns: The json response from the server.
+
+ """
+ url = self.reddit_session.config['report']
+ data = {'id': self.fullname}
+ if reason:
+ data['reason'] = reason
+ response = self.reddit_session.request_json(url, data=data)
+ # Reported objects are automatically hidden as well
+ # pylint: disable=W0212
+ self.reddit_session.evict(
+ [self.reddit_session.config['user'],
+ urljoin(self.reddit_session.user._url, 'hidden')])
+ # pylint: enable=W0212
+ return response
+
+
+class Saveable(RedditContentObject):
+ """Interface for RedditContentObjects that can be saved."""
+
+ @restrict_access(scope='save')
+ def save(self, unsave=False):
+ """Save the object.
+
+ :returns: The json response from the server.
+
+ """
+ url = self.reddit_session.config['unsave' if unsave else 'save']
+ data = {'id': self.fullname,
+ 'executed': 'unsaved' if unsave else 'saved'}
+ response = self.reddit_session.request_json(url, data=data)
+ self.reddit_session.evict(self.reddit_session.config['saved'])
+ return response
+
+ def unsave(self):
+ """Unsave the object.
+
+ :returns: The json response from the server.
+
+ """
+ return self.save(unsave=True)
+
+
+class Voteable(RedditContentObject):
+ """Interface for RedditContentObjects that can be voted on."""
+
+ def clear_vote(self):
+ """Remove the logged in user's vote on the object.
+
+ Running this on an object with no existing vote has no adverse effects.
+
+ Note: votes must be cast by humans. That is, API clients proxying a
+ human's action one-for-one are OK, but bots deciding how to vote on
+ content or amplifying a human's vote are not. See the reddit rules for
+ more details on what constitutes vote cheating.
+
+ Source for note: http://www.reddit.com/dev/api#POST_api_vote
+
+ :returns: The json response from the server.
+
+ """
+ return self.vote()
+
+ def downvote(self):
+ """Downvote object. If there already is a vote, replace it.
+
+ Note: votes must be cast by humans. That is, API clients proxying a
+ human's action one-for-one are OK, but bots deciding how to vote on
+ content or amplifying a human's vote are not. See the reddit rules for
+ more details on what constitutes vote cheating.
+
+ Source for note: http://www.reddit.com/dev/api#POST_api_vote
+
+ :returns: The json response from the server.
+
+ """
+ return self.vote(direction=-1)
+
+ def upvote(self):
+ """Upvote object. If there already is a vote, replace it.
+
+ Note: votes must be cast by humans. That is, API clients proxying a
+ human's action one-for-one are OK, but bots deciding how to vote on
+ content or amplifying a human's vote are not. See the reddit rules for
+ more details on what constitutes vote cheating.
+
+ Source for note: http://www.reddit.com/dev/api#POST_api_vote
+
+ :returns: The json response from the server.
+
+ """
+ return self.vote(direction=1)
+
+ @restrict_access(scope='vote')
+ def vote(self, direction=0):
+ """Vote for the given item in the direction specified.
+
+ Note: votes must be cast by humans. That is, API clients proxying a
+ human's action one-for-one are OK, but bots deciding how to vote on
+ content or amplifying a human's vote are not. See the reddit rules for
+ more details on what constitutes vote cheating.
+
+ Source for note: http://www.reddit.com/dev/api#POST_api_vote
+
+ :returns: The json response from the server.
+
+ """
+ url = self.reddit_session.config['vote']
+ data = {'id': self.fullname,
+ 'dir': six.text_type(direction)}
+ if self.reddit_session.user:
+ # pylint: disable=W0212
+ urls = [urljoin(self.reddit_session.user._url, 'disliked'),
+ urljoin(self.reddit_session.user._url, 'liked')]
+ # pylint: enable=W0212
+ self.reddit_session.evict(urls)
+ return self.reddit_session.request_json(url, data=data)
+
+
+class Comment(Editable, Gildable, Inboxable, Moderatable, Refreshable,
+ Reportable, Saveable, Voteable):
+ """A class that represents a reddit comments."""
+
+ def __init__(self, reddit_session, json_dict):
+ """Construct an instance of the Comment object."""
+ super(Comment, self).__init__(reddit_session, json_dict,
+ underscore_names=['replies'])
+ self._has_fetched_replies = not hasattr(self, 'was_comment')
+ if self._replies:
+ self._replies = self._replies['data']['children']
+ elif self._replies == '': # Comment tree was built and there are none
+ self._replies = []
+ else:
+ self._replies = None
+ self._submission = None
+
+ @limit_chars
+ def __unicode__(self):
+ """Return a string representation of the comment."""
+ return getattr(self, 'body', '[Unloaded Comment]')
+
+ @property
+ def _fast_permalink(self):
+ """Return the short permalink to the comment."""
+ if hasattr(self, 'link_id'): # from /r or /u comments page
+ sid = self.link_id.split('_')[1]
+ else: # from user's /message page
+ sid = self.context.split('/')[4]
+ return urljoin(self.reddit_session.config['comments'], '{0}/_/{1}'
+ .format(sid, self.id))
+
+ def _update_submission(self, submission):
+ """Submission isn't set on __init__ thus we need to update it."""
+ submission._comments_by_id[self.name] = self # pylint: disable=W0212
+ self._submission = submission
+ if self._replies:
+ for reply in self._replies:
+ reply._update_submission(submission) # pylint: disable=W0212
+
+ @property
+ def is_root(self):
+ """Return True when the comment is a top level comment."""
+ sub_prefix = self.reddit_session.config.by_object[Submission]
+ return self.parent_id.startswith(sub_prefix)
+
+ @property
+ def permalink(self):
+ """Return a permalink to the comment."""
+ return urljoin(self.submission.permalink, self.id)
+
+ @property
+ def replies(self):
+ """Return a list of the comment replies to this comment.
+
+ If the comment is not from a submission, :meth:`replies` will
+ always be an empty list unless you call :meth:`refresh()
+ before calling :meth:`replies` due to a limitation in
+ reddit's API.
+
+ """
+ if self._replies is None or not self._has_fetched_replies:
+ response = self.reddit_session.request_json(self._fast_permalink)
+ if response[1]['data']['children']:
+ # pylint: disable=W0212
+ self._replies = response[1]['data']['children'][0]._replies
+ else:
+ # comment is "specially deleted", a reddit inconsistency;
+ # see #519, #524, #535, #537, and #552 it needs to be
+ # retreived via /api/info, but that's okay since these
+ # specially deleted comments always have the same json
+ # structure.
+ msg = ("Comment {0} was deleted or removed, and had "
+ "no replies when such happened, so it still "
+ "has no replies".format(self.name))
+ warn(msg, RuntimeWarning)
+ self._replies = []
+ # pylint: enable=W0212
+ self._has_fetched_replies = True
+ # Set the submission object if it is not set.
+ if not self._submission:
+ self._submission = response[0]['data']['children'][0]
+ return self._replies
+
+ @property
+ def submission(self):
+ """Return the Submission object this comment belongs to."""
+ if not self._submission: # Comment not from submission
+ self._submission = self.reddit_session.get_submission(
+ url=self._fast_permalink)
+ return self._submission
+
+
+class Message(Inboxable):
+ """A class for private messages."""
+
+ @staticmethod
+ @restrict_access(scope='privatemessages')
+ def from_id(reddit_session, message_id, *args, **kwargs):
+ """Request the url for a Message and return a Message object.
+
+ :param reddit_session: The session to make the request with.
+ :param message_id: The ID of the message to request.
+
+ The additional parameters are passed directly into
+ :meth:`.request_json`.
+
+ """
+ # Reduce fullname to ID if necessary
+ message_id = message_id.split('_', 1)[-1]
+ url = reddit_session.config['message'].format(messageid=message_id)
+ message_info = reddit_session.request_json(url, *args, **kwargs)
+ message = message_info['data']['children'][0]
+
+ # Messages are received as a listing such that
+ # the first item is always the thread's root.
+ # The ID requested by the user may be a child.
+ if message.id == message_id:
+ return message
+ for child in message.replies:
+ if child.id == message_id:
+ return child
+
+ def __init__(self, reddit_session, json_dict):
+ """Construct an instance of the Message object."""
+ super(Message, self).__init__(reddit_session, json_dict)
+ if self.replies: # pylint: disable=E0203
+ self.replies = self.replies['data']['children']
+ else:
+ self.replies = []
+
+ @limit_chars
+ def __unicode__(self):
+ """Return a string representation of the Message."""
+ return 'From: {0}\nSubject: {1}\n\n{2}'.format(self.author,
+ self.subject, self.body)
+
+ @restrict_access(scope='privatemessages')
+ def collapse(self):
+ """Collapse a private message or modmail."""
+ url = self.reddit_session.config['collapse_message']
+ self.reddit_session.request_json(url, data={'id': self.name})
+
+ @restrict_access(scope='modcontributors')
+ def mute_modmail_author(self, _unmute=False):
+ """Mute the sender of this modmail message.
+
+ :param _unmute: Unmute the user instead. Please use
+ :meth:`unmute_modmail_author` instead of setting this directly.
+
+ """
+ path = 'unmute_sender' if _unmute else 'mute_sender'
+ return self.reddit_session.request_json(
+ self.reddit_session.config[path], data={'id': self.fullname})
+
+ @restrict_access(scope='privatemessages')
+ def uncollapse(self):
+ """Uncollapse a private message or modmail."""
+ url = self.reddit_session.config['uncollapse_message']
+ self.reddit_session.request_json(url, data={'id': self.name})
+
+ def unmute_modmail_author(self):
+ """Unmute the sender of this modmail message."""
+ return self.mute_modmail_author(_unmute=True)
+
+
+class MoreComments(RedditContentObject):
+ """A class indicating there are more comments."""
+
+ def __init__(self, reddit_session, json_dict):
+ """Construct an instance of the MoreComment object."""
+ super(MoreComments, self).__init__(reddit_session, json_dict)
+ self.submission = None
+ self._comments = None
+
+ def __lt__(self, other):
+ """Proide a sort order on the MoreComments object."""
+ # To work with heapq a "smaller" item is the one with the most comments
+ # We are intentionally making the biggest element the smallest element
+ # to turn the min-heap implementation in heapq into a max-heap
+ # implementation for Submission.replace_more_comments()
+ return self.count > other.count
+
+ def __unicode__(self):
+ """Return a string representation of the MoreComments object."""
+ return '[More Comments: {0}]'.format(self.count)
+
+ def _continue_comments(self, update):
+ assert len(self.children) > 0
+ tmp = self.reddit_session.get_submission(urljoin(
+ self.submission.permalink, self.parent_id.split('_', 1)[1]))
+ assert len(tmp.comments) == 1
+ self._comments = tmp.comments[0].replies
+ if update:
+ for comment in self._comments:
+ # pylint: disable=W0212
+ comment._update_submission(self.submission)
+ # pylint: enable=W0212
+ return self._comments
+
+ def _update_submission(self, submission):
+ self.submission = submission
+
+ def comments(self, update=True):
+ """Fetch and return the comments for a single MoreComments object."""
+ if not self._comments:
+ if self.count == 0: # Handle 'continue this thread' type
+ return self._continue_comments(update)
+ # pylint: disable=W0212
+ children = [x for x in self.children if 't1_{0}'.format(x)
+ not in self.submission._comments_by_id]
+ # pylint: enable=W0212
+ if not children:
+ return None
+ data = {'children': ','.join(children),
+ 'link_id': self.submission.fullname,
+ 'r': str(self.submission.subreddit)}
+
+ # pylint: disable=W0212
+ if self.submission._comment_sort:
+ data['where'] = self.submission._comment_sort
+ # pylint: enable=W0212
+ url = self.reddit_session.config['morechildren']
+ response = self.reddit_session.request_json(url, data=data)
+ self._comments = response['data']['things']
+ if update:
+ for comment in self._comments:
+ # pylint: disable=W0212
+ comment._update_submission(self.submission)
+ # pylint: enable=W0212
+ return self._comments
+
+
+class Redditor(Gildable, Messageable, Refreshable):
+ """A class representing the users of reddit."""
+
+ _methods = (('get_multireddit', MultiMix), ('get_multireddits', MultiMix))
+
+ get_comments = _get_redditor_listing('comments')
+ get_overview = _get_redditor_listing('')
+ get_submitted = _get_redditor_listing('submitted')
+
+ def __init__(self, reddit_session, user_name=None, json_dict=None,
+ fetch=False, **kwargs):
+ """Construct an instance of the Redditor object."""
+ if not user_name:
+ user_name = json_dict['name']
+ info_url = reddit_session.config['user_about'].format(user=user_name)
+ # name is set before calling the parent constructor so that the
+ # json_dict 'name' attribute (if available) has precedence
+ self._case_name = user_name
+ super(Redditor, self).__init__(reddit_session, json_dict,
+ fetch, info_url, **kwargs)
+ self.name = self._case_name
+ self._url = reddit_session.config['user'].format(user=self.name)
+ self._mod_subs = None
+
+ def __repr__(self):
+ """Return a code representation of the Redditor."""
+ return 'Redditor(user_name=\'{0}\')'.format(self.name)
+
+ def __unicode__(self):
+ """Return a string representation of the Redditor."""
+ return self.name
+
+ def _post_populate(self, fetch):
+ if fetch:
+ # Maintain a consistent `name` until the user
+ # explicitly calls `redditor.refresh()`
+ tmp = self._case_name
+ self._case_name = self.name
+ self.name = tmp
+
+ @restrict_access(scope='subscribe')
+ def friend(self, note=None, _unfriend=False):
+ """Friend the user.
+
+ :param note: A personal note about the user. Requires reddit Gold.
+ :param _unfriend: Unfriend the user. Please use :meth:`unfriend`
+ instead of setting this parameter manually.
+
+ :returns: The json response from the server.
+
+ """
+ self.reddit_session.evict(self.reddit_session.config['friends'])
+
+ # Requests through password auth use /api/friend
+ # Requests through oauth use /api/v1/me/friends/{username}
+ if not self.reddit_session.is_oauth_session():
+ modifier = _modify_relationship('friend', unlink=_unfriend)
+ data = {'note': note} if note else {}
+ return modifier(self.reddit_session.user, self, **data)
+
+ url = self.reddit_session.config['friend_v1'].format(user=self.name)
+ # This endpoint wants the data to be a string instead of an actual
+ # dictionary, although it is not required to have any content for adds.
+ # Unfriending does require the 'id' key.
+ if _unfriend:
+ data = {'id': self.name}
+ else:
+ # We cannot send a null or empty note string.
+ data = {'note': note} if note else {}
+ data = dumps(data)
+ method = 'DELETE' if _unfriend else 'PUT'
+ return self.reddit_session.request_json(url, data=data, method=method)
+
+ def get_disliked(self, *args, **kwargs):
+ """Return a listing of the Submissions the user has downvoted.
+
+ This method points to :meth:`get_downvoted`, as the "disliked" name
+ is being phased out.
+ """
+ return self.get_downvoted(*args, **kwargs)
+
+ def get_downvoted(self, *args, **kwargs):
+ """Return a listing of the Submissions the user has downvoted.
+
+ :returns: get_content generator of Submission items.
+
+ The additional parameters are passed directly into
+ :meth:`.get_content`. Note: the `url` parameter cannot be altered.
+
+ As a default, this listing is only accessible by the user. Thereby
+ requiring either user/pswd authentication or OAuth authentication with
+ the 'history' scope. Users may choose to make their voting record
+ public by changing a user preference. In this case, no authentication
+ will be needed to access this listing.
+
+ """
+ # Sending an OAuth authenticated request for a redditor, who isn't the
+ # authenticated user. But who has a public voting record will be
+ # successful.
+ kwargs['_use_oauth'] = self.reddit_session.is_oauth_session()
+ return _get_redditor_listing('downvoted')(self, *args, **kwargs)
+
+ @restrict_access(scope='mysubreddits')
+ def get_friend_info(self):
+ """Return information about this friend, including personal notes.
+
+ The personal note can be added or overwritten with :meth:friend, but
+ only if the user has reddit Gold.
+
+ :returns: The json response from the server.
+
+ """
+ url = self.reddit_session.config['friend_v1'].format(user=self.name)
+ data = {'id': self.name}
+ return self.reddit_session.request_json(url, data=data, method='GET')
+
+ def get_liked(self, *args, **kwargs):
+ """Return a listing of the Submissions the user has upvoted.
+
+ This method points to :meth:`get_upvoted`, as the "liked" name
+ is being phased out.
+ """
+ return self.get_upvoted(*args, **kwargs)
+
+ def get_upvoted(self, *args, **kwargs):
+ """Return a listing of the Submissions the user has upvoted.
+
+ :returns: get_content generator of Submission items.
+
+ The additional parameters are passed directly into
+ :meth:`.get_content`. Note: the `url` parameter cannot be altered.
+
+ As a default, this listing is only accessible by the user. Thereby
+ requirering either user/pswd authentication or OAuth authentication
+ with the 'history' scope. Users may choose to make their voting record
+ public by changing a user preference. In this case, no authentication
+ will be needed to access this listing.
+
+ """
+ kwargs['_use_oauth'] = self.reddit_session.is_oauth_session()
+ return _get_redditor_listing('upvoted')(self, *args, **kwargs)
+
+ def mark_as_read(self, messages, unread=False):
+ """Mark message(s) as read or unread.
+
+ :returns: The json response from the server.
+
+ """
+ ids = []
+ if isinstance(messages, Inboxable):
+ ids.append(messages.fullname)
+ elif hasattr(messages, '__iter__'):
+ for msg in messages:
+ if not isinstance(msg, Inboxable):
+ msg = 'Invalid message type: {0}'.format(type(msg))
+ raise ClientException(msg)
+ ids.append(msg.fullname)
+ else:
+ msg = 'Invalid message type: {0}'.format(type(messages))
+ raise ClientException(msg)
+ # pylint: disable=W0212
+ retval = self.reddit_session._mark_as_read(ids, unread=unread)
+ # pylint: enable=W0212
+ return retval
+
+ def unfriend(self):
+ """Unfriend the user.
+
+ :returns: The json response from the server.
+
+ """
+ return self.friend(_unfriend=True)
+
+
+class LoggedInRedditor(Redditor):
+ """A class representing a currently logged in Redditor."""
+
+ get_hidden = restrict_access('history')(_get_redditor_listing('hidden'))
+ get_saved = restrict_access('history')(_get_redditor_listing('saved'))
+
+ def get_blocked(self):
+ """Return a UserList of Redditors with whom the user has blocked."""
+ url = self.reddit_session.config['blocked']
+ return self.reddit_session.request_json(url)
+
+ def get_cached_moderated_reddits(self):
+ """Return a cached dictionary of the user's moderated reddits.
+
+ This list is used internally. Consider using the `get_my_moderation`
+ function instead.
+
+ """
+ if self._mod_subs is None:
+ self._mod_subs = {'mod': self.reddit_session.get_subreddit('mod')}
+ for sub in self.reddit_session.get_my_moderation(limit=None):
+ self._mod_subs[six.text_type(sub).lower()] = sub
+ return self._mod_subs
+
+ @deprecated('``get_friends`` has been moved to '
+ ':class:`praw.AuthenticatedReddit` and will be removed from '
+ ':class:`objects.LoggedInRedditor` in PRAW v4.0.0')
+ def get_friends(self, **params):
+ """Return a UserList of Redditors with whom the user is friends.
+
+ This method has been moved to :class:`praw.AuthenticatedReddit`.
+
+ """
+ return self.reddit_session.get_friends(**params)
+
+
+class ModAction(RedditContentObject):
+ """A moderator action."""
+
+ def __init__(self, reddit_session, json_dict=None, fetch=False):
+ """Construct an instance of the ModAction object."""
+ super(ModAction, self).__init__(reddit_session, json_dict, fetch)
+
+ def __unicode__(self):
+ """Return a string reprsentation of the moderator action."""
+ return 'Action: {0}'.format(self.action)
+
+
+class Submission(Editable, Gildable, Hideable, Moderatable, Refreshable,
+ Reportable, Saveable, Voteable):
+ """A class for submissions to reddit."""
+
+ _methods = (('select_flair', AR),)
+
+ @staticmethod
+ def _extract_more_comments(tree):
+ """Return a list of MoreComments objects removed from tree."""
+ more_comments = []
+ queue = [(None, x) for x in tree]
+ while len(queue) > 0:
+ parent, comm = queue.pop(0)
+ if isinstance(comm, MoreComments):
+ heappush(more_comments, comm)
+ if parent:
+ parent.replies.remove(comm)
+ else:
+ tree.remove(comm)
+ else:
+ for item in comm.replies:
+ queue.append((comm, item))
+ return more_comments
+
+ @staticmethod
+ def from_id(reddit_session, subreddit_id):
+ """Return an edit-only submission object based on the id."""
+ pseudo_data = {'id': subreddit_id,
+ 'permalink': '/comments/{0}'.format(subreddit_id)}
+ return Submission(reddit_session, pseudo_data)
+
+ @staticmethod
+ def from_json(json_response):
+ """Return a submission object from the json response."""
+ submission = json_response[0]['data']['children'][0]
+ submission.comments = json_response[1]['data']['children']
+ return submission
+
+ @staticmethod
+ @restrict_access(scope='read')
+ def from_url(reddit_session, url, comment_limit=0, comment_sort=None,
+ comments_only=False, params=None):
+ """Request the url and return a Submission object.
+
+ :param reddit_session: The session to make the request with.
+ :param url: The url to build the Submission object from.
+ :param comment_limit: The desired number of comments to fetch. If <= 0
+ fetch the default number for the session's user. If None, fetch the
+ maximum possible.
+ :param comment_sort: The sort order for retrieved comments. When None
+ use the default for the session's user.
+ :param comments_only: Return only the list of comments.
+ :param params: dictionary containing extra GET data to put in the url.
+
+ """
+ if params is None:
+ params = {}
+
+ parsed = urlparse(url)
+ query_pairs = parse_qs(parsed.query)
+ get_params = dict((k, ",".join(v)) for k, v in query_pairs.items())
+ params.update(get_params)
+ url = urlunparse(parsed[:3] + ("", "", ""))
+ if comment_limit is None: # Fetch MAX
+ params['limit'] = 2048 # Just use a big number
+ elif comment_limit > 0: # Use value
+ params['limit'] = comment_limit
+ if comment_sort:
+ params['sort'] = comment_sort
+
+ response = reddit_session.request_json(url, params=params)
+ if comments_only:
+ return response[1]['data']['children']
+ submission = Submission.from_json(response)
+ submission._comment_sort = comment_sort # pylint: disable=W0212
+ submission._params = params # pylint: disable=W0212
+ return submission
+
+ def __init__(self, reddit_session, json_dict):
+ """Construct an instance of the Subreddit object."""
+ super(Submission, self).__init__(reddit_session, json_dict)
+ # pylint: disable=E0203
+ self._api_link = urljoin(reddit_session.config.api_url, self.permalink)
+ # pylint: enable=E0203
+ self.permalink = urljoin(reddit_session.config.permalink_url,
+ self.permalink)
+ self._comment_sort = None
+ self._comments_by_id = {}
+ self._comments = None
+ self._orphaned = {}
+ self._replaced_more = False
+ self._params = {}
+
+ @limit_chars
+ def __unicode__(self):
+ """Return a string representation of the Subreddit.
+
+ Note: The representation is truncated to a fix number of characters.
+ """
+ title = self.title.replace('\r\n', ' ')
+ return six.text_type('{0} :: {1}').format(self.score, title)
+
+ def _insert_comment(self, comment):
+ if comment.name in self._comments_by_id: # Skip existing comments
+ return
+
+ comment._update_submission(self) # pylint: disable=W0212
+
+ if comment.name in self._orphaned: # Reunite children with parent
+ comment.replies.extend(self._orphaned[comment.name])
+ del self._orphaned[comment.name]
+
+ if comment.is_root:
+ self._comments.append(comment)
+ elif comment.parent_id in self._comments_by_id:
+ self._comments_by_id[comment.parent_id].replies.append(comment)
+ else: # Orphan
+ if comment.parent_id in self._orphaned:
+ self._orphaned[comment.parent_id].append(comment)
+ else:
+ self._orphaned[comment.parent_id] = [comment]
+
+ def _update_comments(self, comments):
+ self._comments = comments
+ for comment in self._comments:
+ comment._update_submission(self) # pylint: disable=W0212
+
+ def add_comment(self, text):
+ """Comment on the submission using the specified text.
+
+ :returns: A Comment object for the newly created comment.
+
+ """
+ # pylint: disable=W0212
+ response = self.reddit_session._add_comment(self.fullname, text)
+ # pylint: enable=W0212
+ self.reddit_session.evict(self._api_link) # pylint: disable=W0212
+ return response
+
+ @property
+ def comments(self): # pylint: disable=E0202
+ """Return forest of comments, with top-level comments as tree roots.
+
+ May contain instances of MoreComment objects. To easily replace these
+ objects with Comment objects, use the replace_more_comments method then
+ fetch this attribute. Use comment replies to walk down the tree. To get
+ an unnested, flat list of comments from this attribute use
+ helpers.flatten_tree.
+
+ """
+ if self._comments is None:
+ self.comments = Submission.from_url( # pylint: disable=W0212
+ self.reddit_session, self._api_link, comments_only=True)
+ return self._comments
+
+ @comments.setter # NOQA
+ def comments(self, new_comments): # pylint: disable=E0202
+ """Update the list of comments with the provided nested list."""
+ self._update_comments(new_comments)
+ self._orphaned = {}
+
+ def get_duplicates(self, *args, **kwargs):
+ """Return a get_content generator for the submission's duplicates.
+
+ :returns: get_content generator iterating over Submission objects.
+
+ The additional parameters are passed directly into
+ :meth:`.get_content`. Note: the `url` and `object_filter` parameters
+ cannot be altered.
+
+ """
+ url = self.reddit_session.config['duplicates'].format(
+ submissionid=self.id)
+ return self.reddit_session.get_content(url, *args, object_filter=1,
+ **kwargs)
+
+ def get_flair_choices(self, *args, **kwargs):
+ """Return available link flair choices and current flair.
+
+ Convenience function for
+ :meth:`~.AuthenticatedReddit.get_flair_choices` populating both the
+ `subreddit` and `link` parameters.
+
+ :returns: The json response from the server.
+
+ """
+ return self.subreddit.get_flair_choices(self.fullname, *args, **kwargs)
+
+ @restrict_access(scope='modposts')
+ def lock(self):
+ """Lock thread.
+
+ Requires that the currently authenticated user has the modposts oauth
+ scope or has user/password authentication as a mod of the subreddit.
+
+ :returns: The json response from the server.
+
+ """
+ url = self.reddit_session.config['lock']
+ data = {'id': self.fullname}
+ return self.reddit_session.request_json(url, data=data)
+
+ def mark_as_nsfw(self, unmark_nsfw=False):
+ """Mark as Not Safe For Work.
+
+ Requires that the currently authenticated user is the author of the
+ submission, has the modposts oauth scope or has user/password
+ authentication as a mod of the subreddit.
+
+ :returns: The json response from the server.
+
+ """
+ def mark_as_nsfw_helper(self): # pylint: disable=W0613
+ # It is necessary to have the 'self' argument as it's needed in
+ # restrict_access to determine what class the decorator is
+ # operating on.
+ url = self.reddit_session.config['unmarknsfw' if unmark_nsfw else
+ 'marknsfw']
+ data = {'id': self.fullname}
+ return self.reddit_session.request_json(url, data=data)
+
+ is_author = (self.reddit_session.is_logged_in() and self.author ==
+ self.reddit_session.user)
+ if is_author:
+ return mark_as_nsfw_helper(self)
+ else:
+ return restrict_access('modposts')(mark_as_nsfw_helper)(self)
+
+ def replace_more_comments(self, limit=32, threshold=1):
+ """Update the comment tree by replacing instances of MoreComments.
+
+ :param limit: The maximum number of MoreComments objects to
+ replace. Each replacement requires 1 API request. Set to None to
+ have no limit, or to 0 to make no extra requests. Default: 32
+ :param threshold: The minimum number of children comments a
+ MoreComments object must have in order to be replaced. Default: 1
+ :returns: A list of MoreComments objects that were not replaced.
+
+ Note that after making this call, the `comments` attribute of the
+ submission will no longer contain any MoreComments objects. Items that
+ weren't replaced are still removed from the tree, and will be included
+ in the returned list.
+
+ """
+ if self._replaced_more:
+ return []
+
+ remaining = limit
+ more_comments = self._extract_more_comments(self.comments)
+ skipped = []
+
+ # Fetch largest more_comments until reaching the limit or the threshold
+ while more_comments:
+ item = heappop(more_comments)
+ if remaining == 0: # We're not going to replace any more
+ heappush(more_comments, item) # It wasn't replaced
+ break
+ elif len(item.children) == 0 or 0 < item.count < threshold:
+ heappush(skipped, item) # It wasn't replaced
+ continue
+
+ # Fetch new comments and decrease remaining if a request was made
+ new_comments = item.comments(update=False)
+ if new_comments is not None and remaining is not None:
+ remaining -= 1
+ elif new_comments is None:
+ continue
+
+ # Re-add new MoreComment objects to the heap of more_comments
+ for more in self._extract_more_comments(new_comments):
+ more._update_submission(self) # pylint: disable=W0212
+ heappush(more_comments, more)
+ # Insert the new comments into the tree
+ for comment in new_comments:
+ self._insert_comment(comment)
+
+ self._replaced_more = True
+ return more_comments + skipped
+
+ def set_flair(self, *args, **kwargs):
+ """Set flair for this submission.
+
+ Convenience function that utilizes :meth:`.ModFlairMixin.set_flair`
+ populating both the `subreddit` and `item` parameters.
+
+ :returns: The json response from the server.
+
+ """
+ return self.subreddit.set_flair(self, *args, **kwargs)
+
+ @restrict_access(scope='modposts')
+ def set_contest_mode(self, state=True):
+ """Set 'Contest Mode' for the comments of this submission.
+
+ Contest mode have the following effects:
+ * The comment thread will default to being sorted randomly.
+ * Replies to top-level comments will be hidden behind
+ "[show replies]" buttons.
+ * Scores will be hidden from non-moderators.
+ * Scores accessed through the API (mobile apps, bots) will be
+ obscured to "1" for non-moderators.
+
+ Source for effects: https://www.reddit.com/159bww/
+
+ :returns: The json response from the server.
+
+ """
+ # TODO: Whether a submission is in contest mode is not exposed via the
+ # API. Adding a test of this method is thus currently impossible.
+ # Add a test when it becomes possible.
+ url = self.reddit_session.config['contest_mode']
+ data = {'id': self.fullname, 'state': state}
+ return self.reddit_session.request_json(url, data=data)
+
+ @restrict_access(scope='modposts')
+ def set_suggested_sort(self, sort='blank'):
+ """Set 'Suggested Sort' for the comments of the submission.
+
+ Comments can be sorted in one of (confidence, top, new, hot,
+ controversial, old, random, qa, blank).
+
+ :returns: The json response from the server.
+
+ """
+ url = self.reddit_session.config['suggested_sort']
+ data = {'id': self.fullname, 'sort': sort}
+ return self.reddit_session.request_json(url, data=data)
+
+ @property
+ def short_link(self):
+ """Return a short link to the submission.
+
+ The short link points to a page on the short_domain that redirects to
+ the main. For example http://redd.it/eorhm is a short link for
+ https://www.reddit.com/r/announcements/comments/eorhm/reddit_30_less_typing/.
+
+ """
+ return urljoin(self.reddit_session.config.short_domain, self.id)
+
+ @restrict_access(scope='modposts')
+ def sticky(self, bottom=True):
+ """Sticky a post in its subreddit.
+
+ If there is already a stickied post in the designated slot it will be
+ unstickied.
+
+ :param bottom: Set this as the top or bottom sticky. If no top sticky
+ exists, this submission will become the top sticky regardless.
+
+ :returns: The json response from the server
+
+ """
+ url = self.reddit_session.config['sticky_submission']
+ data = {'id': self.fullname, 'state': True}
+ if not bottom:
+ data['num'] = 1
+ return self.reddit_session.request_json(url, data=data)
+
+ @restrict_access(scope='modposts')
+ def unlock(self):
+ """Lock thread.
+
+ Requires that the currently authenticated user has the modposts oauth
+ scope or has user/password authentication as a mod of the subreddit.
+
+ :returns: The json response from the server.
+
+ """
+ url = self.reddit_session.config['unlock']
+ data = {'id': self.fullname}
+ return self.reddit_session.request_json(url, data=data)
+
+ def unmark_as_nsfw(self):
+ """Mark as Safe For Work.
+
+ :returns: The json response from the server.
+
+ """
+ return self.mark_as_nsfw(unmark_nsfw=True)
+
+ @restrict_access(scope='modposts')
+ def unset_contest_mode(self):
+ """Unset 'Contest Mode' for the comments of this submission.
+
+ Contest mode have the following effects:
+ * The comment thread will default to being sorted randomly.
+ * Replies to top-level comments will be hidden behind
+ "[show replies]" buttons.
+ * Scores will be hidden from non-moderators.
+ * Scores accessed through the API (mobile apps, bots) will be
+ obscured to "1" for non-moderators.
+
+ Source for effects: http://www.reddit.com/159bww/
+
+ :returns: The json response from the server.
+
+ """
+ return self.set_contest_mode(False)
+
+ @restrict_access(scope='modposts')
+ def unsticky(self):
+ """Unsticky this post.
+
+ :returns: The json response from the server
+
+ """
+ url = self.reddit_session.config['sticky_submission']
+ data = {'id': self.fullname, 'state': False}
+ return self.reddit_session.request_json(url, data=data)
+
+
+class Subreddit(Messageable, Refreshable):
+ """A class for Subreddits."""
+
+ _methods = (('accept_moderator_invite', AR),
+ ('add_flair_template', MFMix),
+ ('clear_flair_templates', MFMix),
+ ('configure_flair', MFMix),
+ ('delete_flair', MFMix),
+ ('delete_image', MCMix),
+ ('edit_wiki_page', AR),
+ ('get_banned', MOMix),
+ ('get_comments', UR),
+ ('get_contributors', MOMix),
+ ('get_edited', MOMix),
+ ('get_flair', UR),
+ ('get_flair_choices', AR),
+ ('get_flair_list', MFMix),
+ ('get_moderators', UR),
+ ('get_mod_log', MLMix),
+ ('get_mod_queue', MOMix),
+ ('get_mod_mail', MOMix),
+ ('get_muted', MOMix),
+ ('get_random_submission', UR),
+ ('get_reports', MOMix),
+ ('get_rules', UR),
+ ('get_settings', MCMix),
+ ('get_spam', MOMix),
+ ('get_sticky', UR),
+ ('get_stylesheet', MOMix),
+ ('get_traffic', UR),
+ ('get_unmoderated', MOMix),
+ ('get_wiki_banned', MOMix),
+ ('get_wiki_contributors', MOMix),
+ ('get_wiki_page', UR),
+ ('get_wiki_pages', UR),
+ ('leave_contributor', MSMix),
+ ('leave_moderator', MSMix),
+ ('search', UR),
+ ('select_flair', AR),
+ ('set_flair', MFMix),
+ ('set_flair_csv', MFMix),
+ ('set_settings', MCMix),
+ ('set_stylesheet', MCMix),
+ ('submit', SubmitMixin),
+ ('subscribe', SubscribeMixin),
+ ('unsubscribe', SubscribeMixin),
+ ('update_settings', MCMix),
+ ('upload_image', MCMix))
+
+ # Subreddit banned
+ add_ban = _modify_relationship('banned', is_sub=True)
+ remove_ban = _modify_relationship('banned', unlink=True, is_sub=True)
+
+ # Subreddit contributors
+ add_contributor = _modify_relationship('contributor', is_sub=True)
+ remove_contributor = _modify_relationship('contributor', unlink=True,
+ is_sub=True)
+ # Subreddit moderators
+ add_moderator = _modify_relationship('moderator', is_sub=True)
+ remove_moderator = _modify_relationship('moderator', unlink=True,
+ is_sub=True)
+ # Subreddit muted
+ add_mute = _modify_relationship('muted', is_sub=True)
+ remove_mute = _modify_relationship('muted', is_sub=True, unlink=True)
+
+ # Subreddit wiki banned
+ add_wiki_ban = _modify_relationship('wikibanned', is_sub=True)
+ remove_wiki_ban = _modify_relationship('wikibanned', unlink=True,
+ is_sub=True)
+ # Subreddit wiki contributors
+ add_wiki_contributor = _modify_relationship('wikicontributor', is_sub=True)
+ remove_wiki_contributor = _modify_relationship('wikicontributor',
+ unlink=True, is_sub=True)
+
+ # Generic listing selectors
+ get_controversial = _get_sorter('controversial')
+ get_hot = _get_sorter('')
+ get_new = _get_sorter('new')
+ get_top = _get_sorter('top')
+ get_gilded = _get_sorter('gilded')
+
+ # Explicit listing selectors
+ get_controversial_from_all = _get_sorter('controversial', t='all')
+ get_controversial_from_day = _get_sorter('controversial', t='day')
+ get_controversial_from_hour = _get_sorter('controversial', t='hour')
+ get_controversial_from_month = _get_sorter('controversial', t='month')
+ get_controversial_from_week = _get_sorter('controversial', t='week')
+ get_controversial_from_year = _get_sorter('controversial', t='year')
+ get_rising = _get_sorter('rising')
+ get_top_from_all = _get_sorter('top', t='all')
+ get_top_from_day = _get_sorter('top', t='day')
+ get_top_from_hour = _get_sorter('top', t='hour')
+ get_top_from_month = _get_sorter('top', t='month')
+ get_top_from_week = _get_sorter('top', t='week')
+ get_top_from_year = _get_sorter('top', t='year')
+
+ def __init__(self, reddit_session, subreddit_name=None, json_dict=None,
+ fetch=False, **kwargs):
+ """Construct an instance of the Subreddit object."""
+ # Special case for when my_subreddits is called as no name is returned
+ # so we have to extract the name from the URL. The URLs are returned
+ # as: /r/reddit_name/
+ if subreddit_name is None:
+ subreddit_name = json_dict['url'].split('/')[2]
+
+ if not isinstance(subreddit_name, six.string_types) \
+ or not subreddit_name:
+ raise TypeError('subreddit_name must be a non-empty string.')
+
+ if fetch and ('+' in subreddit_name or '-' in subreddit_name):
+ fetch = False
+ warn_explicit('fetch=True has no effect on multireddits',
+ UserWarning, '', 0)
+
+ info_url = reddit_session.config['subreddit_about'].format(
+ subreddit=subreddit_name)
+ self._case_name = subreddit_name
+ super(Subreddit, self).__init__(reddit_session, json_dict, fetch,
+ info_url, **kwargs)
+ self.display_name = self._case_name
+ self._url = reddit_session.config['subreddit'].format(
+ subreddit=self.display_name)
+ # '' is the hot listing
+ listings = ['new/', '', 'top/', 'controversial/', 'rising/']
+ base = reddit_session.config['subreddit'].format(
+ subreddit=self.display_name)
+ self._listing_urls = [base + x + '.json' for x in listings]
+
+ def __repr__(self):
+ """Return a code representation of the Subreddit."""
+ return 'Subreddit(subreddit_name=\'{0}\')'.format(self.display_name)
+
+ def __unicode__(self):
+ """Return a string representation of the Subreddit."""
+ return self.display_name
+
+ def _post_populate(self, fetch):
+ if fetch:
+ # Maintain a consistent `display_name` until the user
+ # explicitly calls `subreddit.refresh()`
+ tmp = self._case_name
+ self._case_name = self.display_name
+ self.display_name = tmp
+
+ def clear_all_flair(self):
+ """Remove all user flair on this subreddit.
+
+ :returns: The json response from the server when there is flair to
+ clear, otherwise returns None.
+
+ """
+ csv = [{'user': x['user']} for x in self.get_flair_list(limit=None)]
+ if csv:
+ return self.set_flair_csv(csv)
+ else:
+ return
+
+
+class Multireddit(Refreshable):
+ """A class for users' Multireddits."""
+
+ # 2017-11-13
+ # Several of the @restrict_access decorators have been removed here,
+ # because they were duplicated in the corresponding reddit_session
+ # methods and raised assertion errors. The is the same category of
+ # bug as this issue:
+ # https://github.com/praw-dev/praw/issues/477
+
+ # Generic listing selectors
+ get_controversial = _get_sorter('controversial')
+ get_hot = _get_sorter('')
+ get_new = _get_sorter('new')
+ get_top = _get_sorter('top')
+
+ # Explicit listing selectors
+ get_controversial_from_all = _get_sorter('controversial', t='all')
+ get_controversial_from_day = _get_sorter('controversial', t='day')
+ get_controversial_from_hour = _get_sorter('controversial', t='hour')
+ get_controversial_from_month = _get_sorter('controversial', t='month')
+ get_controversial_from_week = _get_sorter('controversial', t='week')
+ get_controversial_from_year = _get_sorter('controversial', t='year')
+ get_rising = _get_sorter('rising')
+ get_top_from_all = _get_sorter('top', t='all')
+ get_top_from_day = _get_sorter('top', t='day')
+ get_top_from_hour = _get_sorter('top', t='hour')
+ get_top_from_month = _get_sorter('top', t='month')
+ get_top_from_week = _get_sorter('top', t='week')
+ get_top_from_year = _get_sorter('top', t='year')
+
+ @classmethod
+ def from_api_response(cls, reddit_session, json_dict):
+ """Return an instance of the appropriate class from the json dict."""
+ # The Multireddit response contains the Subreddits attribute as a list
+ # of dicts of the form {'name': 'subredditname'}.
+ # We must convert each of these into a Subreddit object.
+ json_dict['subreddits'] = [Subreddit(reddit_session, item['name'])
+ for item in json_dict['subreddits']]
+ return cls(reddit_session, None, None, json_dict)
+
+ def __init__(self, reddit_session, author=None, name=None,
+ json_dict=None, fetch=False, **kwargs):
+ """Construct an instance of the Multireddit object."""
+
+ # When get_my_multireddits is called, we extract the author
+ # and multireddit name from the path. A trailing forward
+ # slash was recently added to the path string in the API
+ # response, the needs to be removed to fix the code.
+ # path = "/user/redditor/m/multi/"
+ if json_dict and json_dict['path']:
+ json_dict['path'] = json_dict['path'].rstrip('/')
+
+ author = six.text_type(author) if author \
+ else json_dict['path'].split('/')[-3]
+ if not name:
+ name = json_dict['path'].split('/')[-1]
+
+ info_url = reddit_session.config['multireddit_about'].format(
+ user=author, multi=name)
+ self.name = name
+ self._author = author
+ super(Multireddit, self).__init__(reddit_session, json_dict, fetch,
+ info_url, **kwargs)
+ self._url = reddit_session.config['multireddit'].format(
+ user=author, multi=name)
+
+ def __repr__(self):
+ """Return a code representation of the Multireddit."""
+ return 'Multireddit(author=\'{0}\', name=\'{1}\')'.format(
+ self._author, self.name)
+
+ def __unicode__(self):
+ """Return a string representation of the Multireddit."""
+ return self.name
+
+ def _post_populate(self, fetch):
+ if fetch:
+ # Subreddits are returned as dictionaries in the form
+ # {'name': 'subredditname'}. Convert them to Subreddit objects.
+ self.subreddits = [Subreddit(self.reddit_session, item['name'])
+ for item in self.subreddits]
+
+ # paths are of the form "/user/{USERNAME}/m/{MULTINAME}"
+ author = self.path.split('/')[2]
+ self.author = Redditor(self.reddit_session, author)
+
+ @restrict_access(scope='subscribe')
+ def add_subreddit(self, subreddit, _delete=False, *args, **kwargs):
+ """Add a subreddit to the multireddit.
+
+ :param subreddit: The subreddit name or Subreddit object to add
+
+ The additional parameters are passed directly into
+ :meth:`~praw.__init__.BaseReddit.request_json`.
+
+ """
+ subreddit = six.text_type(subreddit)
+ url = self.reddit_session.config['multireddit_add'].format(
+ user=self._author, multi=self.name, subreddit=subreddit)
+ method = 'DELETE' if _delete else 'PUT'
+ # The modhash isn't necessary for OAuth requests
+ if not self.reddit_session._use_oauth:
+ self.reddit_session.http.headers['x-modhash'] = \
+ self.reddit_session.modhash
+ data = {'model': dumps({'name': subreddit})}
+ try:
+ self.reddit_session.request(url, data=data, method=method,
+ *args, **kwargs)
+ finally:
+ # The modhash isn't necessary for OAuth requests
+ if not self.reddit_session._use_oauth:
+ del self.reddit_session.http.headers['x-modhash']
+
+ def copy(self, to_name):
+ """Copy this multireddit.
+
+ Convenience function that utilizes
+ :meth:`.MultiredditMixin.copy_multireddit` populating both
+ the `from_redditor` and `from_name` parameters.
+
+ """
+ return self.reddit_session.copy_multireddit(self._author, self.name,
+ to_name)
+
+ def delete(self):
+ """Delete this multireddit.
+
+ Convenience function that utilizes
+ :meth:`.MultiredditMixin.delete_multireddit` populating the `name`
+ parameter.
+
+ """
+ return self.reddit_session.delete_multireddit(self.name)
+
+ def edit(self, *args, **kwargs):
+ """Edit this multireddit.
+
+ Convenience function that utilizes
+ :meth:`.MultiredditMixin.edit_multireddit` populating the `name`
+ parameter.
+
+ """
+ return self.reddit_session.edit_multireddit(name=self.name, *args,
+ **kwargs)
+
+ def remove_subreddit(self, subreddit, *args, **kwargs):
+ """Remove a subreddit from the user's multireddit."""
+ return self.add_subreddit(subreddit, True, *args, **kwargs)
+
+ def rename(self, new_name, *args, **kwargs):
+ """Rename this multireddit.
+
+ This function is a handy shortcut to
+ :meth:`rename_multireddit` of the reddit_session.
+
+ """
+ new = self.reddit_session.rename_multireddit(self.name, new_name,
+ *args, **kwargs)
+ self.__dict__ = new.__dict__ # pylint: disable=W0201
+ return self
+
+
+class PRAWListing(RedditContentObject):
+ """An abstract class to coerce a listing into RedditContentObjects."""
+
+ CHILD_ATTRIBUTE = None
+
+ def __init__(self, reddit_session, json_dict=None, fetch=False):
+ """Construct an instance of the PRAWListing object."""
+ super(PRAWListing, self).__init__(reddit_session, json_dict, fetch)
+
+ if not self.CHILD_ATTRIBUTE:
+ raise NotImplementedError('PRAWListing must be extended.')
+
+ child_list = getattr(self, self.CHILD_ATTRIBUTE)
+ for i in range(len(child_list)):
+ child_list[i] = self._convert(reddit_session, child_list[i])
+
+ def __contains__(self, item):
+ """Test if item exists in the listing."""
+ return item in getattr(self, self.CHILD_ATTRIBUTE)
+
+ def __delitem__(self, index):
+ """Remove the item at position index from the listing."""
+ del getattr(self, self.CHILD_ATTRIBUTE)[index]
+
+ def __getitem__(self, index):
+ """Return the item at position index in the listing."""
+ return getattr(self, self.CHILD_ATTRIBUTE)[index]
+
+ def __iter__(self):
+ """Return an iterator to the listing."""
+ return getattr(self, self.CHILD_ATTRIBUTE).__iter__()
+
+ def __len__(self):
+ """Return the number of items in the listing."""
+ return len(getattr(self, self.CHILD_ATTRIBUTE))
+
+ def __setitem__(self, index, item):
+ """Set item at position `index` in the listing."""
+ getattr(self, self.CHILD_ATTRIBUTE)[index] = item
+
+ def __unicode__(self):
+ """Return a string representation of the listing."""
+ return six.text_type(getattr(self, self.CHILD_ATTRIBUTE))
+
+
+class UserList(PRAWListing):
+ """A list of Redditors. Works just like a regular list."""
+
+ CHILD_ATTRIBUTE = 'children'
+
+ @staticmethod
+ def _convert(reddit_session, data):
+ """Return a Redditor object from the data."""
+ retval = Redditor(reddit_session, data['name'], fetch=False)
+ retval.id = data['id'].split('_')[1] # pylint: disable=C0103,W0201
+ return retval
+
+
+class WikiPage(Refreshable):
+ """An individual WikiPage object."""
+
+ @classmethod
+ def from_api_response(cls, reddit_session, json_dict):
+ """Return an instance of the appropriate class from the json_dict."""
+ # The WikiPage response does not contain the necessary information
+ # in the JSON response to determine the name of the page nor the
+ # subreddit it belongs to. Thus we must extract this information
+ # from the request URL.
+ # pylint: disable=W0212
+ parts = reddit_session._request_url.split('/', 6)
+ # pylint: enable=W0212
+ subreddit = parts[4]
+ page = parts[6].split('.', 1)[0]
+ return cls(reddit_session, subreddit, page, json_dict=json_dict)
+
+ def __init__(self, reddit_session, subreddit=None, page=None,
+ json_dict=None, fetch=False, **kwargs):
+ """Construct an instance of the WikiPage object."""
+ if not subreddit and not page:
+ subreddit = json_dict['sr']
+ page = json_dict['page']
+ info_url = reddit_session.config['wiki_page'].format(
+ subreddit=six.text_type(subreddit), page=page)
+ super(WikiPage, self).__init__(reddit_session, json_dict, fetch,
+ info_url, **kwargs)
+ self.page = page
+ self.subreddit = subreddit
+
+ def __unicode__(self):
+ """Return a string representation of the page."""
+ return six.text_type('{0}:{1}').format(self.subreddit, self.page)
+
+ @restrict_access(scope='modwiki')
+ def add_editor(self, username, _delete=False, *args, **kwargs):
+ """Add an editor to this wiki page.
+
+ :param username: The name or Redditor object of the user to add.
+ :param _delete: If True, remove the user as an editor instead.
+ Please use :meth:`remove_editor` rather than setting it manually.
+
+ Additional parameters are passed into
+ :meth:`~praw.__init__.BaseReddit.request_json`.
+ """
+ url = self.reddit_session.config['wiki_page_editor']
+ url = url.format(subreddit=six.text_type(self.subreddit),
+ method='del' if _delete else 'add')
+
+ data = {'page': self.page,
+ 'username': six.text_type(username)}
+ return self.reddit_session.request_json(url, data=data, *args,
+ **kwargs)
+
+ @restrict_access(scope='modwiki')
+ def get_settings(self, *args, **kwargs):
+ """Return the settings for this wiki page.
+
+ Includes permission level, names of editors, and whether
+ the page is listed on /wiki/pages.
+
+ Additional parameters are passed into
+ :meth:`~praw.__init__.BaseReddit.request_json`
+ """
+ url = self.reddit_session.config['wiki_page_settings']
+ url = url.format(subreddit=six.text_type(self.subreddit),
+ page=self.page)
+ return self.reddit_session.request_json(url, *args, **kwargs)['data']
+
+ def edit(self, *args, **kwargs):
+ """Edit the wiki page.
+
+ Convenience function that utilizes
+ :meth:`.AuthenticatedReddit.edit_wiki_page` populating both the
+ ``subreddit`` and ``page`` parameters.
+ """
+ return self.subreddit.edit_wiki_page(self.page, *args, **kwargs)
+
+ @restrict_access(scope='modwiki')
+ def edit_settings(self, permlevel, listed, *args, **kwargs):
+ """Edit the settings for this individual wiki page.
+
+ :param permlevel: Who can edit this page?
+ (0) use subreddit wiki permissions, (1) only approved wiki
+ contributors for this page may edit (see
+ :meth:`~praw.objects.WikiPage.add_editor`), (2) only mods may edit
+ and view
+ :param listed: Show this page on the listing?
+ True - Appear in /wiki/pages
+ False - Do not appear in /wiki/pages
+ :returns: The updated settings data.
+
+ Additional parameters are passed into :meth:`request_json`.
+
+ """
+ url = self.reddit_session.config['wiki_page_settings']
+ url = url.format(subreddit=six.text_type(self.subreddit),
+ page=self.page)
+ data = {'permlevel': permlevel,
+ 'listed': 'on' if listed else 'off'}
+
+ return self.reddit_session.request_json(url, data=data, *args,
+ **kwargs)['data']
+
+ def remove_editor(self, username, *args, **kwargs):
+ """Remove an editor from this wiki page.
+
+ :param username: The name or Redditor object of the user to remove.
+
+ This method points to :meth:`add_editor` with _delete=True.
+
+ Additional parameters are are passed to :meth:`add_editor` and
+ subsequently into :meth:`~praw.__init__.BaseReddit.request_json`.
+ """
+ return self.add_editor(username=username, _delete=True, *args,
+ **kwargs)
+
+
+class WikiPageListing(PRAWListing):
+ """A list of WikiPages. Works just like a regular list."""
+
+ CHILD_ATTRIBUTE = '_tmp'
+
+ @staticmethod
+ def _convert(reddit_session, data):
+ """Return a WikiPage object from the data."""
+ # TODO: The _request_url hack shouldn't be necessary
+ # pylint: disable=W0212
+ subreddit = reddit_session._request_url.rsplit('/', 4)[1]
+ # pylint: enable=W0212
+ return WikiPage(reddit_session, subreddit, data, fetch=False)
+
+
+def _add_aliases():
+ def predicate(obj):
+ return inspect.isclass(obj) and hasattr(obj, '_methods')
+
+ import inspect
+ import sys
+
+ for _, cls in inspect.getmembers(sys.modules[__name__], predicate):
+ for name, mixin in cls._methods: # pylint: disable=W0212
+ setattr(cls, name, alias_function(getattr(mixin, name),
+ mixin.__name__))
+_add_aliases()
diff --git a/ttrv/packages/praw/praw.ini b/ttrv/packages/praw/praw.ini
new file mode 100644
index 0000000..5926c60
--- /dev/null
+++ b/ttrv/packages/praw/praw.ini
@@ -0,0 +1,79 @@
+[DEFAULT]
+# The domain name PRAW will use to interact with the reddit site via its API.
+api_domain: api.reddit.com
+
+# Time, a float, in seconds, required between calls. See:
+# http://code.reddit.com/wiki/API
+api_request_delay: 2.0
+
+# A boolean to indicate whether or not to check for package updates.
+check_for_updates: True
+
+# Time, a float, in seconds, to save the results of a get/post request.
+cache_timeout: 30
+
+# Log the API calls
+# 0: no logging
+# 1: log only the request URIs
+# 2: log the request URIs as well as any POST data
+log_requests: 0
+
+# The domain name PRAW will use for oauth-related requests.
+oauth_domain: oauth.reddit.com
+
+# Whether or not to use HTTPS for oauth connections. This should only be
+# changed for development environments.
+oauth_https: True
+
+# OAuth grant type: either `authorization_code` or `password`
+oauth_grant_type: authorization_code
+
+# The maximum length of unicode representations of Comment, Message and
+# Submission objects. This is mainly used to fit them within a terminal window
+# line. A negative value means no limit.
+output_chars_limit: 80
+
+# The domain name PRAW will use when permalinks are requested.
+permalink_domain: www.reddit.com
+
+# The domain name to use for short urls.
+short_domain: redd.it
+
+# A boolean to indicate if json_dict, which contains the original API response,
+# should be stored on every object in the json_dict attribute. Default is
+# False as memory usage will double if enabled.
+store_json_result: False
+
+# Maximum time, a float, in seconds, before a single HTTP request times
+# out. urllib2.URLError is raised upon timeout.
+timeout: 45
+
+# A boolean to indicate if SSL certificats should be validated. The
+# default is True.
+validate_certs: True
+
+# Object to kind mappings
+comment_kind: t1
+message_kind: t4
+redditor_kind: t2
+submission_kind: t3
+subreddit_kind: t5
+
+
+[reddit]
+# Uses the default settings
+
+[reddit_oauth_test]
+oauth_client_id: stJlUSUbPQe5lQ
+oauth_client_secret: iU-LsOzyJH7BDVoq-qOWNEq2zuI
+oauth_redirect_uri: https://127.0.0.1:65010/authorize_callback
+
+[local_example]
+api_domain: reddit.local
+api_request_delay: 0
+log_requests: 0
+message_kind: t7
+permalink_domain: reddit.local
+short_domain:
+submission_kind: t6
+subreddit_kind: t5
diff --git a/ttrv/packages/praw/settings.py b/ttrv/packages/praw/settings.py
new file mode 100644
index 0000000..49821fb
--- /dev/null
+++ b/ttrv/packages/praw/settings.py
@@ -0,0 +1,45 @@
+# This file is part of PRAW.
+#
+# PRAW is free software: you can redistribute it and/or modify it under the
+# terms of the GNU General Public License as published by the Free Software
+# Foundation, either version 3 of the License, or (at your option) any later
+# version.
+#
+# PRAW is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# PRAW. If not, see .
+
+"""Provides the code to load PRAW's configuration file `praw.ini`."""
+
+from __future__ import print_function, unicode_literals
+
+import os
+import sys
+
+from six.moves import configparser
+
+
+def _load_configuration():
+ """Attempt to load settings from various praw.ini files."""
+ config = configparser.RawConfigParser()
+ module_dir = os.path.dirname(sys.modules[__name__].__file__)
+ if 'APPDATA' in os.environ: # Windows
+ os_config_path = os.environ['APPDATA']
+ elif 'XDG_CONFIG_HOME' in os.environ: # Modern Linux
+ os_config_path = os.environ['XDG_CONFIG_HOME']
+ elif 'HOME' in os.environ: # Legacy Linux
+ os_config_path = os.path.join(os.environ['HOME'], '.config')
+ else:
+ os_config_path = None
+ locations = [os.path.join(module_dir, 'praw.ini'), 'praw.ini']
+ if os_config_path is not None:
+ locations.insert(1, os.path.join(os_config_path, 'praw.ini'))
+ if not config.read(locations):
+ raise Exception('Could not find config file in any of: {0}'
+ .format(locations))
+ return config
+CONFIG = _load_configuration()
+del _load_configuration
diff --git a/ttrv/page.py b/ttrv/page.py
new file mode 100644
index 0000000..743de8c
--- /dev/null
+++ b/ttrv/page.py
@@ -0,0 +1,913 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+import re
+import os
+import sys
+import time
+import logging
+from functools import wraps
+
+import six
+from kitchen.text.display import textual_width
+
+from . import docs
+from .clipboard import copy as clipboard_copy
+from .objects import Controller, Command
+from .exceptions import TemporaryFileError, ProgramError
+from .__version__ import __version__
+
+_logger = logging.getLogger(__name__)
+
+
+def logged_in(f):
+ """
+ Decorator for Page methods that require the user to be authenticated.
+ """
+
+ @wraps(f)
+ def wrapped_method(self, *args, **kwargs):
+ if not self.reddit.is_oauth_session():
+ self.term.show_notification('Not logged in')
+ return None
+ return f(self, *args, **kwargs)
+ return wrapped_method
+
+
+class PageController(Controller):
+ character_map = {}
+
+
+class Page(object):
+
+ BANNER = None
+ FOOTER = None
+
+ def __init__(self, reddit, term, config, oauth):
+ self.reddit = reddit
+ self.term = term
+ self.config = config
+ self.oauth = oauth
+ self.content = None
+ self.nav = None
+ self.controller = None
+
+ self.active = True
+ self.selected_page = None
+ self._row = 0
+ self._subwindows = None
+
+ def refresh_content(self, order=None, name=None):
+ raise NotImplementedError
+
+ def _draw_item(self, win, data, inverted):
+ raise NotImplementedError
+
+ def get_selected_item(self):
+ """
+ Return the content dictionary that is currently selected by the cursor.
+ """
+ return self.content.get(self.nav.absolute_index)
+
+ def loop(self):
+ """
+ Main control loop runs the following steps:
+ 1. Re-draw the screen
+ 2. Wait for user to press a key (includes terminal resizing)
+ 3. Trigger the method registered to the input key
+ 4. Check if there are any nested pages that need to be looped over
+
+ The loop will run until self.active is set to False from within one of
+ the methods.
+ """
+ self.active = True
+
+ # This needs to be called once before the main loop, in case a subpage
+ # was pre-selected before the loop started. This happens in __main__.py
+ # with ``page.open_submission(url=url)``
+ while self.selected_page and self.active:
+ self.handle_selected_page()
+
+ while self.active:
+ self.draw()
+ ch = self.term.stdscr.getch()
+ self.controller.trigger(ch)
+
+ while self.selected_page and self.active:
+ self.handle_selected_page()
+
+ return self.selected_page
+
+ def handle_selected_page(self):
+ """
+ Some commands will result in an action that causes a new page to open.
+ Examples include selecting a submission, viewing subscribed subreddits,
+ or opening the user's inbox. With these commands, the newly selected
+ page will be pre-loaded and stored in ``self.selected_page`` variable.
+ It's up to each page type to determine what to do when another page is
+ selected.
+
+ - It can start a nested page.loop(). This would allow the user to
+ return to their previous screen after exiting the sub-page. For
+ example, this is what happens when opening an individual submission
+ from within a subreddit page. When the submission is closed, the
+ user resumes the subreddit that they were previously viewing.
+
+ - It can close the current self.loop() and bubble the selected page up
+ one level in the loop stack. For example, this is what happens when
+ the user opens their subscriptions and selects a subreddit. The
+ subscription page loop is closed and the selected subreddit is
+ bubbled up to the root level loop.
+
+ Care should be taken to ensure the user can never enter an infinite
+ nested loop, as this could lead to memory leaks and recursion errors.
+
+ # Example of an unsafe nested loop
+ subreddit_page.loop()
+ -> submission_page.loop()
+ -> subreddit_page.loop()
+ -> submission_page.loop()
+ ...
+
+ """
+ raise NotImplementedError
+
+ @PageController.register(Command('REFRESH'))
+ def reload_page(self):
+ """
+ Clear the PRAW cache to force the page the re-fetch content from reddit.
+ """
+ self.reddit.handler.clear_cache()
+ self.refresh_content()
+
+ @PageController.register(Command('EXIT'))
+ def exit(self):
+ """
+ Prompt and exit the application.
+ """
+ if self.term.prompt_y_or_n('Do you really want to quit? (y/n): '):
+ sys.exit()
+
+ @PageController.register(Command('FORCE_EXIT'))
+ def force_exit(self):
+ """
+ Immediately exit the application.
+ """
+ sys.exit()
+
+ @PageController.register(Command('PREVIOUS_THEME'))
+ def previous_theme(self):
+ """
+ Cycle to preview the previous theme from the internal list of themes.
+ """
+ theme = self.term.theme_list.previous(self.term.theme)
+ while not self.term.check_theme(theme):
+ theme = self.term.theme_list.previous(theme)
+
+ self.term.set_theme(theme)
+ self.draw()
+ message = self.term.theme.display_string
+ self.term.show_notification(message, timeout=1)
+
+ @PageController.register(Command('NEXT_THEME'))
+ def next_theme(self):
+ """
+ Cycle to preview the next theme from the internal list of themes.
+ """
+ theme = self.term.theme_list.next(self.term.theme)
+ while not self.term.check_theme(theme):
+ theme = self.term.theme_list.next(theme)
+
+ self.term.set_theme(theme)
+ self.draw()
+ message = self.term.theme.display_string
+ self.term.show_notification(message, timeout=1)
+
+ @PageController.register(Command('HELP'))
+ def show_help(self):
+ """
+ Open the help documentation in the system pager.
+ """
+ self.term.open_pager(docs.HELP.strip())
+
+ @PageController.register(Command('MOVE_UP'))
+ def move_cursor_up(self):
+ """
+ Move the cursor up one selection.
+ """
+ self._move_cursor(-1)
+ self.clear_input_queue()
+
+ @PageController.register(Command('MOVE_DOWN'))
+ def move_cursor_down(self):
+ """
+ Move the cursor down one selection.
+ """
+ self._move_cursor(1)
+ self.clear_input_queue()
+
+ @PageController.register(Command('MOVE_NEXT_UNREAD'))
+ def move_next_unread(self):
+ """
+ Move the cursor to the next unread url.
+ """
+ self._move_cursor_to_unread(1)
+ self.clear_input_queue()
+
+ @PageController.register(Command('MOVE_PREV_UNREAD'))
+ def move_prev_unread(self):
+ """
+ Move the cursor to the previous unread url.
+ """
+ self._move_cursor_to_unread(-1)
+ self.clear_input_queue()
+
+ @PageController.register(Command('PAGE_UP'))
+ def move_page_up(self):
+ """
+ Move the cursor up approximately the number of entries on the page.
+ """
+ self._move_page(-1)
+ self.clear_input_queue()
+
+ @PageController.register(Command('PAGE_DOWN'))
+ def move_page_down(self):
+ """
+ Move the cursor down approximately the number of entries on the page.
+ """
+ self._move_page(1)
+ self.clear_input_queue()
+
+ @PageController.register(Command('PAGE_TOP'))
+ def move_page_top(self):
+ """
+ Move the cursor to the first item on the page.
+ """
+ self.nav.page_index = self.content.range[0]
+ self.nav.cursor_index = 0
+ self.nav.inverted = False
+
+ @PageController.register(Command('PAGE_BOTTOM'))
+ def move_page_bottom(self):
+ """
+ Move the cursor to the last item on the page.
+ """
+ self.nav.page_index = self.content.range[1]
+ self.nav.cursor_index = 0
+ self.nav.inverted = True
+
+ @PageController.register(Command('UPVOTE'))
+ @logged_in
+ def upvote(self):
+ """
+ Upvote the currently selected item.
+ """
+ data = self.get_selected_item()
+ if 'likes' not in data:
+ self.term.flash()
+ elif getattr(data['object'], 'archived'):
+ self.term.show_notification("Voting disabled for archived post", style='Error')
+ elif data['likes']:
+ with self.term.loader('Clearing vote'):
+ data['object'].clear_vote()
+ if not self.term.loader.exception:
+ data['likes'] = None
+ else:
+ with self.term.loader('Voting'):
+ data['object'].upvote()
+ if not self.term.loader.exception:
+ data['likes'] = True
+
+ @PageController.register(Command('DOWNVOTE'))
+ @logged_in
+ def downvote(self):
+ """
+ Downvote the currently selected item.
+ """
+ data = self.get_selected_item()
+ if 'likes' not in data:
+ self.term.flash()
+ elif getattr(data['object'], 'archived'):
+ self.term.show_notification("Voting disabled for archived post", style='Error')
+ elif data['likes'] or data['likes'] is None:
+ with self.term.loader('Voting'):
+ data['object'].downvote()
+ if not self.term.loader.exception:
+ data['likes'] = False
+ else:
+ with self.term.loader('Clearing vote'):
+ data['object'].clear_vote()
+ if not self.term.loader.exception:
+ data['likes'] = None
+
+ @PageController.register(Command('SAVE'))
+ @logged_in
+ def save(self):
+ """
+ Mark the currently selected item as saved through the reddit API.
+ """
+ data = self.get_selected_item()
+ if 'saved' not in data:
+ self.term.flash()
+ elif not data['saved']:
+ with self.term.loader('Saving'):
+ data['object'].save()
+ if not self.term.loader.exception:
+ data['saved'] = True
+ else:
+ with self.term.loader('Unsaving'):
+ data['object'].unsave()
+ if not self.term.loader.exception:
+ data['saved'] = False
+
+ @PageController.register(Command('LOGIN'))
+ def login(self):
+ """
+ Prompt to log into the user's account, or log out of the current
+ account.
+ """
+ if self.reddit.is_oauth_session():
+ ch = self.term.show_notification('Log out? (y/n)')
+ if ch in (ord('y'), ord('Y')):
+ self.oauth.clear_oauth_data()
+ self.term.show_notification('Logged out')
+ else:
+ self.oauth.authorize()
+
+ def reply(self):
+ """
+ Reply to the selected item. This is a utility method and should not
+ be bound to a key directly.
+
+ Item type:
+ Submission - add a top level comment
+ Comment - add a comment reply
+ Message - reply to a private message
+ """
+ data = self.get_selected_item()
+
+ if data['type'] == 'Submission':
+ body = data['text']
+ description = 'submission'
+ reply = data['object'].add_comment
+ elif data['type'] in ('Comment', 'InboxComment'):
+ body = data['body']
+ description = 'comment'
+ reply = data['object'].reply
+ elif data['type'] == 'Message':
+ body = data['body']
+ description = 'private message'
+ reply = data['object'].reply
+ else:
+ self.term.flash()
+ return
+
+ # Construct the text that will be displayed in the editor file.
+ # The post body will be commented out and added for reference
+ lines = [' |' + line for line in body.split('\n')]
+ content = '\n'.join(lines)
+ comment_info = docs.REPLY_FILE.format(
+ author=data['author'],
+ type=description,
+ content=content)
+
+ with self.term.open_editor(comment_info) as comment:
+ if not comment:
+ self.term.show_notification('Canceled')
+ return
+
+ with self.term.loader('Posting {}'.format(description), delay=0):
+ reply(comment)
+ # Give reddit time to process the submission
+ time.sleep(2.0)
+
+ if self.term.loader.exception is None:
+ self.reload_page()
+ else:
+ raise TemporaryFileError()
+
+ @PageController.register(Command('DELETE'))
+ @logged_in
+ def delete_item(self):
+ """
+ Delete a submission or comment.
+ """
+ data = self.get_selected_item()
+ if data.get('author') != self.reddit.user.name:
+ self.term.flash()
+ return
+
+ prompt = 'Are you sure you want to delete this? (y/n): '
+ if not self.term.prompt_y_or_n(prompt):
+ self.term.show_notification('Canceled')
+ return
+
+ with self.term.loader('Deleting', delay=0):
+ data['object'].delete()
+ # Give reddit time to process the request
+ time.sleep(2.0)
+
+ if self.term.loader.exception is None:
+ self.reload_page()
+
+ @PageController.register(Command('EDIT'))
+ @logged_in
+ def edit(self):
+ """
+ Edit a submission or comment.
+ """
+ data = self.get_selected_item()
+ if data.get('author') != self.reddit.user.name:
+ self.term.flash()
+ return
+
+ if data['type'] == 'Submission':
+ content = data['text']
+ info = docs.SUBMISSION_EDIT_FILE.format(
+ content=content, id=data['object'].id)
+ elif data['type'] == 'Comment':
+ content = data['body']
+ info = docs.COMMENT_EDIT_FILE.format(
+ content=content, id=data['object'].id)
+ else:
+ self.term.flash()
+ return
+
+ with self.term.open_editor(info) as text:
+ if not text or text == content:
+ self.term.show_notification('Canceled')
+ return
+
+ with self.term.loader('Editing', delay=0):
+ data['object'].edit(text)
+ time.sleep(2.0)
+
+ if self.term.loader.exception is None:
+ self.reload_page()
+ else:
+ raise TemporaryFileError()
+
+ @PageController.register(Command('PRIVATE_MESSAGE'))
+ @logged_in
+ def send_private_message(self):
+ """
+ Send a new private message to another user.
+ """
+ message_info = docs.MESSAGE_FILE
+ with self.term.open_editor(message_info) as text:
+ if not text:
+ self.term.show_notification('Canceled')
+ return
+
+ parts = text.split('\n', 2)
+ if len(parts) == 1:
+ self.term.show_notification('Missing message subject')
+ return
+ elif len(parts) == 2:
+ self.term.show_notification('Missing message body')
+ return
+
+ recipient, subject, message = parts
+ recipient = recipient.strip()
+ subject = subject.strip()
+ message = message.rstrip()
+
+ if not recipient:
+ self.term.show_notification('Missing recipient')
+ return
+ elif not subject:
+ self.term.show_notification('Missing message subject')
+ return
+ elif not message:
+ self.term.show_notification('Missing message body')
+ return
+
+ with self.term.loader('Sending message', delay=0):
+ self.reddit.send_message(
+ recipient, subject, message, raise_captcha_exception=True)
+ # Give reddit time to process the message
+ time.sleep(2.0)
+
+ if self.term.loader.exception:
+ raise TemporaryFileError()
+ else:
+ self.term.show_notification('Message sent!')
+ self.selected_page = self.open_inbox_page('sent')
+
+ def prompt_and_select_link(self):
+ """
+ Prompt the user to select a link from a list to open.
+
+ Return the link that was selected, or ``None`` if no link was selected.
+ """
+ data = self.get_selected_item()
+ url_full = data.get('url_full')
+ permalink = data.get('permalink')
+
+ if url_full and url_full != permalink:
+ # The item is a link-only submission that won't contain text
+ link = url_full
+ else:
+ html = data.get('html')
+ if html:
+ extracted_links = self.content.extract_links(html)
+ if not extracted_links:
+ # Only one selection to choose from, so just pick it
+ link = permalink
+ else:
+ # Let the user decide which link to open
+ links = []
+ if permalink:
+ links += [{'text': 'Permalink', 'href': permalink}]
+ links += extracted_links
+ link = self.term.prompt_user_to_select_link(links)
+ else:
+ # Some items like hidden comments don't have any HTML to parse
+ link = permalink
+
+ return link
+
+ @PageController.register(Command('COPY_PERMALINK'))
+ def copy_permalink(self):
+ """
+ Copy the submission permalink to OS clipboard
+ """
+ url = self.get_selected_item().get('permalink')
+ self.copy_to_clipboard(url)
+
+ @PageController.register(Command('COPY_URL'))
+ def copy_url(self):
+ """
+ Copy a link to OS clipboard
+ """
+ url = self.prompt_and_select_link()
+ self.copy_to_clipboard(url)
+
+ def copy_to_clipboard(self, url):
+ """
+ Attempt to copy the selected URL to the user's clipboard
+ """
+ if url is None:
+ self.term.flash()
+ return
+
+ try:
+ clipboard_copy(url)
+ except (ProgramError, OSError) as e:
+ _logger.exception(e)
+ self.term.show_notification(
+ 'Failed to copy url: {0}'.format(e))
+ else:
+ self.term.show_notification(
+ ['Copied to clipboard:', url], timeout=1)
+
+ @PageController.register(Command('SUBSCRIPTIONS'))
+ @logged_in
+ def subscriptions(self):
+ """
+ View a list of the user's subscribed subreddits
+ """
+ self.selected_page = self.open_subscription_page('subreddit')
+
+ @PageController.register(Command('MULTIREDDITS'))
+ @logged_in
+ def multireddits(self):
+ """
+ View a list of the user's subscribed multireddits
+ """
+ self.selected_page = self.open_subscription_page('multireddit')
+
+ @PageController.register(Command('PROMPT'))
+ def prompt(self):
+ """
+ Open a prompt to navigate to a different subreddit or comment"
+ """
+ name = self.term.prompt_input('Enter page: /')
+ if name:
+ # Check if opening a submission url or a subreddit url
+ # Example patterns for submissions:
+ # comments/571dw3
+ # /comments/571dw3
+ # /r/pics/comments/571dw3/
+ # https://www.reddit.com/r/pics/comments/571dw3/at_disneyland
+ submission_pattern = re.compile(r'(^|/)comments/(?P.+?)($|/)')
+
+ match = submission_pattern.search(name)
+ if match:
+ url = 'https://www.reddit.com/comments/{0}'.format(match.group('id'))
+ self.selected_page = self.open_submission_page(url)
+ else:
+ self.selected_page = self.open_subreddit_page(name)
+
+ @PageController.register(Command('INBOX'))
+ @logged_in
+ def inbox(self):
+ """
+ View the user's inbox.
+ """
+ self.selected_page = self.open_inbox_page('all')
+
+ def open_inbox_page(self, content_type):
+ """
+ Open an instance of the inbox page for the logged in user.
+ """
+ from .inbox_page import InboxPage
+
+ with self.term.loader('Loading inbox'):
+ page = InboxPage(self.reddit, self.term, self.config, self.oauth,
+ content_type=content_type)
+ if not self.term.loader.exception:
+ return page
+
+ def open_subscription_page(self, content_type):
+ """
+ Open an instance of the subscriptions page with the selected content.
+ """
+ from .subscription_page import SubscriptionPage
+
+ with self.term.loader('Loading {0}s'.format(content_type)):
+ page = SubscriptionPage(self.reddit, self.term, self.config,
+ self.oauth, content_type=content_type)
+ if not self.term.loader.exception:
+ return page
+
+ def open_submission_page(self, url=None, submission=None):
+ """
+ Open an instance of the submission page for the given submission URL.
+ """
+ from .submission_page import SubmissionPage
+
+ with self.term.loader('Loading submission'):
+ page = SubmissionPage(self.reddit, self.term, self.config,
+ self.oauth, url=url, submission=submission)
+ if not self.term.loader.exception:
+ return page
+
+ def open_subreddit_page(self, name):
+ """
+ Open an instance of the subreddit page for the given subreddit name.
+ """
+ from .subreddit_page import SubredditPage
+
+ with self.term.loader('Loading subreddit'):
+ page = SubredditPage(self.reddit, self.term, self.config,
+ self.oauth, name)
+ if not self.term.loader.exception:
+ return page
+
+ def clear_input_queue(self):
+ """
+ Clear excessive input caused by the scroll wheel or holding down a key
+ """
+ with self.term.no_delay():
+ while self.term.getch() != -1:
+ continue
+
+ def draw(self):
+ """
+ Clear the terminal screen and redraw all of the sub-windows
+ """
+ n_rows, n_cols = self.term.stdscr.getmaxyx()
+ if n_rows < self.term.MIN_HEIGHT or n_cols < self.term.MIN_WIDTH:
+ # TODO: Will crash when you try to navigate if the terminal is too
+ # small at startup because self._subwindows will never be populated
+ return
+
+ self._row = 0
+ self._draw_header()
+ self._draw_banner()
+ self._draw_content()
+ self._draw_footer()
+ self.term.clear_screen()
+ self.term.stdscr.refresh()
+
+ def _draw_header(self):
+ """
+ Draw the title bar at the top of the screen
+ """
+ n_rows, n_cols = self.term.stdscr.getmaxyx()
+
+ # Note: 2 argument form of derwin breaks PDcurses on Windows 7!
+ window = self.term.stdscr.derwin(1, n_cols, self._row, 0)
+ window.erase()
+ # curses.bkgd expects bytes in py2 and unicode in py3
+ window.bkgd(str(' '), self.term.attr('TitleBar'))
+
+ sub_name = self.content.name
+ sub_name = sub_name.replace('/r/front', 'Front Page')
+
+ parts = sub_name.split('/')
+ if len(parts) == 1:
+ pass
+ elif '/m/' in sub_name:
+ _, _, user, _, multi = parts
+ sub_name = '{} Curated by {}'.format(multi, user)
+ elif parts[1] == 'u':
+ noun = 'My' if parts[2] == 'me' else parts[2] + "'s"
+ user_room = parts[3] if len(parts) == 4 else 'overview'
+ title_lookup = {
+ 'overview': 'Overview',
+ 'submitted': 'Submissions',
+ 'comments': 'Comments',
+ 'saved': 'Saved Content',
+ 'hidden': 'Hidden Content',
+ 'upvoted': 'Upvoted Content',
+ 'downvoted': 'Downvoted Content'
+ }
+ sub_name = "{} {}".format(noun, title_lookup[user_room])
+
+ query = self.content.query
+ if query:
+ sub_name = 'Searching {0}: {1}'.format(sub_name, query)
+ self.term.add_line(window, sub_name, 0, 0)
+
+ # Set the terminal title
+ if len(sub_name) > 50:
+ title = sub_name.strip('/')
+ title = title.replace('_', ' ')
+ try:
+ title = title.rsplit('/', 1)[1]
+ except IndexError:
+ pass
+ else:
+ title = sub_name
+
+ # Setting the terminal title will break emacs or systems without
+ # X window.
+ if os.getenv('DISPLAY') and not os.getenv('INSIDE_EMACS'):
+ title += ' - ttrv {0}'.format(__version__)
+ title = self.term.clean(title)
+ if six.PY3:
+ # In py3 you can't write bytes to stdout
+ title = title.decode('utf-8')
+ title = '\x1b]2;{0}\x07'.format(title)
+ else:
+ title = b'\x1b]2;{0}\x07'.format(title)
+ sys.stdout.write(title)
+ sys.stdout.flush()
+
+ if self.reddit and self.reddit.user is not None:
+ # The starting position of the name depends on if we're converting
+ # to ascii or not
+ width = len if self.config['ascii'] else textual_width
+
+ if self.config['hide_username']:
+ username = "Logged in"
+ else:
+ username = self.reddit.user.name
+ s_col = (n_cols - width(username) - 1)
+ # Only print username if it fits in the empty space on the right
+ if (s_col - 1) >= width(sub_name):
+ self.term.add_line(window, username, 0, s_col)
+
+ self._row += 1
+
+ def _draw_banner(self):
+ """
+ Draw the banner with sorting options at the top of the page
+ """
+ n_rows, n_cols = self.term.stdscr.getmaxyx()
+ window = self.term.stdscr.derwin(1, n_cols, self._row, 0)
+ window.erase()
+ window.bkgd(str(' '), self.term.attr('OrderBar'))
+
+ banner = docs.BANNER_SEARCH if self.content.query else self.BANNER
+ items = banner.strip().split(' ')
+
+ distance = (n_cols - sum(len(t) for t in items) - 1) / (len(items) - 1)
+ spacing = max(1, int(distance)) * ' '
+ text = spacing.join(items)
+ self.term.add_line(window, text, 0, 0)
+ if self.content.order is not None:
+ order = self.content.order.split('-')[0]
+ col = text.find(order) - 3
+ attr = self.term.attr('OrderBarHighlight')
+ window.chgat(0, col, 3, attr)
+
+ self._row += 1
+
+ def _draw_content(self):
+ """
+ Loop through submissions and fill up the content page.
+ """
+ n_rows, n_cols = self.term.stdscr.getmaxyx()
+ window = self.term.stdscr.derwin(n_rows - self._row - 1, n_cols, self._row, 0)
+ window.erase()
+ win_n_rows, win_n_cols = window.getmaxyx()
+
+ self._subwindows = []
+ page_index, cursor_index, inverted = self.nav.position
+ step = self.nav.step
+
+ # If not inverted, align the first submission with the top and draw
+ # downwards. If inverted, align the first submission with the bottom
+ # and draw upwards.
+ cancel_inverted = True
+ current_row = (win_n_rows - 1) if inverted else 0
+ available_rows = win_n_rows
+ top_item_height = None if inverted else self.nav.top_item_height
+ for data in self.content.iterate(page_index, step, win_n_cols - 2):
+ subwin_n_rows = min(available_rows, data['n_rows'])
+ subwin_inverted = inverted
+ if top_item_height is not None:
+ # Special case: draw the page as non-inverted, except for the
+ # top element. This element will be drawn as inverted with a
+ # restricted height
+ subwin_n_rows = min(subwin_n_rows, top_item_height)
+ subwin_inverted = True
+ top_item_height = None
+ subwin_n_cols = win_n_cols - data['h_offset']
+ start = current_row - subwin_n_rows + 1 if inverted else current_row
+ subwindow = window.derwin(subwin_n_rows, subwin_n_cols, start, data['h_offset'])
+ self._subwindows.append((subwindow, data, subwin_inverted))
+ available_rows -= (subwin_n_rows + 1) # Add one for the blank line
+ current_row += step * (subwin_n_rows + 1)
+ if available_rows <= 0:
+ # Indicate the page is full and we can keep the inverted screen.
+ cancel_inverted = False
+ break
+
+ if len(self._subwindows) == 1:
+ # Never draw inverted if only one subwindow. The top of the
+ # subwindow should always be aligned with the top of the screen.
+ cancel_inverted = True
+
+ if cancel_inverted and self.nav.inverted:
+ # In some cases we need to make sure that the screen is NOT
+ # inverted. Unfortunately, this currently means drawing the whole
+ # page over again. Could not think of a better way to pre-determine
+ # if the content will fill up the page, given that it is dependent
+ # on the size of the terminal.
+ self.nav.flip((len(self._subwindows) - 1))
+ self._draw_content()
+ return
+
+ if self.nav.cursor_index >= len(self._subwindows):
+ # Don't allow the cursor to go over the number of subwindows
+ # This could happen if the window is resized and the cursor index is
+ # pushed out of bounds
+ self.nav.cursor_index = len(self._subwindows) - 1
+
+ # Now that the windows are setup, we can take a second pass through
+ # to draw the text onto each subwindow
+ for index, (win, data, inverted) in enumerate(self._subwindows):
+ if self.nav.absolute_index >= 0 and index == self.nav.cursor_index:
+ win.bkgd(str(' '), self.term.attr('Selected'))
+ with self.term.theme.turn_on_selected():
+ self._draw_item(win, data, inverted)
+ else:
+ win.bkgd(str(' '), self.term.attr('Normal'))
+ self._draw_item(win, data, inverted)
+
+ self._row += win_n_rows
+
+ def _draw_footer(self):
+ """
+ Draw the key binds help bar at the bottom of the screen
+ """
+ n_rows, n_cols = self.term.stdscr.getmaxyx()
+ window = self.term.stdscr.derwin(1, n_cols, self._row, 0)
+ window.erase()
+ window.bkgd(str(' '), self.term.attr('HelpBar'))
+
+ text = self.FOOTER.strip()
+ self.term.add_line(window, text, 0, 0)
+ self._row += 1
+
+ def _move_cursor(self, direction):
+ # Note: ACS_VLINE doesn't like changing the attribute, so disregard the
+ # redraw flag and opt to always redraw
+ valid, redraw = self.nav.move(direction, len(self._subwindows))
+ if not valid:
+ self.term.flash()
+
+ def _move_cursor_to_unread(self, direction):
+ url_in_history = True
+ valid = True
+ while valid and url_in_history:
+ valid, redraw = self.nav.move(direction, len(self._subwindows))
+ if valid:
+ data = self.get_selected_item()
+ url_in_history = data['url_full'] in self.config.history
+ if not valid:
+ self.term.flash()
+
+ def _move_page(self, direction):
+ valid, redraw = self.nav.move_page(direction, len(self._subwindows)-1)
+ if not valid:
+ self.term.flash()
+
+ def _prompt_period(self, order):
+ choices = {
+ '\n': order,
+ '1': '{0}-hour'.format(order),
+ '2': '{0}-day'.format(order),
+ '3': '{0}-week'.format(order),
+ '4': '{0}-month'.format(order),
+ '5': '{0}-year'.format(order),
+ '6': '{0}-all'.format(order)}
+
+ message = docs.TIME_ORDER_MENU.strip().splitlines()
+ ch = self.term.show_notification(message)
+ ch = six.unichr(ch)
+ return choices.get(ch)
diff --git a/ttrv/submission_page.py b/ttrv/submission_page.py
new file mode 100644
index 0000000..6f5db44
--- /dev/null
+++ b/ttrv/submission_page.py
@@ -0,0 +1,415 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from . import docs
+from .content import SubmissionContent
+from .page import Page, PageController, logged_in
+from .objects import Navigator, Command
+
+
+class SubmissionController(PageController):
+ character_map = {}
+
+
+class SubmissionPage(Page):
+ BANNER = docs.BANNER_SUBMISSION
+ FOOTER = docs.FOOTER_SUBMISSION
+
+ name = 'submission'
+
+ def __init__(self, reddit, term, config, oauth, url=None, submission=None):
+ super(SubmissionPage, self).__init__(reddit, term, config, oauth)
+
+ self.controller = SubmissionController(self, keymap=config.keymap)
+
+ if url:
+ self.content = SubmissionContent.from_url(
+ reddit, url, term.loader,
+ max_comment_cols=config['max_comment_cols'])
+ else:
+ self.content = SubmissionContent(
+ submission, term.loader,
+ max_comment_cols=config['max_comment_cols'])
+
+ # Start at the submission post, which is indexed as -1
+ self.nav = Navigator(self.content.get, page_index=-1)
+
+ def handle_selected_page(self):
+ """
+ Open the subscription page in a subwindow, but close the current page
+ if any other type of page is selected.
+ """
+ if not self.selected_page:
+ pass
+ elif self.selected_page.name == 'subscription':
+ # Launch page in a subwindow
+ self.selected_page = self.selected_page.loop()
+ elif self.selected_page.name in ('subreddit', 'submission', 'inbox'):
+ # Replace the current page
+ self.active = False
+ else:
+ raise RuntimeError(self.selected_page.name)
+
+ def refresh_content(self, order=None, name=None):
+ """
+ Re-download comments and reset the page index
+ """
+ order = order or self.content.order
+ url = name or self.content.name
+
+ # Hack to allow an order specified in the name by prompt_subreddit() to
+ # override the current default
+ if order == 'ignore':
+ order = None
+
+ with self.term.loader('Refreshing page'):
+ self.content = SubmissionContent.from_url(
+ self.reddit, url, self.term.loader, order=order,
+ max_comment_cols=self.config['max_comment_cols'])
+ if not self.term.loader.exception:
+ self.nav = Navigator(self.content.get, page_index=-1)
+
+ @SubmissionController.register(Command('SORT_1'))
+ def sort_content_hot(self):
+ self.refresh_content(order='hot')
+
+ @SubmissionController.register(Command('SORT_2'))
+ def sort_content_top(self):
+ self.refresh_content(order='top')
+
+ @SubmissionController.register(Command('SORT_3'))
+ def sort_content_rising(self):
+ self.refresh_content(order='rising')
+
+ @SubmissionController.register(Command('SORT_4'))
+ def sort_content_new(self):
+ self.refresh_content(order='new')
+
+ @SubmissionController.register(Command('SORT_5'))
+ def sort_content_controversial(self):
+ self.refresh_content(order='controversial')
+
+ @SubmissionController.register(Command('SUBMISSION_TOGGLE_COMMENT'))
+ def toggle_comment(self):
+ """
+ Toggle the selected comment tree between visible and hidden
+ """
+ current_index = self.nav.absolute_index
+ self.content.toggle(current_index)
+
+ # This logic handles a display edge case after a comment toggle. We
+ # want to make sure that when we re-draw the page, the cursor stays at
+ # its current absolute position on the screen. In order to do this,
+ # apply a fixed offset if, while inverted, we either try to hide the
+ # bottom comment or toggle any of the middle comments.
+ if self.nav.inverted:
+ data = self.content.get(current_index)
+ if data['hidden'] or self.nav.cursor_index != 0:
+ window = self._subwindows[-1][0]
+ n_rows, _ = window.getmaxyx()
+ self.nav.flip(len(self._subwindows) - 1)
+ self.nav.top_item_height = n_rows
+
+ @SubmissionController.register(Command('SUBMISSION_EXIT'))
+ def exit_submission(self):
+ """
+ Close the submission and return to the subreddit page
+ """
+ self.active = False
+
+ @SubmissionController.register(Command('SUBMISSION_OPEN_IN_BROWSER'))
+ def open_link(self):
+ """
+ Open the link contained in the selected item.
+
+ If there is more than one link contained in the item, prompt the user
+ to choose which link to open.
+ """
+ data = self.get_selected_item()
+ if data['type'] == 'Submission':
+ link = self.prompt_and_select_link()
+ if link:
+ self.config.history.add(link)
+ self.term.open_link(link)
+ elif data['type'] == 'Comment':
+ link = self.prompt_and_select_link()
+ if link:
+ self.term.open_link(link)
+ else:
+ self.term.flash()
+
+ @SubmissionController.register(Command('SUBMISSION_OPEN_IN_PAGER'))
+ def open_pager(self):
+ """
+ Open the selected item with the system's pager
+ """
+ n_rows, n_cols = self.term.stdscr.getmaxyx()
+
+ if self.config['max_pager_cols'] is not None:
+ n_cols = min(n_cols, self.config['max_pager_cols'])
+
+ data = self.get_selected_item()
+ if data['type'] == 'Submission':
+ text = '\n\n'.join((data['permalink'], data['text']))
+ self.term.open_pager(text, wrap=n_cols)
+ elif data['type'] == 'Comment':
+ text = '\n\n'.join((data['permalink'], data['body']))
+ self.term.open_pager(text, wrap=n_cols)
+ else:
+ self.term.flash()
+
+ @SubmissionController.register(Command('SUBMISSION_POST'))
+ @logged_in
+ def add_comment(self):
+ """
+ Submit a reply to the selected item.
+ """
+ self.reply()
+
+ @SubmissionController.register(Command('DELETE'))
+ @logged_in
+ def delete_comment(self):
+ """
+ Delete the selected comment
+ """
+ if self.get_selected_item()['type'] == 'Comment':
+ self.delete_item()
+ else:
+ self.term.flash()
+
+ @SubmissionController.register(Command('SUBMISSION_OPEN_IN_URLVIEWER'))
+ def comment_urlview(self):
+ """
+ Open the selected comment with the URL viewer
+ """
+ data = self.get_selected_item()
+ comment = data.get('body') or data.get('text') or data.get('url_full')
+ if comment:
+ self.term.open_urlview(comment)
+ else:
+ self.term.flash()
+
+ @SubmissionController.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()
+
+ @SubmissionController.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':
+ return self._draw_more_comments(win, data)
+ elif data['type'] == 'HiddenComment':
+ return self._draw_more_comments(win, data)
+ elif data['type'] == 'Comment':
+ return self._draw_comment(win, data, inverted)
+ else:
+ return self._draw_submission(win, data)
+
+ def _draw_comment(self, win, data, inverted):
+
+ n_rows, n_cols = win.getmaxyx()
+ n_cols -= 1
+
+ # Handle the case where the window is not large enough to fit the text.
+ valid_rows = range(0, n_rows)
+ offset = 0 if not inverted else -(data['n_rows'] - n_rows)
+
+ # If there isn't enough space to fit the comment body on the screen,
+ # replace the last line with a notification.
+ split_body = data['split_body']
+ if data['n_rows'] > n_rows:
+ # Only when there is a single comment on the page and not inverted
+ if not inverted and len(self._subwindows) == 1:
+ cutoff = data['n_rows'] - n_rows + 1
+ split_body = split_body[:-cutoff]
+ split_body.append('(Not enough space to display)')
+
+ row = offset
+ if row in valid_rows:
+ if data['is_author']:
+ attr = self.term.attr('CommentAuthorSelf')
+ text = '{author} [S]'.format(**data)
+ else:
+ attr = self.term.attr('CommentAuthor')
+ text = '{author}'.format(**data)
+ self.term.add_line(win, text, row, 1, attr)
+
+ if data['flair']:
+ attr = self.term.attr('UserFlair')
+ self.term.add_space(win)
+ self.term.add_line(win, '{flair}'.format(**data), attr=attr)
+
+ arrow, attr = self.term.get_arrow(data['likes'])
+ self.term.add_space(win)
+ self.term.add_line(win, arrow, attr=attr)
+
+ attr = self.term.attr('Score')
+ self.term.add_space(win)
+ self.term.add_line(win, '{score}'.format(**data), attr=attr)
+
+ attr = self.term.attr('Created')
+ self.term.add_space(win)
+ self.term.add_line(win, '{created}{edited}'.format(**data),
+ attr=attr)
+
+ if data['gold']:
+ attr = self.term.attr('Gold')
+ self.term.add_space(win)
+ count = 'x{}'.format(data['gold']) if data['gold'] > 1 else ''
+ text = self.term.gilded + count
+ self.term.add_line(win, text, attr=attr)
+
+ if data['stickied']:
+ attr = self.term.attr('Stickied')
+ self.term.add_space(win)
+ self.term.add_line(win, '[stickied]', attr=attr)
+
+ if data['saved']:
+ attr = self.term.attr('Saved')
+ self.term.add_space(win)
+ self.term.add_line(win, '[saved]', attr=attr)
+
+ for row, text in enumerate(split_body, start=offset + 1):
+ attr = self.term.attr('CommentText')
+ if row in valid_rows:
+ self.term.add_line(win, text, row, 1, attr=attr)
+
+ # curses.vline() doesn't support custom colors so need to build the
+ # cursor bar on the left of the comment one character at a time
+ index = data['level'] % len(self.term.theme.CURSOR_BARS)
+ attr = self.term.attr(self.term.theme.CURSOR_BARS[index])
+ for y in range(n_rows):
+ self.term.addch(win, y, 0, self.term.vline, attr)
+
+ def _draw_more_comments(self, win, data):
+
+ n_rows, n_cols = win.getmaxyx()
+ n_cols -= 1
+
+ attr = self.term.attr('HiddenCommentText')
+ self.term.add_line(win, '{body}'.format(**data), 0, 1, attr=attr)
+
+ attr = self.term.attr('HiddenCommentExpand')
+ self.term.add_space(win)
+ self.term.add_line(win, '[{count}]'.format(**data), attr=attr)
+
+ index = data['level'] % len(self.term.theme.CURSOR_BARS)
+ attr = self.term.attr(self.term.theme.CURSOR_BARS[index])
+ self.term.addch(win, 0, 0, self.term.vline, attr)
+
+ def _draw_submission(self, win, data):
+
+ n_rows, n_cols = win.getmaxyx()
+ n_cols -= 3 # one for each side of the border + one for offset
+
+ attr = self.term.attr('SubmissionTitle')
+ for row, text in enumerate(data['split_title'], start=1):
+ self.term.add_line(win, text, row, 1, attr)
+
+ row = len(data['split_title']) + 1
+ attr = self.term.attr('SubmissionAuthor')
+ self.term.add_line(win, '{author}'.format(**data), row, 1, attr)
+
+ if data['flair']:
+ attr = self.term.attr('SubmissionFlair')
+ self.term.add_space(win)
+ self.term.add_line(win, '{flair}'.format(**data), attr=attr)
+
+ attr = self.term.attr('SubmissionSubreddit')
+ self.term.add_space(win)
+ self.term.add_line(win, '/r/{subreddit}'.format(**data), attr=attr)
+
+ attr = self.term.attr('Created')
+ self.term.add_space(win)
+ self.term.add_line(win, '{created_long}{edited_long}'.format(**data),
+ attr=attr)
+
+ row = len(data['split_title']) + 2
+ if data['url_full'] in self.config.history:
+ attr = self.term.attr('LinkSeen')
+ else:
+ attr = self.term.attr('Link')
+ self.term.add_line(win, '{url}'.format(**data), row, 1, attr)
+
+ offset = len(data['split_title']) + 3
+
+ # Cut off text if there is not enough room to display the whole post
+ split_text = data['split_text']
+ if data['n_rows'] > n_rows:
+ cutoff = data['n_rows'] - n_rows + 1
+ split_text = split_text[:-cutoff]
+ split_text.append('(Not enough space to display)')
+
+ attr = self.term.attr('SubmissionText')
+ for row, text in enumerate(split_text, start=offset):
+ self.term.add_line(win, text, row, 1, attr=attr)
+
+ row = len(data['split_title']) + len(split_text) + 3
+ attr = self.term.attr('Score')
+ self.term.add_line(win, '{score}'.format(**data), row, 1, attr=attr)
+
+ arrow, attr = self.term.get_arrow(data['likes'])
+ self.term.add_space(win)
+ self.term.add_line(win, arrow, attr=attr)
+
+ attr = self.term.attr('CommentCount')
+ self.term.add_space(win)
+ self.term.add_line(win, '{comments}'.format(**data), attr=attr)
+
+ if data['gold']:
+ attr = self.term.attr('Gold')
+ self.term.add_space(win)
+ count = 'x{}'.format(data['gold']) if data['gold'] > 1 else ''
+ text = self.term.gilded + count
+ self.term.add_line(win, text, attr=attr)
+
+ if data['nsfw']:
+ attr = self.term.attr('NSFW')
+ self.term.add_space(win)
+ self.term.add_line(win, 'NSFW', attr=attr)
+
+ if data['saved']:
+ attr = self.term.attr('Saved')
+ self.term.add_space(win)
+ self.term.add_line(win, '[saved]', attr=attr)
+
+ win.border()
diff --git a/ttrv/subreddit_page.py b/ttrv/subreddit_page.py
new file mode 100644
index 0000000..86b352e
--- /dev/null
+++ b/ttrv/subreddit_page.py
@@ -0,0 +1,329 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+import time
+
+from . import docs
+from .content import SubredditContent
+from .page import Page, PageController, logged_in
+from .objects import Navigator, Command
+from .exceptions import TemporaryFileError
+
+
+class SubredditController(PageController):
+ character_map = {}
+
+
+class SubredditPage(Page):
+ BANNER = docs.BANNER_SUBREDDIT
+ FOOTER = docs.FOOTER_SUBREDDIT
+
+ name = 'subreddit'
+
+ def __init__(self, reddit, term, config, oauth, name):
+ """
+ Params:
+ name (string): Name of subreddit to open
+ """
+ super(SubredditPage, self).__init__(reddit, term, config, oauth)
+
+ self.controller = SubredditController(self, keymap=config.keymap)
+ self.content = SubredditContent.from_name(reddit, name, term.loader)
+ self.nav = Navigator(self.content.get)
+ self.toggled_subreddit = None
+
+ def handle_selected_page(self):
+ """
+ Open all selected pages in subwindows except other subreddit pages.
+ """
+ if not self.selected_page:
+ pass
+ elif self.selected_page.name in ('subscription', 'submission', 'inbox'):
+ # Launch page in a subwindow
+ self.selected_page = self.selected_page.loop()
+ elif self.selected_page.name == 'subreddit':
+ # Replace the current page
+ self.active = False
+ else:
+ raise RuntimeError(self.selected_page.name)
+
+ def refresh_content(self, order=None, name=None):
+ """
+ Re-download all submissions and reset the page index
+ """
+ order = order or self.content.order
+
+ # Preserve the query if staying on the current page
+ if name is None:
+ query = self.content.query
+ else:
+ query = None
+
+ name = name or self.content.name
+
+ # Hack to allow an order specified in the name by prompt_subreddit() to
+ # override the current default
+ if order == 'ignore':
+ order = None
+
+ with self.term.loader('Refreshing page'):
+ self.content = SubredditContent.from_name(
+ self.reddit, name, self.term.loader, order=order, query=query)
+ if not self.term.loader.exception:
+ self.nav = Navigator(self.content.get)
+
+ @SubredditController.register(Command('SORT_1'))
+ def sort_content_hot(self):
+ if self.content.query:
+ self.refresh_content(order='relevance')
+ else:
+ self.refresh_content(order='hot')
+
+ @SubredditController.register(Command('SORT_2'))
+ def sort_content_top(self):
+ order = self._prompt_period('top')
+ if order is None:
+ self.term.show_notification('Invalid option')
+ else:
+ self.refresh_content(order=order)
+
+ @SubredditController.register(Command('SORT_3'))
+ def sort_content_rising(self):
+ if self.content.query:
+ order = self._prompt_period('comments')
+ if order is None:
+ self.term.show_notification('Invalid option')
+ else:
+ self.refresh_content(order=order)
+ else:
+ self.refresh_content(order='rising')
+
+ @SubredditController.register(Command('SORT_4'))
+ def sort_content_new(self):
+ self.refresh_content(order='new')
+
+ @SubredditController.register(Command('SORT_5'))
+ def sort_content_controversial(self):
+ if self.content.query:
+ self.term.flash()
+ else:
+ order = self._prompt_period('controversial')
+ if order is None:
+ self.term.show_notification('Invalid option')
+ else:
+ self.refresh_content(order=order)
+
+ @SubredditController.register(Command('SORT_6'))
+ def sort_content_gilded(self):
+ if self.content.query:
+ self.term.flash()
+ else:
+ self.refresh_content(order='gilded')
+
+ @SubredditController.register(Command('SUBREDDIT_SEARCH'))
+ def search_subreddit(self, name=None):
+ """
+ Open a prompt to search the given subreddit
+ """
+ name = name or self.content.name
+
+ query = self.term.prompt_input('Search {0}: '.format(name))
+ if not query:
+ return
+
+ with self.term.loader('Searching'):
+ self.content = SubredditContent.from_name(
+ self.reddit, name, self.term.loader, query=query)
+ if not self.term.loader.exception:
+ self.nav = Navigator(self.content.get)
+
+ @SubredditController.register(Command('SUBREDDIT_FRONTPAGE'))
+ def show_frontpage(self):
+ """
+ If on a subreddit, remember it and head back to the front page.
+ If this was pressed on the front page, go back to the last subreddit.
+ """
+
+ if self.content.name != '/r/front':
+ target = '/r/front'
+ self.toggled_subreddit = self.content.name
+ else:
+ target = self.toggled_subreddit
+
+ # target still may be empty string if this command hasn't yet been used
+ if target is not None:
+ self.refresh_content(order='ignore', name=target)
+
+ @SubredditController.register(Command('SUBREDDIT_OPEN'))
+ def open_submission(self, url=None):
+ """
+ Select the current submission to view posts.
+ """
+ if url is None:
+ data = self.get_selected_item()
+ url = data['permalink']
+ if data.get('url_type') == 'selfpost':
+ self.config.history.add(data['url_full'])
+
+ self.selected_page = self.open_submission_page(url)
+
+ @SubredditController.register(Command('SUBREDDIT_OPEN_IN_BROWSER'))
+ def open_link(self):
+ """
+ Open a link with the webbrowser
+ """
+
+ data = self.get_selected_item()
+ if data['url_type'] == 'selfpost':
+ self.open_submission()
+ elif data['url_type'] == 'x-post subreddit':
+ self.refresh_content(order='ignore', name=data['xpost_subreddit'])
+ elif data['url_type'] == 'x-post submission':
+ self.open_submission(url=data['url_full'])
+ self.config.history.add(data['url_full'])
+ else:
+ self.term.open_link(data['url_full'])
+ self.config.history.add(data['url_full'])
+
+ @SubredditController.register(Command('SUBREDDIT_POST'))
+ @logged_in
+ def post_submission(self):
+ """
+ Post a new submission to the given subreddit.
+ """
+ # Check that the subreddit can be submitted to
+ name = self.content.name
+ if '+' in name or name in ('/r/all', '/r/front', '/r/me', '/u/saved'):
+ self.term.show_notification("Can't post to {0}".format(name))
+ return
+
+ submission_info = docs.SUBMISSION_FILE.format(name=name)
+ with self.term.open_editor(submission_info) as text:
+ if not text:
+ self.term.show_notification('Canceled')
+ return
+ elif '\n' not in text:
+ self.term.show_notification('Missing body')
+ return
+
+ title, content = text.split('\n', 1)
+ with self.term.loader('Posting', delay=0):
+ submission = self.reddit.submit(name, title, text=content,
+ raise_captcha_exception=True)
+ # Give reddit time to process the submission
+ time.sleep(2.0)
+ if self.term.loader.exception:
+ raise TemporaryFileError()
+
+ if not self.term.loader.exception:
+ # Open the newly created submission
+ self.selected_page = self.open_submission_page(submission=submission)
+
+ @SubredditController.register(Command('SUBREDDIT_HIDE'))
+ @logged_in
+ def hide(self):
+ data = self.get_selected_item()
+ if not hasattr(data["object"], 'hide'):
+ self.term.flash()
+ elif data['hidden']:
+ with self.term.loader('Unhiding'):
+ data['object'].unhide()
+ data['hidden'] = False
+ else:
+ with self.term.loader('Hiding'):
+ data['object'].hide()
+ data['hidden'] = True
+
+ def _draw_item(self, win, data, inverted):
+
+ n_rows, n_cols = win.getmaxyx()
+ n_cols -= 1 # Leave space for the cursor in the first column
+
+ # Handle the case where the window is not large enough to fit the data.
+ valid_rows = range(0, n_rows)
+ offset = 0 if not inverted else -(data['n_rows'] - n_rows)
+
+ n_title = len(data['split_title'])
+ if data['url_full'] in self.config.history:
+ attr = self.term.attr('SubmissionTitleSeen')
+ else:
+ attr = self.term.attr('SubmissionTitle')
+ for row, text in enumerate(data['split_title'], start=offset):
+ if row in valid_rows:
+ self.term.add_line(win, text, row, 1, attr)
+
+ row = n_title + offset
+ if data['url_full'] in self.config.history:
+ attr = self.term.attr('LinkSeen')
+ else:
+ attr = self.term.attr('Link')
+ if row in valid_rows:
+ self.term.add_line(win, '{url}'.format(**data), row, 1, attr)
+
+ row = n_title + offset + 1
+ if row in valid_rows:
+
+ attr = self.term.attr('Score')
+ self.term.add_line(win, '{score}'.format(**data), row, 1, attr)
+ self.term.add_space(win)
+
+ arrow, attr = self.term.get_arrow(data['likes'])
+ self.term.add_line(win, arrow, attr=attr)
+ self.term.add_space(win)
+
+ attr = self.term.attr('Created')
+ self.term.add_line(win, '{created}{edited}'.format(**data), attr=attr)
+
+ if data['comments'] is not None:
+ attr = self.term.attr('Separator')
+ self.term.add_space(win)
+ self.term.add_line(win, '-', attr=attr)
+
+ attr = self.term.attr('CommentCount')
+ self.term.add_space(win)
+ self.term.add_line(win, '{comments}'.format(**data), attr=attr)
+
+ if data['saved']:
+ attr = self.term.attr('Saved')
+ self.term.add_space(win)
+ self.term.add_line(win, '[saved]', attr=attr)
+
+ if data['hidden']:
+ attr = self.term.attr('Hidden')
+ self.term.add_space(win)
+ self.term.add_line(win, '[hidden]', attr=attr)
+
+ if data['stickied']:
+ attr = self.term.attr('Stickied')
+ self.term.add_space(win)
+ self.term.add_line(win, '[stickied]', attr=attr)
+
+ if data['gold']:
+ attr = self.term.attr('Gold')
+ self.term.add_space(win)
+ count = 'x{}'.format(data['gold']) if data['gold'] > 1 else ''
+ text = self.term.gilded + count
+ self.term.add_line(win, text, attr=attr)
+
+ if data['nsfw']:
+ attr = self.term.attr('NSFW')
+ self.term.add_space(win)
+ self.term.add_line(win, 'NSFW', attr=attr)
+
+ row = n_title + offset + 2
+ if row in valid_rows:
+ attr = self.term.attr('SubmissionAuthor')
+ self.term.add_line(win, '{author}'.format(**data), row, 1, attr)
+ self.term.add_space(win)
+
+ attr = self.term.attr('SubmissionSubreddit')
+ self.term.add_line(win, '/r/{subreddit}'.format(**data), attr=attr)
+
+ if data['flair']:
+ attr = self.term.attr('SubmissionFlair')
+ self.term.add_space(win)
+ self.term.add_line(win, '{flair}'.format(**data), attr=attr)
+
+ attr = self.term.attr('CursorBlock')
+ for y in range(n_rows):
+ self.term.addch(win, y, 0, str(' '), attr)
diff --git a/ttrv/subscription_page.py b/ttrv/subscription_page.py
new file mode 100644
index 0000000..da6cc9d
--- /dev/null
+++ b/ttrv/subscription_page.py
@@ -0,0 +1,97 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from . import docs
+from .content import SubscriptionContent
+from .page import Page, PageController, logged_in
+from .objects import Navigator, Command
+
+
+class SubscriptionController(PageController):
+ character_map = {}
+
+
+class SubscriptionPage(Page):
+ BANNER = None
+ FOOTER = docs.FOOTER_SUBSCRIPTION
+
+ name = 'subscription'
+
+ def __init__(self, reddit, term, config, oauth, content_type='subreddit'):
+ super(SubscriptionPage, self).__init__(reddit, term, config, oauth)
+
+ self.controller = SubscriptionController(self, keymap=config.keymap)
+ self.content = SubscriptionContent.from_user(
+ reddit, term.loader, content_type)
+ self.nav = Navigator(self.content.get)
+ self.content_type = content_type
+
+ def handle_selected_page(self):
+ """
+ Always close the current page when another page is selected.
+ """
+ if self.selected_page:
+ self.active = False
+
+ def refresh_content(self, order=None, name=None):
+ """
+ Re-download all subscriptions and reset the page index
+ """
+ # reddit.get_my_subreddits() does not support sorting by order
+ if order:
+ self.term.flash()
+ return
+
+ with self.term.loader():
+ self.content = SubscriptionContent.from_user(
+ self.reddit, self.term.loader, self.content_type)
+ if not self.term.loader.exception:
+ self.nav = Navigator(self.content.get)
+
+ @SubscriptionController.register(Command('SUBSCRIPTION_SELECT'))
+ def select_subreddit(self):
+ """
+ Store the selected subreddit and return to the subreddit page
+ """
+ name = self.get_selected_item()['name']
+ self.selected_page = self.open_subreddit_page(name)
+
+ @SubscriptionController.register(Command('SUBSCRIPTION_EXIT'))
+ def close_subscriptions(self):
+ """
+ Close subscriptions and return to the subreddit page
+ """
+ self.active = False
+
+ def _draw_banner(self):
+ # Subscriptions can't be sorted, so disable showing the order menu
+ pass
+
+ def _draw_item(self, win, data, inverted):
+ n_rows, n_cols = win.getmaxyx()
+ n_cols -= 1 # Leave space for the cursor in the first column
+
+ # Handle the case where the window is not large enough to fit the data.
+ valid_rows = range(0, n_rows)
+ offset = 0 if not inverted else -(data['n_rows'] - n_rows)
+
+ row = offset
+ if row in valid_rows:
+ if data['type'] == 'Multireddit':
+ attr = self.term.attr('MultiredditName')
+ else:
+ attr = self.term.attr('SubscriptionName')
+ self.term.add_line(win, '{name}'.format(**data), row, 1, attr)
+
+ row = offset + 1
+ for row, text in enumerate(data['split_title'], start=row):
+ if row in valid_rows:
+ if data['type'] == 'Multireddit':
+ attr = self.term.attr('MultiredditText')
+ else:
+ attr = self.term.attr('SubscriptionText')
+ self.term.add_line(win, text, row, 1, attr)
+
+ attr = self.term.attr('CursorBlock')
+ for y in range(n_rows):
+ self.term.addch(win, y, 0, str(' '), attr)
diff --git a/ttrv/templates/index.html b/ttrv/templates/index.html
new file mode 100644
index 0000000..0e642e8
--- /dev/null
+++ b/ttrv/templates/index.html
@@ -0,0 +1,31 @@
+
+
+
+ RTV OAuth2 Helper
+
+
+
+
+${message}
+
+
+
diff --git a/ttrv/templates/mailcap b/ttrv/templates/mailcap
new file mode 100644
index 0000000..7540b3c
--- /dev/null
+++ b/ttrv/templates/mailcap
@@ -0,0 +1,70 @@
+# Example mailcap file for Terminal Viewer for Reddit
+# https://github.com/tildeclub/ttrv/
+#
+# Copy the contents of this file to {HOME}/.mailcap, or point to it using $MAILCAPS
+# Then launch TTRV using the --enable-media flag. All shell commands defined in
+# this file depend on external programs that must be installed on your system.
+#
+# HELP REQUESTED! If you come up with your own commands (especially for OS X)
+# and would like to share, please post an issue on the GitHub tracker and we
+# can get them added to this file as references.
+#
+#
+# Mailcap 101
+# - 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 ``needsterminal`` for commands that use the terminal
+# - Add ``copiousoutput`` for commands that dump text to stdout
+
+###############################################################################
+# Commands below this point will open media in a separate window without
+# pausing execution of TTRV.
+###############################################################################
+
+# Feh is a simple and effective image viewer
+# Note that ttrv returns a list of urls for imgur albums, so we don't put quotes
+# around the `%s`
+image/x-imgur-album; feh -g 640x480 -. %s; test=test -n "$DISPLAY"
+image/gif; mpv '%s' --autofit=640x480 --loop=inf; test=test -n "$DISPLAY"
+image/*; feh -g 640x480 -. '%s'; test=test -n "$DISPLAY"
+
+# Youtube videos are assigned a custom mime-type, which can be streamed with
+# vlc or youtube-dl.
+video/x-youtube; vlc '%s' --width 640 --height 480; test=test -n "$DISPLAY"
+video/x-youtube; mpv --ytdl-format=bestvideo+bestaudio/best '%s' --autofit=640x480; test=test -n "$DISPLAY"
+
+# Mpv is a simple and effective video streamer
+video/*; mpv '%s' --autofit=640x480 --loop=inf; test=test -n "$DISPLAY"
+
+###############################################################################
+# Commands below this point will attempt to display media directly in the
+# terminal when a desktop is not available (e.g. inside of an SSH session)
+###############################################################################
+
+# View images directly in your terminal with iTerm2
+# curl -L https://iterm2.com/misc/install_shell_integration_and_utilities.sh | bash
+# image/*; bash -c '[[ "%s" == http* ]] && (curl -s %s | ~/.iterm2/imgcat) || ~/.iterm2/imgcat %s' && read -n 1; needsterminal
+
+# View true images in the terminal, supported by rxvt-unicode, xterm and st
+# Requires the w3m-img package
+# image/*; w3m -o 'ext_image_viewer=off' '%s'; needsterminal
+
+# Don't have a solution for albums yet
+image/x-imgur-album; echo
+
+# 256 color images using half-width unicode characters
+# Much higher quality that img2txt, but must be built from source
+# https://github.com/rossy/img2xterm
+image/*; curl -s '%s' | convert -resize 80x80 - jpg:/tmp/ttrv.jpg && img2xterm /tmp/ttrv.jpg; needsterminal; copiousoutput
+
+# Display images in classic ascii using img2txt and lib-caca
+image/*; curl -s '%s' | convert - jpg:/tmp/ttrv.jpg && img2txt -f utf8 /tmp/ttrv.jpg; needsterminal; copiousoutput
+
+# Full motion videos - requires a framebuffer to view
+video/x-youtube; mpv -vo drm -quiet '%s'; needsterminal
+video/*; mpv -vo drm -quiet '%s'; needsterminal
+
+# Ascii videos
+# video/x-youtube; youtube-dl -q -o - '%s' | mplayer -cache 8192 -vo caca -quiet -; needsterminal
+# video/*; wget '%s' -O - | mplayer -cache 8192 -vo caca -quiet -; needsterminal
diff --git a/ttrv/templates/ttrv.cfg b/ttrv/templates/ttrv.cfg
new file mode 100644
index 0000000..101417c
--- /dev/null
+++ b/ttrv/templates/ttrv.cfg
@@ -0,0 +1,182 @@
+; Tilde Terminal Reddit Viewer Configuration File
+; https://github.com/tildeclub/ttrv
+;
+; This file should be placed in $XDG_CONFIG/ttrv/ttrv.cfg
+; If $XDG_CONFIG is not set, use ~/.config/ttrv/ttrv.cfg
+
+[ttrv]
+##################
+# General Settings
+##################
+
+; Turn on ascii-only mode to disable all unicode characters.
+; This may be necessary for compatibility with some terminal browsers.
+ascii = False
+
+; Turn on monochrome mode to disable color.
+monochrome = False
+
+; Flash when an invalid action is executed.
+flash = True
+
+; Enable debugging by logging all HTTP requests and errors to the given file.
+;log = /tmp/ttrv.log
+
+; Default subreddit that will be opened when the program launches.
+subreddit = front
+;subreddit = python
+;subreddit = python+linux+programming
+;subreddit = all
+
+; Allow ttrv to store reddit authentication credentials between sessions.
+persistent = True
+
+; Automatically log in on startup, if credentials are available.
+autologin = True
+
+; Clear any stored credentials when the program starts.
+clear_auth = False
+
+; Maximum number of opened links that will be saved in the history file.
+history_size = 200
+
+; Open external links using programs defined in the mailcap config.
+enable_media = False
+
+; Maximum number of columns for a comment
+max_comment_cols = 120
+
+; Maximum number of columns for pager
+;max_pager_cols = 70
+
+; Hide username if logged in, display "Logged in" instead
+hide_username = False
+
+; Color theme, use "ttrv --list-themes" to view a list of valid options.
+; This can be an absolute filepath, or the name of a theme file that has
+; been installed into either the custom of default theme paths.
+;theme = molokai
+
+; Open a new browser window instead of a new tab in existing instance
+force_new_browser_window = False
+
+################
+# OAuth Settings
+################
+; This sections defines the paramaters that will be used during the OAuth
+; authentication process. ttrv is registered as an "installed app",
+; see https://github.com/reddit/reddit/wiki/OAuth2 for more information.
+
+; These settings are defined at https://www.reddit.com/prefs/apps and should
+; not be altered unless you are defining your own developer application.
+oauth_client_id = E2oEtRQfdfAfNQ
+oauth_client_secret = praw_gapfill
+oauth_redirect_uri = http://127.0.0.1:65000/
+
+; Port that the ttrv webserver will listen on. This should match the redirect
+; uri defined above.
+oauth_redirect_port = 65000
+
+; Access permissions that will be requested.
+oauth_scope = edit,history,identity,mysubreddits,privatemessages,read,report,save,submit,subscribe,vote
+
+; This is a separate token for the imgur api. It's used to extract images
+; from imgur links and albums so they can be opened with mailcap.
+; See https://imgur.com/account/settings/apps to generate your own key.
+imgur_client_id = 93396265f59dec9
+
+[bindings]
+##############
+# Key Bindings
+##############
+; If you would like to define custom bindings, copy this section into your
+; config file with the [bindings] heading. All commands must be bound to at
+; least one key for the config to be valid.
+;
+; 1.) Plain keys can be represented by either uppercase/lowercase characters
+; or the hexadecimal numbers referring their ascii codes. For reference, see
+; https://en.wikipedia.org/wiki/ASCII#ASCII_printable_code_chart
+; e.g. Q, q, 1, ?
+; e.g. 0x20 (space), 0x3c (less-than sign)
+;
+; 2.) Special ascii control codes should be surrounded with <>. For reference,
+; see https://en.wikipedia.org/wiki/ASCII#ASCII_control_code_chart
+; e.g. (enter), (escape)
+;
+; 3.) Other special keys are defined by curses, they should be surrounded by <>
+; and prefixed with KEY_. For reference, see
+; https://docs.python.org/2/library/curses.html#constants
+; e.g. (left arrow), , (page down)
+;
+; Notes:
+; - Curses is unreliable and should always be used in conjunction
+; with .
+; - Use 0x20 for the space key.
+; - A subset of Ctrl modifiers are available through the ascii control codes.
+; For example, Ctrl-D will trigger an signal. See the table above for
+; a complete reference.
+
+; Base page
+EXIT = q
+FORCE_EXIT = Q
+HELP = ?
+SORT_1 = 1
+SORT_2 = 2
+SORT_3 = 3
+SORT_4 = 4
+SORT_5 = 5
+SORT_6 = 6
+SORT_7 = 7
+MOVE_UP = k,
+MOVE_DOWN = j,
+MOVE_NEXT_UNREAD = J
+MOVE_PREV_UNREAD = K
+PREVIOUS_THEME =
+NEXT_THEME =
+PAGE_UP = m, ,
+PAGE_DOWN = n, ,
+PAGE_TOP = gg
+PAGE_BOTTOM = G
+UPVOTE = a
+DOWNVOTE = z
+LOGIN = u
+DELETE = d
+EDIT = e
+INBOX = i
+REFRESH = r,
+PROMPT = /
+SAVE = w
+COPY_PERMALINK = y
+COPY_URL = Y
+PRIVATE_MESSAGE = C
+SUBSCRIPTIONS = s
+MULTIREDDITS = S
+
+; Submission page
+SUBMISSION_TOGGLE_COMMENT = 0x20
+SUBMISSION_OPEN_IN_BROWSER = o, ,
+SUBMISSION_POST = c
+SUBMISSION_EXIT = h,
+SUBMISSION_OPEN_IN_PAGER = l,
+SUBMISSION_OPEN_IN_URLVIEWER = b
+SUBMISSION_GOTO_PARENT = K
+SUBMISSION_GOTO_SIBLING = J
+
+; Subreddit page
+SUBREDDIT_SEARCH = f
+SUBREDDIT_POST = c
+SUBREDDIT_OPEN = l,
+SUBREDDIT_OPEN_IN_BROWSER = o, ,
+SUBREDDIT_FRONTPAGE = p
+SUBREDDIT_HIDE = 0x20
+
+; Subscription page
+SUBSCRIPTION_SELECT = l, , ,
+SUBSCRIPTION_EXIT = h, s, S, ,
+
+; Inbox page
+INBOX_VIEW_CONTEXT = l,
+INBOX_OPEN_SUBMISSION = o, ,
+INBOX_REPLY = c
+INBOX_MARK_READ = w
+INBOX_EXIT = h, ,
diff --git a/ttrv/terminal.py b/ttrv/terminal.py
new file mode 100644
index 0000000..807edf5
--- /dev/null
+++ b/ttrv/terminal.py
@@ -0,0 +1,1015 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+import os
+import re
+import sys
+import time
+import shlex
+import codecs
+import curses
+import logging
+import threading
+import webbrowser
+import subprocess
+import curses.ascii
+from curses import textpad
+from multiprocessing import Process
+from contextlib import contextmanager
+from tempfile import NamedTemporaryFile
+
+import six
+from kitchen.text.display import textual_width_chop
+
+from . import exceptions, mime_parsers, content
+from .docs import TOKEN
+from .theme import Theme, ThemeList
+from .objects import LoadScreen
+
+try:
+ # Fix only needed for versions prior to python 3.6
+ from mailcap_fix import mailcap
+except ImportError:
+ import mailcap
+
+try:
+ # Added in python 3.4+
+ from html import unescape
+except ImportError:
+ from six.moves import html_parser
+ unescape = html_parser.HTMLParser().unescape
+
+if sys.version_info[0] == 3 and sys.version_info[1] >= 8 and sys.platform == 'darwin':
+ from multiprocessing import set_start_method
+ set_start_method('fork')
+
+_logger = logging.getLogger(__name__)
+
+
+class Terminal(object):
+
+ MIN_HEIGHT = 10
+ MIN_WIDTH = 20
+
+ # ASCII codes
+ ESCAPE = 27
+ RETURN = 10
+ SPACE = 32
+
+ def __init__(self, stdscr, config):
+
+ self.stdscr = stdscr
+ self.config = config
+ self.loader = LoadScreen(self)
+ self.theme = None # Initialized by term.set_theme()
+ self.theme_list = ThemeList()
+
+ self._display = None
+ self._mailcap_dict = mailcap.getcaps()
+ self._term = os.environ.get('TERM')
+
+ # This is a hack, the MIME parsers should be stateless
+ # but we need to load the imgur credentials from the config
+ mime_parsers.ImgurApiMIMEParser.CLIENT_ID = config['imgur_client_id']
+
+ @property
+ def up_arrow(self):
+ return '^' if self.config['ascii'] else '▲'
+
+ @property
+ def down_arrow(self):
+ return 'v' if self.config['ascii'] else '▼'
+
+ @property
+ def neutral_arrow(self):
+ return 'o' if self.config['ascii'] else '•'
+
+ @property
+ def gilded(self):
+ return '*' if self.config['ascii'] else '✪'
+
+ @property
+ def vline(self):
+ return getattr(curses, 'ACS_VLINE', ord('|'))
+
+ @property
+ def display(self):
+ """
+ Use a number of methods to guess if the default webbrowser will open in
+ the background as opposed to opening directly in the terminal.
+ """
+
+ if self._display is None:
+ if sys.platform == 'darwin':
+ # OS X won't set $DISPLAY unless xQuartz is installed.
+ # If you're using OS X and you want to access a terminal
+ # browser, you need to set it manually via $BROWSER.
+ # See issue #166
+ display = True
+ else:
+ display = bool(os.environ.get("DISPLAY"))
+
+ # Use the convention defined here to parse $BROWSER
+ # https://docs.python.org/2/library/webbrowser.html
+ console_browsers = ['www-browser', 'links', 'links2', 'elinks',
+ 'lynx', 'w3m']
+ if "BROWSER" in os.environ:
+ user_browser = os.environ["BROWSER"].split(os.pathsep)[0]
+ if user_browser in console_browsers:
+ display = False
+ if webbrowser._tryorder:
+ if webbrowser._tryorder[0] in console_browsers:
+ display = False
+ self._display = display
+ return self._display
+
+ def flash(self):
+ """
+ Flash the screen to indicate that an action was invalid.
+ """
+ if self.config['flash']:
+ return curses.flash()
+ else:
+ return None
+
+ @staticmethod
+ def curs_set(val):
+ """
+ Change the cursor visibility, may fail for some terminals with limited
+ cursor support.
+ """
+ try:
+ curses.curs_set(val)
+ except:
+ pass
+
+ @staticmethod
+ def addch(window, y, x, ch, attr):
+ """
+ Curses addch() method that fixes a major bug in python 3.4.
+
+ See http://bugs.python.org/issue21088
+ """
+
+ if sys.version_info[:3] == (3, 4, 0):
+ y, x = x, y
+
+ window.addch(y, x, ch, attr)
+
+ def getch(self):
+ """
+ Wait for a keypress and return the corresponding character code (int).
+ """
+ return self.stdscr.getch()
+
+ @staticmethod
+ @contextmanager
+ def suspend():
+ """
+ Suspend curses in order to open another subprocess in the terminal.
+ """
+
+ try:
+ curses.endwin()
+ yield
+ finally:
+ curses.doupdate()
+
+ @contextmanager
+ def no_delay(self):
+ """
+ Temporarily turn off character delay mode. In this mode, getch will not
+ block while waiting for input and will return -1 if no key has been
+ pressed.
+ """
+
+ try:
+ self.stdscr.nodelay(1)
+ yield
+ finally:
+ self.stdscr.nodelay(0)
+
+ def get_arrow(self, likes):
+ """
+ Curses does define constants for symbols (e.g. curses.ACS_BULLET).
+ However, they rely on using the curses.addch() function, which has been
+ found to be buggy and a general PITA to work with. By defining them as
+ unicode points they can be added via the more reliable curses.addstr().
+ http://bugs.python.org/issue21088
+ """
+
+ if likes is None:
+ return self.neutral_arrow, self.attr('NeutralVote')
+ elif likes:
+ return self.up_arrow, self.attr('Upvote')
+ else:
+ return self.down_arrow, self.attr('Downvote')
+
+ def clean(self, string, n_cols=None):
+ """
+ Required reading!
+ http://nedbatchelder.com/text/unipain.html
+
+ Python 2 input string will be a unicode type (unicode code points).
+ Curses will accept unicode if all of the points are in the ascii range.
+ However, if any of the code points are not valid ascii curses will
+ throw a UnicodeEncodeError: 'ascii' codec can't encode character,
+ ordinal not in range(128). If we encode the unicode to a utf-8 byte
+ string and pass that to curses, it will render correctly.
+
+ Python 3 input string will be a string type (unicode code points).
+ Curses will accept that in all cases. However, the n character count in
+ addnstr will not be correct. If code points are passed to addnstr,
+ curses will treat each code point as one character and will not account
+ for wide characters. If utf-8 is passed in, addnstr will treat each
+ 'byte' as a single character.
+
+ Reddit's api sometimes chokes and double-encodes some html characters
+ Praw handles the initial decoding, but we need to do a second pass
+ just to make sure. See https://github.com/tildeclub/ttrv/issues/96
+
+ Example:
+ & -> returned directly from reddit's api
+ & -> returned after PRAW decodes the html characters
+ & -> returned after our second pass, this is the true value
+ """
+
+ if n_cols is not None and n_cols <= 0:
+ return ''
+
+ if isinstance(string, six.text_type):
+ string = unescape(string)
+
+ if self.config['ascii']:
+ if isinstance(string, six.binary_type):
+ string = string.decode('utf-8')
+ string = string.encode('ascii', 'replace')
+ return string[:n_cols] if n_cols else string
+ else:
+ if n_cols:
+ string = textual_width_chop(string, n_cols)
+ if isinstance(string, six.text_type):
+ string = string.encode('utf-8')
+ return string
+
+ def add_line(self, window, text, row=None, col=None, attr=None):
+ """
+ Unicode aware version of curses's built-in addnstr method.
+
+ Safely draws a line of text on the window starting at position
+ (row, col). Checks the boundaries of the window and cuts off the text
+ if it exceeds the length of the window.
+ """
+
+ # The following arg combos must be supported to conform with addnstr
+ # (window, text)
+ # (window, text, attr)
+ # (window, text, row, col)
+ # (window, text, row, col, attr)
+ cursor_row, cursor_col = window.getyx()
+ row = row if row is not None else cursor_row
+ col = col if col is not None else cursor_col
+
+ max_rows, max_cols = window.getmaxyx()
+ n_cols = max_cols - col - 1
+ if n_cols <= 0:
+ # Trying to draw outside of the screen bounds
+ return
+
+ try:
+ text = self.clean(text, n_cols)
+ params = [] if attr is None else [attr]
+ window.addstr(row, col, text, *params)
+ except (curses.error, ValueError, TypeError) as e:
+ # Curses handling of strings with invalid null bytes (b'\00')
+ # python 2: TypeError: "int,int,str"
+ # python 3: ValueError: "embedded null byte"
+ _logger.warning('add_line raised an exception')
+ _logger.exception(str(e))
+
+ @staticmethod
+ def add_space(window):
+ """
+ Shortcut for adding a single space to a window at the current position
+ """
+
+ row, col = window.getyx()
+ _, max_cols = window.getmaxyx()
+ n_cols = max_cols - col - 1
+ if n_cols <= 0:
+ # Trying to draw outside of the screen bounds
+ return
+
+ window.addstr(row, col, ' ')
+
+ def show_notification(self, message, timeout=None, style='Info'):
+ """
+ Overlay a message box on the center of the screen and wait for input.
+
+ Params:
+ message (list or string): List of strings, one per line.
+ timeout (float): Optional, maximum length of time that the message
+ will be shown before disappearing.
+ style (str): The theme element that will be applied to the
+ notification window
+ """
+
+ assert style in ('Info', 'Warning', 'Error', 'Success')
+
+ if isinstance(message, six.string_types):
+ message = message.splitlines()
+
+ n_rows, n_cols = self.stdscr.getmaxyx()
+ v_offset, h_offset = self.stdscr.getbegyx()
+
+ box_width = max(len(m) for m in message) + 2
+ box_height = len(message) + 2
+
+ # Cut off the lines of the message that don't fit on the screen
+ box_width = min(box_width, n_cols)
+ box_height = min(box_height, n_rows)
+ message = message[:box_height - 2]
+
+ s_row = (n_rows - box_height) // 2 + v_offset
+ s_col = (n_cols - box_width) // 2 + h_offset
+
+ window = curses.newwin(box_height, box_width, s_row, s_col)
+ window.bkgd(str(' '), self.attr('Notice{0}'.format(style)))
+ window.erase()
+ window.border()
+
+ for index, line in enumerate(message, start=1):
+ self.add_line(window, line, index, 1)
+ window.refresh()
+
+ ch, start = -1, time.time()
+ with self.no_delay():
+ while timeout is None or time.time() - start < timeout:
+ ch = self.getch()
+ if ch != -1:
+ break
+ time.sleep(0.01)
+
+ window.clear()
+ del window
+ self.stdscr.touchwin()
+ self.stdscr.refresh()
+
+ return ch
+
+ def prompt_user_to_select_link(self, links):
+ """
+ Prompt the user to select a link from a list to open.
+
+ Return the link that was selected, or ``None`` if no link was selected.
+ """
+ link_pages = self.get_link_pages(links)
+ n = 0
+ while n in range(len(link_pages)):
+ link_page = link_pages[n]
+ text = 'Select a link to open (page {} of {}):\n\n'
+ text = text.format(n+1, len(link_pages))
+ text += self.get_link_page_text(link_page)
+ if link_page is not link_pages[-1]:
+ text += '[j] next page...'
+ if link_page is not link_pages[0]:
+ if link_page is not link_pages[-1]:
+ text += '\n'
+ text += '[k] ...previous page'
+
+ try:
+ choice = chr(self.show_notification(text))
+ try:
+ choice = int(choice)
+ except ValueError:
+ pass
+ except ValueError:
+ return None
+ if choice == 'j':
+ if link_page is not link_pages[-1]:
+ n += 1
+ continue
+ elif choice == 'k':
+ if link_page is not link_pages[0]:
+ n -= 1
+ continue
+ elif choice not in range(len(link_page)):
+ return None
+ return link_page[choice]['href']
+
+ @staticmethod
+ def get_link_pages(links):
+ """
+ Given a list of links, separate them into pages that can be displayed
+ to the user and navigated using the 1-9 and 0 number keys.
+ """
+ link_pages = []
+ i = 0
+ while i < len(links):
+ link_page = []
+ while i < len(links) and len(link_page) < 10:
+ link_page.append(links[i])
+ i += 1
+ link_pages.append(link_page)
+ return link_pages
+
+ @staticmethod
+ def get_link_page_text(link_page):
+ """
+ Construct the dialog box to display a list of links to the user.
+ """
+ text = ''
+ for i, link in enumerate(link_page):
+ capped_link_text = (link['text'] if len(link['text']) <= 20
+ else link['text'][:19] + '…')
+ text += '[{}] [{}]({})\n'.format(i, capped_link_text, link['href'])
+ return text
+
+ def open_link(self, url):
+ """
+ Open a media link using the definitions from the user's mailcap file.
+
+ Most urls are parsed using their file extension, but special cases
+ exist for websites that are prevalent on reddit such as Imgur and
+ Gfycat. If there are no valid mailcap definitions, TTRV will fall back
+ to using the default webbrowser.
+
+ TTRV checks for certain mailcap fields to determine how to open a link:
+ - If ``copiousoutput`` is specified, the curses application will
+ be paused and stdout will be piped to the system pager.
+ - If `needsterminal`` is specified, the curses application will
+ yield terminal control to the subprocess until it has exited.
+ - Otherwise, we assume that the subprocess is meant to open a new
+ x-window, and we swallow all stdout output.
+
+ Examples:
+ Stream youtube videos with VLC
+ Browse images and imgur albums with feh
+ Watch .webm videos through your terminal with mplayer
+ View images directly in your terminal with fbi or w3m
+ Play .mp3 files with sox player
+ Send HTML pages your pager using to html2text
+ ...anything is possible!
+ """
+
+ if not self.config['enable_media']:
+ self.open_browser(url)
+ return
+
+ try:
+ with self.loader('Checking link', catch_exception=False):
+ command, entry = self.get_mailcap_entry(url)
+ except exceptions.MailcapEntryNotFound:
+ self.open_browser(url)
+ return
+
+ _logger.info('Executing command: %s', command)
+ needs_terminal = 'needsterminal' in entry
+ copious_output = 'copiousoutput' in entry
+
+ if needs_terminal or copious_output:
+ # Blocking, pause ttrv until the process returns
+ with self.suspend():
+ os.system('clear')
+ p = subprocess.Popen(
+ [command], stderr=subprocess.PIPE,
+ universal_newlines=True, shell=True)
+ _, stderr = p.communicate()
+ if copious_output:
+ six.moves.input('Press any key to continue')
+ code = p.poll()
+ if code != 0:
+ _logger.warning(stderr)
+ self.show_notification(
+ 'Program exited with status={0}\n{1}'.format(
+ code, stderr.strip()), style='Error')
+
+ else:
+ # Non-blocking, open a background process
+ with self.loader('Opening page', delay=0):
+ p = subprocess.Popen(
+ [command], shell=True, universal_newlines=True,
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ # Wait a little while to make sure that the command doesn't
+ # exit with an error. This isn't perfect, but it should be good
+ # enough to catch invalid commands.
+ time.sleep(1.0)
+ code = p.poll()
+ if code is not None and code != 0:
+ _, stderr = p.communicate()
+ raise exceptions.BrowserError(
+ 'Program exited with status={0}\n{1}'.format(
+ code, stderr.strip()))
+
+ # Spin off a thread with p.communicate() to avoid subprocess
+ # hang when the stodout/stderr PIPE gets filled up. This
+ # behavior was discovered when opening long gifs with mpv
+ # because mpv sends a progress bar to stderr.
+ # https://thraxil.org/users/anders/posts/2008/03/13/
+ threading.Thread(target=p.communicate).start()
+
+ def get_mailcap_entry(self, url):
+ """
+ Search through the mime handlers list and attempt to find the
+ appropriate command to open the provided url with.
+
+ Will raise a MailcapEntryNotFound exception if no valid command exists.
+
+ Params:
+ url (text): URL that will be checked
+
+ Returns:
+ command (text): The string of the command that should be executed
+ in a subprocess to open the resource.
+ entry (dict): The full mailcap entry for the corresponding command
+ """
+
+ for parser in mime_parsers.parsers:
+ if parser.pattern.match(url):
+ # modified_url may be the same as the original url, but it
+ # could also be updated to point to a different page, or it
+ # could refer to the location of a temporary file with the
+ # page's downloaded content.
+ try:
+ modified_url, content_type = parser.get_mimetype(url)
+ except Exception as e:
+ # If Imgur decides to change its html layout, let it fail
+ # silently in the background instead of crashing.
+ _logger.warning('parser %s raised an exception', parser)
+ _logger.exception(e)
+ raise exceptions.MailcapEntryNotFound()
+ if not content_type:
+ _logger.info('Content type could not be determined')
+ raise exceptions.MailcapEntryNotFound()
+ elif content_type == 'text/html':
+ _logger.info('Content type text/html, deferring to browser')
+ raise exceptions.MailcapEntryNotFound()
+
+ command, entry = mailcap.findmatch(
+ self._mailcap_dict, content_type, filename=modified_url)
+ if not entry:
+ _logger.info('Could not find a valid mailcap entry')
+ raise exceptions.MailcapEntryNotFound()
+
+ return command, entry
+
+ # No parsers matched the url
+ raise exceptions.MailcapEntryNotFound()
+
+ def open_browser(self, url):
+ """
+ Open the given url using the default webbrowser. The preferred browser
+ can specified with the $BROWSER environment variable. If not specified,
+ python webbrowser will try to determine the default to use based on
+ your system.
+
+ For browsers requiring an X display, we open a new subprocess and
+ redirect stdout/stderr to devnull. This is a workaround to stop
+ BackgroundBrowsers (e.g. xdg-open, any BROWSER command ending in "&"),
+ from spewing warning messages to the console. See
+ http://bugs.python.org/issue22277 for a better description of the
+ problem.
+
+ For console browsers (e.g. w3m), TTRV will suspend and display the
+ browser window within the same terminal. This mode is triggered either
+ when
+
+ 1. $BROWSER is set to a known console browser, or
+ 2. $DISPLAY is undefined, indicating that the terminal is running
+ headless
+
+ There may be other cases where console browsers are opened (xdg-open?)
+ but are not detected here. These cases are still unhandled and will
+ probably be broken if we incorrectly assume that self.display=True.
+ """
+
+ if self.display:
+ with self.loader('Opening page in a new window'):
+
+ def open_url_silent(url):
+ # This used to be done using subprocess.Popen().
+ # It was switched to multiprocessing.Process so that we
+ # can re-use the webbrowser instance that has been patched
+ # by TTRV. It's also safer because it doesn't inject
+ # python code through the command line.
+
+ # Suppress stdout/stderr from the browser, see
+ # https://stackoverflow.com/questions/2323080. We can't
+ # depend on replacing sys.stdout & sys.stderr because
+ # webbrowser uses Popen().
+ stdout, stderr = os.dup(1), os.dup(2)
+ null = os.open(os.devnull, os.O_RDWR)
+ try:
+ os.dup2(null, 1)
+ os.dup2(null, 2)
+ if self.config['force_new_browser_window']:
+ webbrowser.open_new(url)
+ else:
+ webbrowser.open_new_tab(url)
+ finally:
+ try:
+ os.close(null)
+ except OSError:
+ pass
+ os.dup2(stdout, 1)
+ os.dup2(stderr, 2)
+
+ p = Process(target=open_url_silent, args=(url,))
+ p.start()
+ # Give the browser 7 seconds to open a new tab. Because the
+ # display is set, calling webbrowser should be non-blocking.
+ # If it blocks or returns an error, something went wrong.
+ try:
+ p.join(7)
+ if p.is_alive():
+ raise exceptions.BrowserError(
+ 'Timeout waiting for browser to open')
+ finally:
+ # This will be hit on the browser timeout, but also if the
+ # user presses the ESC key. We always want to kill the
+ # webbrowser process if it hasn't opened the tab and
+ # terminated by now.
+ try:
+ p.terminate()
+ except OSError:
+ pass
+ else:
+ with self.suspend():
+ if self.config['force_new_browser_window']:
+ webbrowser.open_new(url)
+ else:
+ webbrowser.open_new_tab(url)
+
+ def open_pager(self, data, wrap=None):
+ """
+ View a long block of text using an external pager / viewer. The setting
+ of the TTRV_PAGER variable will be used if set, otherwise the system's
+ default pager is chosen, finally defaulting to 'less' if both TTRV_PAGER
+ and PAGER is unset in the calling environment.
+
+ The data string will be piped directly to the pager.
+ """
+
+ pager = os.getenv('TTRV_PAGER')
+ if pager is None:
+ pager = os.getenv('PAGER') or 'less'
+ command = shlex.split(pager)
+
+ if wrap:
+ data_lines = content.Content.wrap_text(data, wrap)
+ data = '\n'.join(data_lines)
+
+ try:
+ with self.suspend():
+ _logger.debug('Running command: %s', command)
+ p = subprocess.Popen(command, stdin=subprocess.PIPE)
+ try:
+ p.communicate(data.encode('utf-8'))
+ except KeyboardInterrupt:
+ p.terminate()
+ except OSError as e:
+ _logger.exception(e)
+ self.show_notification('Could not open pager %s' % pager)
+
+ @contextmanager
+ def open_editor(self, data=''):
+ """
+ Open a file for editing using the system's default editor.
+
+ After the file has been altered, the text will be read back and the
+ HTML comment tag will be stripped. If an error
+ occurs inside of the context manager, the file will be preserved so
+ users can recover their data. Otherwise, the file will be deleted when
+ the context manager closes.
+
+ Params:
+ data (str): If provided, text will be written to the file before
+ opening it with the editor.
+
+ Returns:
+ text (str): The text that the user entered into the editor.
+ """
+
+ with NamedTemporaryFile(prefix='ttrv_', suffix='.txt', delete=False) as fp:
+ # Create a tempory file and grab the name, but close immediately so
+ # we can re-open using the right encoding
+ filepath = fp.name
+
+ with codecs.open(filepath, 'w', 'utf-8') as fp:
+ fp.write(data)
+ _logger.info('File created: %s', filepath)
+
+ editor = (os.getenv('TTRV_EDITOR') or
+ os.getenv('VISUAL') or
+ os.getenv('EDITOR') or
+ 'nano')
+ command = shlex.split(editor) + [filepath]
+ try:
+ with self.suspend():
+ _logger.debug('Running command: %s', command)
+ p = subprocess.Popen(command)
+ try:
+ p.communicate()
+ except KeyboardInterrupt:
+ p.terminate()
+ except OSError as e:
+ _logger.exception(e)
+ self.show_notification('Could not open file with %s' % editor)
+
+ with codecs.open(filepath, 'r', 'utf-8') as fp:
+ text = fp.read()
+ text = self.strip_instructions(text)
+
+ try:
+ yield text
+ except exceptions.TemporaryFileError:
+ # All exceptions will cause the file to *not* be removed, but these
+ # ones should also be swallowed
+ _logger.info('Caught TemporaryFileError')
+ self.show_notification('Post saved as: %s' % filepath)
+ else:
+ # If no errors occurred, try to remove the file
+ try:
+ os.remove(filepath)
+ except OSError:
+ _logger.warning('Could not delete: %s', filepath)
+ else:
+ _logger.info('File deleted: %s', filepath)
+
+ def open_urlview(self, data):
+ """
+ Pipe a block of text to urlview, which displays a list of urls
+ contained in the text and allows the user to open them with their
+ web browser.
+ """
+
+ urlview = os.getenv('TTRV_URLVIEWER') or 'urlview'
+ command = shlex.split(urlview)
+ try:
+ with self.suspend():
+ _logger.debug('Running command: %s', command)
+ p = subprocess.Popen(command, stdin=subprocess.PIPE)
+ try:
+ p.communicate(input=data.encode('utf-8'))
+ except KeyboardInterrupt:
+ p.terminate()
+
+ code = p.poll()
+ if code == 1:
+ # Clear the "No URLs found." message from stdout
+ sys.stdout.write("\033[F")
+ sys.stdout.flush()
+
+ if code == 1:
+ self.show_notification('No URLs found')
+
+ except OSError as e:
+ _logger.exception(e)
+ self.show_notification(
+ 'Failed to open {0}'.format(urlview))
+
+ def text_input(self, window, allow_resize=False):
+ """
+ Transform a window into a text box that will accept user input and loop
+ until an escape sequence is entered.
+
+ If the escape key (27) is pressed, cancel the textbox and return None.
+ Otherwise, the textbox will wait until it is full (^j, or a new line is
+ entered on the bottom line) or the BEL key (^g) is pressed.
+ """
+
+ window.clear()
+
+ # Set cursor mode to 1 because 2 doesn't display on some terminals
+ self.curs_set(1)
+
+ # Keep insert_mode off to avoid the recursion error described here
+ # http://bugs.python.org/issue13051
+ textbox = textpad.Textbox(window)
+ textbox.stripspaces = 0
+
+ def validate(ch):
+ "Filters characters for special key sequences"
+ if ch == self.ESCAPE:
+ raise exceptions.EscapeInterrupt()
+ if (not allow_resize) and (ch == curses.KEY_RESIZE):
+ raise exceptions.EscapeInterrupt()
+ # Fix backspace for iterm
+ if ch == curses.ascii.DEL:
+ ch = curses.KEY_BACKSPACE
+ return ch
+
+ # Wrapping in an exception block so that we can distinguish when the
+ # user hits the return character from when the user tries to back out
+ # of the input.
+ try:
+ out = textbox.edit(validate=validate)
+ if isinstance(out, six.binary_type):
+ out = out.decode('utf-8')
+ except exceptions.EscapeInterrupt:
+ out = None
+
+ self.curs_set(0)
+ return self.strip_textpad(out)
+
+ def prompt_input(self, prompt, key=False):
+ """
+ Display a text prompt at the bottom of the screen.
+
+ Params:
+ prompt (string): Text prompt that will be displayed
+ key (bool): If true, grab a single keystroke instead of a full
+ string. This can be faster than pressing enter for
+ single key prompts (e.g. y/n?)
+ """
+
+ n_rows, n_cols = self.stdscr.getmaxyx()
+ v_offset, h_offset = self.stdscr.getbegyx()
+ ch, attr = str(' '), self.attr('Prompt')
+ prompt = self.clean(prompt, n_cols - 1)
+
+ # Create a new window to draw the text at the bottom of the screen,
+ # so we can erase it when we're done.
+ s_row = v_offset + n_rows - 1
+ s_col = h_offset
+ prompt_win = curses.newwin(1, len(prompt) + 1, s_row, s_col)
+ prompt_win.bkgd(ch, attr)
+ self.add_line(prompt_win, prompt)
+ prompt_win.refresh()
+
+ # Create a separate window for text input
+ s_col = h_offset + len(prompt)
+ input_win = curses.newwin(1, n_cols - len(prompt), s_row, s_col)
+ input_win.bkgd(ch, attr)
+ input_win.refresh()
+
+ if key:
+ self.curs_set(1)
+ ch = self.getch()
+ # We can't convert the character to unicode, because it may return
+ # Invalid values for keys that don't map to unicode characters,
+ # e.g. F1
+ text = ch if ch != self.ESCAPE else None
+ self.curs_set(0)
+ else:
+ text = self.text_input(input_win)
+
+ prompt_win.clear()
+ input_win.clear()
+ del prompt_win
+ del input_win
+ self.stdscr.touchwin()
+ self.stdscr.refresh()
+
+ return text
+
+ def prompt_y_or_n(self, prompt):
+ """
+ Wrapper around prompt_input for simple yes/no queries.
+ """
+
+ ch = self.prompt_input(prompt, key=True)
+ if ch in (ord('Y'), ord('y')):
+ return True
+ elif ch in (ord('N'), ord('n'), None):
+ return False
+ else:
+ self.flash()
+ return False
+
+ @staticmethod
+ def strip_textpad(text):
+ """
+ Attempt to intelligently strip excess whitespace from the output of a
+ curses textpad.
+ """
+
+ if text is None:
+ return text
+
+ # Trivial case where the textbox is only one line long.
+ if '\n' not in text:
+ return text.rstrip()
+
+ # Allow one space at the end of the line. If there is more than one
+ # space, assume that a newline operation was intended by the user
+ stack, current_line = [], ''
+ for line in text.split('\n'):
+ if line.endswith(' ') or not line:
+ stack.append(current_line + line.rstrip())
+ current_line = ''
+ else:
+ current_line += line
+ stack.append(current_line)
+
+ # Prune empty lines at the bottom of the textbox.
+ for item in stack[::-1]:
+ if not item:
+ stack.pop()
+ else:
+ break
+
+ out = '\n'.join(stack)
+ return out
+
+ @staticmethod
+ def strip_instructions(text):
+ """
+ Remove instructional HTML comment tags inserted by TTRV.
+
+ We used to use # to annotate comments, but it conflicted with the
+ header tag for markdown, which some people use to format their posts.
+ """
+ # Pattern can span multiple lines, allows dot to match newline chars
+ flags = re.MULTILINE | re.DOTALL
+ pattern = ''.format(token=TOKEN)
+ text = re.sub(pattern, '', text, flags=flags)
+ return re.sub(r'\A[\s\n]*\n', '', text, flags=flags).rstrip()
+
+ 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/tildeclub/ttrv/issues/343
+ https://github.com/tildeclub/ttrv/issues/323
+ """
+
+ if self._term != 'xterm-256color':
+ self.stdscr.touchwin()
+ else:
+ self.stdscr.clearok(True)
+
+ def attr(self, element):
+ """
+ Shortcut for fetching the color + attribute code for an element.
+ """
+ # The theme must be initialized before calling this
+ assert self.theme is not None
+
+ return self.theme.get(element)
+
+ @staticmethod
+ def check_theme(theme):
+ """
+ Check if the given theme is compatible with the terminal
+ """
+ terminal_colors = curses.COLORS if curses.has_colors() else 0
+
+ if theme.required_colors > terminal_colors:
+ return False
+ elif theme.required_color_pairs > curses.COLOR_PAIRS:
+ return False
+ else:
+ return True
+
+ def set_theme(self, theme=None):
+ """
+ Check that the terminal supports the provided theme, and applies
+ the theme to the terminal if possible.
+
+ If the terminal doesn't support the theme, this falls back to the
+ default theme. The default theme only requires 8 colors so it
+ should be compatible with any terminal that supports basic colors.
+ """
+
+ terminal_colors = curses.COLORS if curses.has_colors() else 0
+ default_theme = Theme(use_color=bool(terminal_colors))
+
+ if theme is None:
+ theme = default_theme
+
+ elif theme.required_color_pairs > curses.COLOR_PAIRS:
+ _logger.warning(
+ 'Theme `%s` requires %s color pairs, but $TERM=%s only '
+ 'supports %s color pairs, switching to default theme',
+ theme.name, theme.required_color_pairs, self._term,
+ curses.COLOR_PAIRS)
+ theme = default_theme
+
+ elif theme.required_colors > terminal_colors:
+ _logger.warning(
+ 'Theme `%s` requires %s colors, but $TERM=%s only '
+ 'supports %s colors, switching to default theme',
+ theme.name, theme.required_colors, self._term,
+ curses.COLORS)
+ theme = default_theme
+
+ theme.bind_curses()
+ self.theme = theme
+
+ # Apply the default color to the whole screen
+ self.stdscr.bkgd(str(' '), self.attr('Normal'))
diff --git a/ttrv/theme.py b/ttrv/theme.py
new file mode 100644
index 0000000..af662df
--- /dev/null
+++ b/ttrv/theme.py
@@ -0,0 +1,567 @@
+# pylint: disable=bad-whitespace
+
+import os
+import codecs
+import curses
+import logging
+from collections import OrderedDict
+from contextlib import contextmanager
+
+import six
+from six.moves import configparser
+
+from .config import THEMES, DEFAULT_THEMES
+from .exceptions import ConfigError
+
+_logger = logging.getLogger(__name__)
+
+
+class Theme(object):
+
+ ATTRIBUTE_CODES = {
+ '-': None,
+ '': None,
+ 'normal': curses.A_NORMAL,
+ 'bold': curses.A_BOLD,
+ 'reverse': curses.A_REVERSE,
+ 'underline': curses.A_UNDERLINE,
+ 'standout': curses.A_STANDOUT
+ }
+
+ COLOR_CODES = {
+ '-': None,
+ 'default': -1,
+ 'black': curses.COLOR_BLACK,
+ 'red': curses.COLOR_RED,
+ 'green': curses.COLOR_GREEN,
+ 'yellow': curses.COLOR_YELLOW,
+ 'blue': curses.COLOR_BLUE,
+ 'magenta': curses.COLOR_MAGENTA,
+ 'cyan': curses.COLOR_CYAN,
+ 'light_gray': curses.COLOR_WHITE,
+ 'dark_gray': 8,
+ 'bright_red': 9,
+ 'bright_green': 10,
+ 'bright_yellow': 11,
+ 'bright_blue': 12,
+ 'bright_magenta': 13,
+ 'bright_cyan': 14,
+ 'white': 15,
+ }
+
+ for i in range(256):
+ COLOR_CODES['ansi_{0}'.format(i)] = i
+
+ # For compatibility with as many terminals as possible, the default theme
+ # can only use the 8 basic colors with the default color as the background
+ DEFAULT_THEME = {
+ 'modifiers': {
+ 'Normal': (-1, -1, curses.A_NORMAL),
+ 'Selected': (-1, -1, curses.A_NORMAL),
+ 'SelectedCursor': (-1, -1, curses.A_REVERSE),
+ },
+ 'page': {
+ 'TitleBar': (curses.COLOR_CYAN, None, curses.A_BOLD | curses.A_REVERSE),
+ 'OrderBar': (curses.COLOR_YELLOW, None, curses.A_BOLD),
+ 'OrderBarHighlight': (curses.COLOR_YELLOW, None, curses.A_BOLD | curses.A_REVERSE),
+ 'HelpBar': (curses.COLOR_CYAN, None, curses.A_BOLD | curses.A_REVERSE),
+ 'Prompt': (curses.COLOR_CYAN, None, curses.A_BOLD | curses.A_REVERSE),
+ 'NoticeInfo': (None, None, curses.A_BOLD),
+ 'NoticeLoading': (None, None, curses.A_BOLD),
+ 'NoticeError': (None, None, curses.A_BOLD),
+ 'NoticeSuccess': (None, None, curses.A_BOLD),
+ },
+ # Fields that might be highlighted by the "SelectedCursor" element
+ 'cursor': {
+ 'CursorBlock': (None, None, None),
+ 'CursorBar1': (curses.COLOR_MAGENTA, None, None),
+ 'CursorBar2': (curses.COLOR_CYAN, None, None),
+ 'CursorBar3': (curses.COLOR_GREEN, None, None),
+ 'CursorBar4': (curses.COLOR_YELLOW, None, None),
+ },
+ # Fields that might be highlighted by the "Selected" element
+ 'normal': {
+ 'CommentAuthor': (curses.COLOR_BLUE, None, curses.A_BOLD),
+ 'CommentAuthorSelf': (curses.COLOR_GREEN, None, curses.A_BOLD),
+ 'CommentCount': (None, None, None),
+ 'CommentText': (None, None, None),
+ 'Created': (None, None, None),
+ 'Downvote': (curses.COLOR_RED, None, curses.A_BOLD),
+ 'Gold': (curses.COLOR_YELLOW, None, curses.A_BOLD),
+ 'HiddenCommentExpand': (None, None, curses.A_BOLD),
+ 'HiddenCommentText': (None, None, None),
+ 'MultiredditName': (curses.COLOR_YELLOW, None, curses.A_BOLD),
+ 'MultiredditText': (None, None, None),
+ 'NeutralVote': (None, None, curses.A_BOLD),
+ 'NSFW': (curses.COLOR_RED, None, curses.A_BOLD | curses.A_REVERSE),
+ 'Saved': (curses.COLOR_GREEN, None, None),
+ 'Hidden': (curses.COLOR_YELLOW, None, None),
+ 'Score': (None, None, None),
+ 'Separator': (None, None, curses.A_BOLD),
+ 'Stickied': (curses.COLOR_GREEN, None, None),
+ 'SubscriptionName': (curses.COLOR_YELLOW, None, curses.A_BOLD),
+ 'SubscriptionText': (None, None, None),
+ 'SubmissionAuthor': (curses.COLOR_GREEN, None, curses.A_BOLD),
+ 'SubmissionFlair': (curses.COLOR_RED, None, None),
+ 'SubmissionSubreddit': (curses.COLOR_YELLOW, None, None),
+ 'SubmissionText': (None, None, None),
+ 'SubmissionTitle': (None, None, curses.A_BOLD),
+ 'SubmissionTitleSeen': (None, None, None),
+ 'Upvote': (curses.COLOR_GREEN, None, curses.A_BOLD),
+ 'Link': (curses.COLOR_BLUE, None, curses.A_UNDERLINE),
+ 'LinkSeen': (curses.COLOR_MAGENTA, None, curses.A_UNDERLINE),
+ 'UserFlair': (curses.COLOR_YELLOW, None, curses.A_BOLD),
+ 'New': (curses.COLOR_RED, None, curses.A_BOLD),
+ 'Distinguished': (curses.COLOR_RED, None, curses.A_BOLD),
+ 'MessageSubject': (curses.COLOR_BLUE, None, curses.A_BOLD),
+ 'MessageLink': (curses.COLOR_MAGENTA, None, curses.A_BOLD),
+ 'MessageAuthor': (curses.COLOR_GREEN, None, curses.A_BOLD),
+ 'MessageSubreddit': (curses.COLOR_YELLOW, None, None),
+ 'MessageText': (None, None, None),
+
+ }
+ }
+
+ DEFAULT_ELEMENTS = {k: v for group in DEFAULT_THEME.values()
+ for k, v in group.items()}
+
+ # The SubmissionPage uses this to determine which color bar to use
+ CURSOR_BARS = ['CursorBar1', 'CursorBar2', 'CursorBar3', 'CursorBar4']
+
+ def __init__(self, name=None, source=None, elements=None, use_color=True):
+ """
+ Params:
+ name (str): A unique string that describes the theme
+ source (str): A string that describes the source of the theme:
+ built-in - Should only be used when Theme() is called directly
+ preset - Themes packaged with ttrv
+ installed - Themes in ~/.config/ttrv/themes/
+ custom - When a filepath is explicitly provided, e.g.
+ ``ttrv --theme=/path/to/theme_file.cfg``
+ elements (dict): The theme's element map, should be in the same
+ format as Theme.DEFAULT_THEME.
+ """
+
+ if source not in (None, 'built-in', 'preset', 'installed', 'custom'):
+ raise ValueError('Invalid source')
+
+ if name is None and source is None:
+ name = 'default' if use_color else 'monochrome'
+ source = 'built-in'
+ elif name is None or source is None:
+ raise ValueError('Must specify both `name` and `source`, or neither one')
+
+ self.name = name
+ self.source = source
+ self.use_color = use_color
+
+ self._color_pair_map = None
+ self._attribute_map = None
+ self._selected = None
+
+ self.required_color_pairs = 0
+ self.required_colors = 0
+
+ if elements is None:
+ elements = self.DEFAULT_ELEMENTS.copy()
+
+ # Set any elements that weren't defined by the config to fallback to
+ # the default color and attributes
+ for key in self.DEFAULT_ELEMENTS.keys():
+ if key not in elements:
+ elements[key] = (None, None, None)
+
+ self._set_fallback(elements, 'Normal', (-1, -1, curses.A_NORMAL))
+ self._set_fallback(elements, 'Selected', 'Normal')
+ self._set_fallback(elements, 'SelectedCursor', 'Normal')
+
+ # Create the "Selected" versions of elements, which are prefixed with
+ # the @ symbol. For example, "@CommentText" represents how comment
+ # text is formatted when it is highlighted by the cursor.
+ for key in self.DEFAULT_THEME['normal']:
+ dest = '@{0}'.format(key)
+ self._set_fallback(elements, key, 'Selected', dest)
+ for key in self.DEFAULT_THEME['cursor']:
+ dest = '@{0}'.format(key)
+ self._set_fallback(elements, key, 'SelectedCursor', dest)
+
+ # Fill in the ``None`` values for all of the elements with normal text
+ for key in self.DEFAULT_THEME['normal']:
+ self._set_fallback(elements, key, 'Normal')
+ for key in self.DEFAULT_THEME['cursor']:
+ self._set_fallback(elements, key, 'Normal')
+ for key in self.DEFAULT_THEME['page']:
+ self._set_fallback(elements, key, 'Normal')
+
+ self.elements = elements
+
+ if self.use_color:
+ # Pre-calculate how many colors / color pairs the theme will need
+ colors, color_pairs = set(), set()
+ for fg, bg, _ in self.elements.values():
+ colors.add(fg)
+ colors.add(bg)
+ color_pairs.add((fg, bg))
+
+ # Don't count the default (-1, -1) as a color pair because it
+ # doesn't need to be initialized by curses.init_pair().
+ color_pairs.discard((-1, -1))
+ self.required_color_pairs = len(color_pairs)
+
+ # Determine how many colors the terminal needs to support in order
+ # to be able to use the theme. This uses the common breakpoints
+ # that 99% of terminals follow and doesn't take into account
+ # 88 color themes.
+ self.required_colors = None
+ for marker in [0, 8, 16, 256]:
+ if max(colors) < marker:
+ self.required_colors = marker
+ break
+
+ @property
+ def display_string(self):
+ return '{0} ({1})'.format(self.name, self.source)
+
+ def bind_curses(self):
+ """
+ Bind the theme's colors to curses's internal color pair map.
+
+ This method must be called once (after curses has been initialized)
+ before any element attributes can be accessed. Color codes and other
+ special attributes will be mixed bitwise into a single value that
+ can be passed into curses draw functions.
+ """
+ self._color_pair_map = {}
+ self._attribute_map = {}
+
+ for element, item in self.elements.items():
+ fg, bg, attrs = item
+
+ color_pair = (fg, bg)
+ if self.use_color and color_pair != (-1, -1):
+ # Curses limits the number of available color pairs, so we
+ # need to reuse them if there are multiple elements with the
+ # same foreground and background.
+ if color_pair not in self._color_pair_map:
+ # Index 0 is reserved by curses for the default color
+ index = len(self._color_pair_map) + 1
+ curses.init_pair(index, color_pair[0], color_pair[1])
+ self._color_pair_map[color_pair] = curses.color_pair(index)
+ attrs |= self._color_pair_map[color_pair]
+
+ self._attribute_map[element] = attrs
+
+ def get(self, element, selected=False):
+ """
+ Returns the curses attribute code for the given element.
+ """
+ if self._attribute_map is None:
+ raise RuntimeError('Attempted to access theme attribute before '
+ 'calling initialize_curses_theme()')
+
+ if selected or self._selected:
+ element = '@{0}'.format(element)
+
+ return self._attribute_map[element]
+
+ @contextmanager
+ def turn_on_selected(self):
+ """
+ Sets the selected modifier inside of context block.
+
+ For example:
+ >>> with theme.turn_on_selected():
+ >>> attr = theme.get('CursorBlock')
+
+ Is the same as:
+ >>> attr = theme.get('CursorBlock', selected=True)
+
+ Is also the same as:
+ >>> attr = theme.get('@CursorBlock')
+
+ """
+ # This context manager should never be nested
+ assert self._selected is None
+
+ self._selected = True
+ try:
+ yield
+ finally:
+ self._selected = None
+
+ @classmethod
+ def list_themes(cls, path=THEMES):
+ """
+ Compile all of the themes configuration files in the search path.
+ """
+ themes, errors = [], OrderedDict()
+
+ def load_themes(path, source):
+ """
+ Load all themes in the given path.
+ """
+ if os.path.isdir(path):
+ for filename in sorted(os.listdir(path)):
+ if not filename.endswith('.cfg'):
+ continue
+
+ filepath = os.path.join(path, filename)
+ name = filename[:-4]
+ try:
+ # Make sure the theme is valid
+ theme = cls.from_file(filepath, source)
+ except Exception as e:
+ errors[(source, name)] = e
+ else:
+ themes.append(theme)
+
+ themes.extend([Theme(use_color=True), Theme(use_color=False)])
+ load_themes(DEFAULT_THEMES, 'preset')
+ load_themes(path, 'installed')
+
+ return themes, errors
+
+ @classmethod
+ def print_themes(cls, path=THEMES):
+ """
+ Prints a human-readable summary of the installed themes to stdout.
+
+ This is intended to be used as a command-line utility, outside of the
+ main curses display loop.
+ """
+ themes, errors = cls.list_themes(path=path + '/')
+
+ print('\nInstalled ({0}):'.format(path))
+ installed = [t for t in themes if t.source == 'installed']
+ if installed:
+ for theme in installed:
+ line = ' {0:<20}[requires {1} colors]'
+ print(line.format(theme.name, theme.required_colors))
+ else:
+ print(' (empty)')
+
+ print('\nPresets:')
+ preset = [t for t in themes if t.source == 'preset']
+ for theme in preset:
+ line = ' {0:<20}[requires {1} colors]'
+ print(line.format(theme.name, theme.required_colors))
+
+ print('\nBuilt-in:')
+ built_in = [t for t in themes if t.source == 'built-in']
+ for theme in built_in:
+ line = ' {0:<20}[requires {1} colors]'
+ print(line.format(theme.name, theme.required_colors))
+
+ if errors:
+ print('\nWARNING: Some files encountered errors:')
+ for (source, name), error in errors.items():
+ theme_info = '({0}) {1}'.format(source, name)
+ # Align multi-line error messages with the right column
+ err_message = six.text_type(error).replace('\n', '\n' + ' ' * 20)
+ print(' {0:<20}{1}'.format(theme_info, err_message))
+
+ print('')
+
+ @classmethod
+ def from_name(cls, name, path=THEMES):
+ """
+ Search for the given theme on the filesystem and attempt to load it.
+
+ Directories will be checked in a pre-determined order. If the name is
+ provided as an absolute file path, it will be loaded directly.
+ """
+
+ if os.path.isfile(name):
+ return cls.from_file(name, 'custom')
+
+ filename = os.path.join(path, '{0}.cfg'.format(name))
+ if os.path.isfile(filename):
+ return cls.from_file(filename, 'installed')
+
+ filename = os.path.join(DEFAULT_THEMES, '{0}.cfg'.format(name))
+ if os.path.isfile(filename):
+ return cls.from_file(filename, 'preset')
+
+ raise ConfigError('Could not find theme named "{0}"'.format(name))
+
+ @classmethod
+ def from_file(cls, filename, source):
+ """
+ Load a theme from the specified configuration file.
+
+ Parameters:
+ filename: The name of the filename to load.
+ source: A description of where the theme was loaded from.
+ """
+ _logger.info('Loading theme %s', filename)
+
+ try:
+ config = configparser.ConfigParser()
+ config.optionxform = six.text_type # Preserve case
+ with codecs.open(filename, encoding='utf-8') as fp:
+ config.readfp(fp)
+ except configparser.ParsingError as e:
+ raise ConfigError(e.message)
+
+ if not config.has_section('theme'):
+ raise ConfigError(
+ 'Error loading {0}:\n'
+ ' missing [theme] section'.format(filename))
+
+ theme_name = os.path.basename(filename)
+ theme_name, _ = os.path.splitext(theme_name)
+
+ elements = {}
+ for element, line in config.items('theme'):
+ if element not in cls.DEFAULT_ELEMENTS:
+ # Could happen if using a new config with an older version
+ # of the software
+ _logger.info('Skipping element %s', element)
+ continue
+ elements[element] = cls._parse_line(element, line, filename)
+
+ return cls(name=theme_name, source=source, elements=elements)
+
+ @classmethod
+ def _parse_line(cls, element, line, filename=None):
+ """
+ Parse a single line from a theme file.
+
+ Format:
+ :
+ """
+
+ items = line.split()
+ if len(items) == 2:
+ fg, bg, attrs = items[0], items[1], ''
+ elif len(items) == 3:
+ fg, bg, attrs = items
+ else:
+ raise ConfigError(
+ 'Error loading {0}, invalid line:\n'
+ ' {1} = {2}'.format(filename, element, line))
+
+ if fg.startswith('#'):
+ fg = cls.rgb_to_ansi(fg)
+ if bg.startswith('#'):
+ bg = cls.rgb_to_ansi(bg)
+
+ if fg not in cls.COLOR_CODES:
+ raise ConfigError(
+ 'Error loading {0}, invalid :\n'
+ ' {1} = {2}'.format(filename, element, line))
+ fg_code = cls.COLOR_CODES[fg]
+
+ if bg not in cls.COLOR_CODES:
+ raise ConfigError(
+ 'Error loading {0}, invalid :\n'
+ ' {1} = {2}'.format(filename, element, line))
+ bg_code = cls.COLOR_CODES[bg]
+
+ attrs_code = curses.A_NORMAL
+ for attr in attrs.split('+'):
+ if attr not in cls.ATTRIBUTE_CODES:
+ raise ConfigError(
+ 'Error loading {0}, invalid :\n'
+ ' {1} = {2}'.format(filename, element, line))
+ attr_code = cls.ATTRIBUTE_CODES[attr]
+ if attr_code is None:
+ attrs_code = None
+ break
+ else:
+ attrs_code |= attr_code
+
+ return fg_code, bg_code, attrs_code
+
+ @staticmethod
+ def _set_fallback(elements, src_field, fallback, dest_field=None):
+ """
+ Helper function used to set the fallback attributes of an element when
+ they are defined by the configuration as "None" or "-".
+ """
+
+ if dest_field is None:
+ dest_field = src_field
+ if isinstance(fallback, six.string_types):
+ fallback = elements[fallback]
+
+ attrs = elements[src_field]
+ elements[dest_field] = (
+ attrs[0] if attrs[0] is not None else fallback[0],
+ attrs[1] if attrs[1] is not None else fallback[1],
+ attrs[2] if attrs[2] is not None else fallback[2])
+
+ @staticmethod
+ def rgb_to_ansi(color):
+ """
+ Converts hex RGB to the 6x6x6 xterm color space
+
+ Args:
+ color (str): RGB color string in the format "#RRGGBB"
+
+ Returns:
+ str: ansi color string in the format "ansi_n", where n
+ is between 16 and 230
+
+ Reference:
+ https://github.com/chadj2/bash-ui/blob/master/COLORS.md
+ """
+
+ if color[0] != '#' or len(color) != 7:
+ return None
+
+ try:
+ r = round(int(color[1:3], 16) / 51.0) # Normalize between 0-5
+ g = round(int(color[3:5], 16) / 51.0)
+ b = round(int(color[5:7], 16) / 51.0)
+ n = int(36 * r + 6 * g + b + 16)
+ return 'ansi_{0:d}'.format(n)
+ except ValueError:
+ return None
+
+
+class ThemeList(object):
+ """
+ This is a small container around Theme.list_themes() that can be used
+ to cycle through all of the available themes.
+ """
+
+ def __init__(self):
+ self.themes = None
+ self.errors = None
+
+ def reload(self):
+ """
+ This acts as a lazy load, it won't read all of the theme files from
+ disk until the first time somebody tries to access the theme list.
+ """
+ self.themes, self.errors = Theme.list_themes()
+
+ def _step(self, theme, direction):
+ """
+ Traverse the list in the given direction and return the next theme
+ """
+ if not self.themes:
+ self.reload()
+
+ # Try to find the starting index
+ key = (theme.source, theme.name)
+ for i, val in enumerate(self.themes):
+ if (val.source, val.name) == key:
+ index = i
+ break
+ else:
+ # If the theme was set from a custom source it might
+ # not be a part of the list returned by list_themes().
+ self.themes.insert(0, theme)
+ index = 0
+
+ index = (index + direction) % len(self.themes)
+ new_theme = self.themes[index]
+ return new_theme
+
+ def next(self, theme):
+ return self._step(theme, 1)
+
+ def previous(self, theme):
+ return self._step(theme, -1)
diff --git a/ttrv/themes/colorblind-dark.cfg b/ttrv/themes/colorblind-dark.cfg
new file mode 100644
index 0000000..b654455
--- /dev/null
+++ b/ttrv/themes/colorblind-dark.cfg
@@ -0,0 +1,70 @@
+# Black ansi_235
+# White ansi_253
+
+# Sky Blue ansi_81
+# Bluish Green ansi_36
+# Yellow ansi_227
+# Blue ansi_32
+# Vermillion ansi_202
+# Reddish Purple ansi_175
+
+
+[theme]
+; =
+Normal = ansi_253 ansi_235 normal
+Selected = - ansi_236 normal
+SelectedCursor = - - reverse
+
+TitleBar = ansi_81 - bold+reverse
+OrderBar = ansi_227 - bold
+OrderBarHighlight = ansi_227 - bold+reverse
+HelpBar = ansi_81 - bold+reverse
+Prompt = ansi_81 - bold+reverse
+NoticeInfo = - - bold
+NoticeLoading = - - bold
+NoticeError = - - bold
+NoticeSuccess = - - bold
+
+CursorBlock = - - -
+CursorBar1 = ansi_175 - -
+CursorBar2 = ansi_81 - -
+CursorBar3 = ansi_36 - -
+CursorBar4 = ansi_227 - -
+
+CommentAuthor = ansi_32 - bold
+CommentAuthorSelf = ansi_36 - bold
+CommentCount = - - -
+CommentText = - - -
+Created = - - -
+Downvote = ansi_202 - bold
+Gold = ansi_227 - bold
+HiddenCommentExpand = - - bold
+HiddenCommentText = - - -
+MultiredditName = ansi_227 - bold
+MultiredditText = - - -
+NeutralVote = - - bold
+NSFW = ansi_202 - bold+reverse
+Saved = ansi_36 - -
+Hidden = ansi_227 - -
+Score = - - -
+Separator = - - bold
+Stickied = ansi_36 - -
+SubscriptionName = ansi_227 - bold
+SubscriptionText = - - -
+SubmissionAuthor = ansi_36 - bold
+SubmissionFlair = ansi_202 - -
+SubmissionSubreddit = ansi_227 - -
+SubmissionText = - - -
+SubmissionTitle = - - bold
+SubmissionTitleSeen = - - -
+Upvote = ansi_36 - bold
+Link = ansi_32 - underline
+LinkSeen = ansi_175 - underline
+UserFlair = ansi_227 - bold
+New = ansi_227 - bold
+Distinguished = ansi_202 - bold
+MessageSubject = ansi_32 - bold
+MessageLink = ansi_175 - bold
+MessageAuthor = ansi_36 - bold
+MessageSubreddit = ansi_227 - -
+MessageText = - - -
diff --git a/ttrv/themes/default.cfg.example b/ttrv/themes/default.cfg.example
new file mode 100644
index 0000000..1066fea
--- /dev/null
+++ b/ttrv/themes/default.cfg.example
@@ -0,0 +1,59 @@
+[theme]
+; =
+Normal = default default normal
+Selected = default default normal
+SelectedCursor = default default reverse
+
+TitleBar = cyan - bold+reverse
+OrderBar = yellow - bold
+OrderBarHighlight = yellow - bold+reverse
+HelpBar = cyan - bold+reverse
+Prompt = cyan - bold+reverse
+NoticeInfo = - - bold
+NoticeLoading = - - bold
+NoticeError = - - bold
+NoticeSuccess = - - bold
+
+CursorBlock = - - -
+CursorBar1 = magenta - -
+CursorBar2 = cyan - -
+CursorBar3 = green - -
+CursorBar4 = yellow - -
+
+CommentAuthor = blue - bold
+CommentAuthorSelf = green - bold
+CommentCount = - - -
+CommentText = - - -
+Created = - - -
+Downvote = red - bold
+Gold = yellow - bold
+HiddenCommentExpand = - - bold
+HiddenCommentText = - - -
+MultiredditName = yellow - bold
+MultiredditText = - - -
+NeutralVote = - - bold
+NSFW = red - bold+reverse
+Saved = green - -
+Hidden = yellow - -
+Score = - - -
+Separator = - - bold
+Stickied = green - -
+SubscriptionName = yellow - bold
+SubscriptionText = - - -
+SubmissionAuthor = green - bold
+SubmissionFlair = red - -
+SubmissionSubreddit = yellow - -
+SubmissionText = - - -
+SubmissionTitle = - - bold
+SubmissionTitleSeen = - - -
+Upvote = green - bold
+Link = blue - underline
+LinkSeen = magenta - underline
+UserFlair = yellow - bold
+New = red - bold
+Distinguished = red - bold
+MessageSubject = blue - bold
+MessageLink = magenta - bold
+MessageAuthor = green - bold
+MessageSubreddit = yellow - -
+MessageText = - - -
diff --git a/ttrv/themes/molokai.cfg b/ttrv/themes/molokai.cfg
new file mode 100644
index 0000000..f3f908b
--- /dev/null
+++ b/ttrv/themes/molokai.cfg
@@ -0,0 +1,82 @@
+# https://github.com/tomasr/molokai
+
+# normal ansi_252, ansi_234
+# line number ansi_239, ansi_235
+# cursor ansi_252, ansi_236
+# pmenusel ansi_255, ansi_242
+
+# text - normal ansi_252
+# text - dim ansi_244
+# text - ultra dim ansi_241
+
+# purple ansi_141
+# green ansi_154
+# magenta ansi_199, ansi_16
+# gold ansi_222, ansi_233
+# red ansi_197
+# red - dim ansi_203
+# orange ansi_208
+# blue ansi_81
+# blue - dim ansi_67, ansi_16
+
+
+
+[theme]
+; =
+Normal = ansi_252 ansi_234 normal
+Selected = ansi_252 ansi_236 normal
+SelectedCursor = ansi_252 ansi_234 bold+reverse
+
+TitleBar = ansi_81 - bold+reverse
+OrderBar = ansi_244 ansi_235 -
+OrderBarHighlight = ansi_244 ansi_235 bold+reverse
+HelpBar = ansi_81 - bold+reverse
+Prompt = ansi_208 - bold+reverse
+NoticeInfo = - - bold
+NoticeLoading = - - bold
+NoticeError = ansi_199 - bold
+NoticeSuccess = ansi_154 - bold
+
+CursorBlock = ansi_252 - -
+CursorBar1 = ansi_141 - -
+CursorBar2 = ansi_197 - -
+CursorBar3 = ansi_154 - -
+CursorBar4 = ansi_208 - -
+
+CommentAuthor = ansi_81 - -
+CommentAuthorSelf = ansi_154 - -
+CommentCount = - - -
+CommentText = - - -
+Created = - - -
+Downvote = ansi_197 - bold
+Gold = ansi_222 - bold
+HiddenCommentExpand = ansi_244 - bold
+HiddenCommentText = ansi_244 - -
+MultiredditName = - - bold
+MultiredditText = ansi_244 - -
+NeutralVote = - - bold
+NSFW = ansi_197 - bold+reverse
+Saved = ansi_199 - -
+Hidden = ansi_208 - -
+Score = - - bold
+Separator = ansi_241 - bold
+Stickied = ansi_208 - -
+SubscriptionName = - - bold
+SubscriptionText = ansi_244 - -
+SubmissionAuthor = ansi_154 - -
+SubmissionFlair = ansi_197 - -
+SubmissionSubreddit = ansi_222 - -
+SubmissionText = - - -
+SubmissionTitle = - - bold
+SubmissionTitleSeen = - - -
+Upvote = ansi_154 - bold
+Link = ansi_67 - underline
+LinkSeen = ansi_141 - underline
+UserFlair = ansi_222 - bold
+New = ansi_208 - bold
+Distinguished = ansi_197 - bold
+MessageSubject = ansi_81 - bold
+MessageLink = ansi_199 - bold
+MessageAuthor = ansi_154 - bold
+MessageSubreddit = ansi_222 - -
+MessageText = - - -
diff --git a/ttrv/themes/papercolor.cfg b/ttrv/themes/papercolor.cfg
new file mode 100644
index 0000000..51f843d
--- /dev/null
+++ b/ttrv/themes/papercolor.cfg
@@ -0,0 +1,80 @@
+# https://github.com/NLKNguyen/papercolor-theme
+
+# background ansi_255
+# negative ansi_124
+# positive ansi_28
+# olive ansi_64
+# neutral ansi_31
+# comment ansi_102
+# navy ansi_24
+# foreground ansi_238
+# nontext ansi_250
+# red ansi_160
+# pink ansi_162
+# purple ansi_91
+# accent ansi_166
+# orange ansi_166
+# blue ansi_25
+# highlight ansi_24
+# aqua ansi_31
+# green ansi_28
+
+[theme]
+; =
+Normal = ansi_238 ansi_255 normal
+Selected = ansi_238 ansi_254 normal
+SelectedCursor = ansi_238 ansi_255 bold+reverse
+
+TitleBar = ansi_24 - bold+reverse
+OrderBar = ansi_25 - bold
+OrderBarHighlight = ansi_25 - bold+reverse
+HelpBar = ansi_24 - bold+reverse
+Prompt = ansi_31 - bold+reverse
+NoticeInfo = ansi_238 ansi_252 bold
+NoticeLoading = ansi_238 ansi_252 bold
+NoticeError = ansi_124 ansi_225 bold
+NoticeSuccess = ansi_28 ansi_157 bold
+
+CursorBlock = ansi_102 - -
+CursorBar1 = ansi_162 - -
+CursorBar2 = ansi_166 - -
+CursorBar3 = ansi_25 - -
+CursorBar4 = ansi_91 - -
+
+CommentAuthor = ansi_25 - bold
+CommentAuthorSelf = ansi_64 - bold
+CommentCount = - - -
+CommentText = - - -
+Created = - - -
+Downvote = ansi_124 - bold
+Gold = ansi_166 - bold
+HiddenCommentExpand = ansi_102 - bold
+HiddenCommentText = ansi_102 - -
+MultiredditName = - - bold
+MultiredditText = ansi_102 - -
+NeutralVote = - - bold
+NSFW = ansi_160 - bold+reverse
+Saved = ansi_31 - bold
+Hidden = ansi_166 - bold
+Score = - - bold
+Separator = - - bold
+Stickied = ansi_166 - bold
+SubscriptionName = - - bold
+SubscriptionText = ansi_102 - -
+SubmissionAuthor = ansi_64 - bold
+SubmissionFlair = ansi_162 - bold
+SubmissionSubreddit = ansi_166 - bold
+SubmissionText = - - -
+SubmissionTitle = - - bold
+SubmissionTitleSeen = - - -
+Upvote = ansi_28 - bold
+Link = ansi_24 - underline
+LinkSeen = ansi_91 - underline
+UserFlair = ansi_162 - bold
+New = ansi_162 - bold
+Distinguished = ansi_160 - bold
+MessageSubject = ansi_91 - bold
+MessageLink = ansi_162 - bold
+MessageAuthor = ansi_28 - bold
+MessageSubreddit = ansi_166 - bold
+MessageText = - - -
\ No newline at end of file
diff --git a/ttrv/themes/solarized-dark.cfg b/ttrv/themes/solarized-dark.cfg
new file mode 100644
index 0000000..eb360ef
--- /dev/null
+++ b/ttrv/themes/solarized-dark.cfg
@@ -0,0 +1,78 @@
+# http://ethanschoonover.com/solarized
+
+# base3 ansi_230
+# base2 ansi_254
+# base1 ansi_245 (optional emphasized content)
+# base0 ansi_244 (body text / primary content)
+# base00 ansi_241
+# base01 ansi_240 (comments / secondary content)
+# base02 ansi_235 (background highlights)
+# base03 ansi_234 (background)
+# yellow ansi_136
+# orange ansi_166
+# red ansi_160
+# magenta ansi_125
+# violet ansi_61
+# blue ansi_33
+# cyan ansi_37
+# green ansi_64
+
+[theme]
+; =
+Normal = ansi_244 ansi_234 normal
+Selected = ansi_244 ansi_235 normal
+SelectedCursor = ansi_244 ansi_235 bold+reverse
+
+TitleBar = ansi_37 - bold+reverse
+OrderBar = ansi_245 - bold
+OrderBarHighlight = ansi_245 - bold+reverse
+HelpBar = ansi_37 - bold+reverse
+Prompt = ansi_33 - bold+reverse
+NoticeInfo = - - bold
+NoticeLoading = - - bold
+NoticeError = ansi_160 - bold
+NoticeSuccess = ansi_64 - bold
+
+CursorBlock = ansi_240 - -
+CursorBar1 = ansi_125 - -
+CursorBar2 = ansi_160 - -
+CursorBar3 = ansi_61 - -
+CursorBar4 = ansi_37 - -
+
+CommentAuthor = ansi_33 - bold
+CommentAuthorSelf = ansi_64 - bold
+CommentCount = - - -
+CommentText = - - -
+Created = - - -
+Downvote = ansi_160 - bold
+Gold = ansi_136 - bold
+HiddenCommentExpand = ansi_240 - bold
+HiddenCommentText = ansi_240 - -
+MultiredditName = ansi_245 - bold
+MultiredditText = ansi_240 - -
+NeutralVote = - - bold
+NSFW = ansi_160 - bold+reverse
+Saved = ansi_125 - -
+Hidden = ansi_136 - -
+Score = - - -
+Separator = - - bold
+Stickied = ansi_136 - -
+SubscriptionName = ansi_245 - bold
+SubscriptionText = ansi_240 - -
+SubmissionAuthor = ansi_64 - bold
+SubmissionFlair = ansi_160 - -
+SubmissionSubreddit = ansi_166 - -
+SubmissionText = - - -
+SubmissionTitle = ansi_245 - bold
+SubmissionTitleSeen = - - -
+Upvote = ansi_64 - bold
+Link = ansi_33 - underline
+LinkSeen = ansi_61 - underline
+UserFlair = ansi_136 - bold
+New = ansi_136 - bold
+Distinguished = ansi_160 - bold
+MessageSubject = ansi_37 - bold
+MessageLink = ansi_125 - bold
+MessageAuthor = ansi_64 - bold
+MessageSubreddit = ansi_136 - -
+MessageText = - - -
\ No newline at end of file
diff --git a/ttrv/themes/solarized-light.cfg b/ttrv/themes/solarized-light.cfg
new file mode 100644
index 0000000..4b9cf4c
--- /dev/null
+++ b/ttrv/themes/solarized-light.cfg
@@ -0,0 +1,78 @@
+# http://ethanschoonover.com/solarized
+
+# base03 ansi_234
+# base02 ansi_235
+# base01 ansi_240 (optional emphasized content)
+# base00 ansi_241 (body text / primary content)
+# base0 ansi_244
+# base1 ansi_245 (comments / secondary content)
+# base2 ansi_254 (background highlights)
+# base3 ansi_230 (background)
+# yellow ansi_136
+# orange ansi_166
+# red ansi_160
+# magenta ansi_125
+# violet ansi_61
+# blue ansi_33
+# cyan ansi_37
+# green ansi_64
+
+[theme]
+; =
+Normal = ansi_241 ansi_230 normal
+Selected = ansi_241 ansi_254 normal
+SelectedCursor = ansi_241 ansi_254 bold+reverse
+
+TitleBar = ansi_37 - bold+reverse
+OrderBar = ansi_245 - bold
+OrderBarHighlight = ansi_245 - bold+reverse
+HelpBar = ansi_37 - bold+reverse
+Prompt = ansi_33 - bold+reverse
+NoticeInfo = - - bold
+NoticeLoading = - - bold
+NoticeError = ansi_160 - bold
+NoticeSuccess = ansi_64 - bold
+
+CursorBlock = ansi_245 - -
+CursorBar1 = ansi_125 - -
+CursorBar2 = ansi_160 - -
+CursorBar3 = ansi_61 - -
+CursorBar4 = ansi_37 - -
+
+CommentAuthor = ansi_33 - bold
+CommentAuthorSelf = ansi_64 - bold
+CommentCount = - - -
+CommentText = - - -
+Created = - - -
+Downvote = ansi_160 - bold
+Gold = ansi_136 - bold
+HiddenCommentExpand = ansi_245 - bold
+HiddenCommentText = ansi_245 - -
+MultiredditName = ansi_240 - bold
+MultiredditText = ansi_245 - -
+NeutralVote = - - bold
+NSFW = ansi_160 - bold+reverse
+Saved = ansi_125 - bold
+Hidden = ansi_136 - bold
+Score = - - -
+Separator = - - bold
+Stickied = ansi_136 - bold
+SubscriptionName = ansi_240 - bold
+SubscriptionText = ansi_245 - -
+SubmissionAuthor = ansi_64 - bold
+SubmissionFlair = ansi_160 - bold
+SubmissionSubreddit = ansi_166 - bold
+SubmissionText = - - -
+SubmissionTitle = ansi_240 - bold
+SubmissionTitleSeen = - - -
+Upvote = ansi_64 - bold
+Link = ansi_33 - underline
+LinkSeen = ansi_61 - underline
+UserFlair = ansi_136 - bold
+New = ansi_227 - bold
+Distinguished = ansi_202 - bold
+MessageSubject = ansi_32 - bold
+MessageLink = ansi_175 - bold
+MessageAuthor = ansi_36 - bold
+MessageSubreddit = ansi_166 - -
+MessageText = - - -
diff --git a/version.py b/version.py
new file mode 120000
index 0000000..5bf7339
--- /dev/null
+++ b/version.py
@@ -0,0 +1 @@
+ttrv/__version__.py
\ No newline at end of file