gavin19

max_emojis

Nov 18th, 2019
146
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 101.70 KB | None | 0 0
  1. """Provide the Subreddit class."""
  2. # pylint: disable=too-many-lines
  3. from copy import deepcopy
  4. from json import dumps, loads
  5. from os.path import basename, dirname, join
  6. from urllib.parse import urljoin
  7.  
  8. from prawcore import Redirect
  9. import websocket
  10.  
  11. from ...const import API_PATH, JPEG_HEADER
  12. from ...exceptions import APIException, ClientException
  13. from ...util.cache import cachedproperty
  14. from ..util import permissions_string, stream_generator
  15. from ..listing.generator import ListingGenerator
  16. from ..listing.mixins import SubredditListingMixin
  17. from .base import RedditBase
  18. from .emoji import SubredditEmoji
  19. from .mixins import FullnameMixin, MessageableMixin
  20. from .modmail import ModmailConversation
  21. from .widgets import SubredditWidgets, WidgetEncoder
  22. from .wikipage import WikiPage
  23.  
  24.  
  25. class Subreddit(
  26.     MessageableMixin, SubredditListingMixin, FullnameMixin, RedditBase
  27. ):
  28.     """A class for Subreddits.
  29.  
  30.    To obtain an instance of this class for subreddit ``/r/redditdev`` execute:
  31.  
  32.    .. code:: python
  33.  
  34.       subreddit = reddit.subreddit('redditdev')
  35.  
  36.    While ``/r/all`` is not a real subreddit, it can still be treated like
  37.    one. The following outputs the titles of the 25 hottest submissions in
  38.    ``/r/all``:
  39.  
  40.    .. code:: python
  41.  
  42.       for submission in reddit.subreddit('all').hot(limit=25):
  43.           print(submission.title)
  44.  
  45.    Multiple subreddits can be combined like so:
  46.  
  47.    .. code:: python
  48.  
  49.       for submission in reddit.subreddit('redditdev+learnpython').top('all'):
  50.           print(submission)
  51.  
  52.    Subreddits can be filtered from combined listings as follows. Note that
  53.    these filters are ignored by certain methods, including
  54.    :attr:`~praw.models.Subreddit.comments`,
  55.    :meth:`~praw.models.Subreddit.gilded`, and
  56.    :meth:`.SubredditStream.comments`.
  57.  
  58.    .. code:: python
  59.  
  60.       for submission in reddit.subreddit('all-redditdev').new():
  61.           print(submission)
  62.  
  63.    **Typical Attributes**
  64.  
  65.    This table describes attributes that typically belong to objects of this
  66.    class. Since attributes are dynamically provided (see
  67.    :ref:`determine-available-attributes-of-an-object`), there is not a
  68.    guarantee that these attributes will always be present, nor is this list
  69.    comprehensive in any way.
  70.  
  71.    ========================== ===============================================
  72.    Attribute                  Description
  73.    ========================== ===============================================
  74.    ``can_assign_link_flair``  Whether users can assign their own link flair.
  75.    ``can_assign_user_flair``  Whether users can assign their own user flair.
  76.    ``created_utc``            Time the subreddit was created, represented in
  77.                               `Unix Time`_.
  78.    ``description``            Subreddit description, in Markdown.
  79.    ``description_html``       Subreddit description, in HTML.
  80.    ``display_name``           Name of the subreddit.
  81.    ``id``                     ID of the subreddit.
  82.    ``name``                   Fullname of the subreddit.
  83.    ``over18``                 Whether the subreddit is NSFW.
  84.    ``public_description``     Description of the subreddit, shown in searches
  85.                               and on the "You must be invited to visit this
  86.                               community" page (if applicable).
  87.    ``spoilers_enabled``       Whether the spoiler tag feature is enabled.
  88.    ``subscribers``            Count of subscribers.
  89.    ``user_is_banned``         Whether the authenticated user is banned.
  90.    ``user_is_moderator``      Whether the authenticated user is a moderator.
  91.    ``user_is_subscriber``     Whether the authenticated user is subscribed.
  92.    ========================== ===============================================
  93.  
  94.  
  95.    .. _Unix Time: https://en.wikipedia.org/wiki/Unix_time
  96.  
  97.    """
  98.  
  99.     # pylint: disable=too-many-public-methods
  100.  
  101.     STR_FIELD = "display_name"
  102.     MESSAGE_PREFIX = "#"
  103.  
  104.     @staticmethod
  105.     def _create_or_update(
  106.         _reddit,
  107.         allow_images=None,
  108.         allow_post_crossposts=None,
  109.         allow_top=None,
  110.         collapse_deleted_comments=None,
  111.         comment_score_hide_mins=None,
  112.         description=None,
  113.         domain=None,
  114.         exclude_banned_modqueue=None,
  115.         header_hover_text=None,
  116.         hide_ads=None,
  117.         lang=None,
  118.         key_color=None,
  119.         link_type=None,
  120.         name=None,
  121.         over_18=None,
  122.         public_description=None,
  123.         public_traffic=None,
  124.         show_media=None,
  125.         show_media_preview=None,
  126.         spam_comments=None,
  127.         spam_links=None,
  128.         spam_selfposts=None,
  129.         spoilers_enabled=None,
  130.         sr=None,
  131.         submit_link_label=None,
  132.         submit_text=None,
  133.         submit_text_label=None,
  134.         subreddit_type=None,
  135.         suggested_comment_sort=None,
  136.         title=None,
  137.         wiki_edit_age=None,
  138.         wiki_edit_karma=None,
  139.         wikimode=None,
  140.         **other_settings
  141.     ):
  142.         # pylint: disable=invalid-name,too-many-locals,too-many-arguments
  143.         model = {
  144.             "allow_images": allow_images,
  145.             "allow_post_crossposts": allow_post_crossposts,
  146.             "allow_top": allow_top,
  147.             "collapse_deleted_comments": collapse_deleted_comments,
  148.             "comment_score_hide_mins": comment_score_hide_mins,
  149.             "description": description,
  150.             "domain": domain,
  151.             "exclude_banned_modqueue": exclude_banned_modqueue,
  152.             "header-title": header_hover_text,  # Remap here - better name
  153.             "hide_ads": hide_ads,
  154.             "key_color": key_color,
  155.             "lang": lang,
  156.             "link_type": link_type,
  157.             "name": name,
  158.             "over_18": over_18,
  159.             "public_description": public_description,
  160.             "public_traffic": public_traffic,
  161.             "show_media": show_media,
  162.             "show_media_preview": show_media_preview,
  163.             "spam_comments": spam_comments,
  164.             "spam_links": spam_links,
  165.             "spam_selfposts": spam_selfposts,
  166.             "spoilers_enabled": spoilers_enabled,
  167.             "sr": sr,
  168.             "submit_link_label": submit_link_label,
  169.             "submit_text": submit_text,
  170.             "submit_text_label": submit_text_label,
  171.             "suggested_comment_sort": suggested_comment_sort,
  172.             "title": title,
  173.             "type": subreddit_type,
  174.             "wiki_edit_age": wiki_edit_age,
  175.             "wiki_edit_karma": wiki_edit_karma,
  176.             "wikimode": wikimode,
  177.         }
  178.  
  179.         model.update(other_settings)
  180.  
  181.         _reddit.post(API_PATH["site_admin"], data=model)
  182.  
  183.     @staticmethod
  184.     def _subreddit_list(subreddit, other_subreddits):
  185.         if other_subreddits:
  186.             return ",".join(
  187.                 [str(subreddit)] + [str(x) for x in other_subreddits]
  188.             )
  189.         return str(subreddit)
  190.  
  191.     @property
  192.     def _kind(self):
  193.         """Return the class's kind."""
  194.         return self._reddit.config.kinds["subreddit"]
  195.  
  196.     @cachedproperty
  197.     def banned(self):
  198.         """Provide an instance of :class:`.SubredditRelationship`.
  199.  
  200.        For example to ban a user try:
  201.  
  202.        .. code-block:: python
  203.  
  204.           reddit.subreddit('SUBREDDIT').banned.add('NAME', ban_reason='...')
  205.  
  206.        To list the banned users along with any notes, try:
  207.  
  208.        .. code-block:: python
  209.  
  210.           for ban in reddit.subreddit('SUBREDDIT').banned():
  211.               print('{}: {}'.format(ban, ban.note))
  212.  
  213.        """
  214.         return SubredditRelationship(self, "banned")
  215.  
  216.     @cachedproperty
  217.     def collections(self):
  218.         r"""Provide an instance of :class:`.SubredditCollections`.
  219.  
  220.        To see the permalinks of all :class:`.Collection`\ s that belong to
  221.        a subreddit, try:
  222.  
  223.        .. code-block:: python
  224.  
  225.           for collection in reddit.subreddit('SUBREDDIT').collections:
  226.               print(collection.permalink)
  227.  
  228.        To get a specific :class:`.Collection` by its UUID or permalink,
  229.        use one of the following:
  230.  
  231.        .. code-block:: python
  232.  
  233.           collection = reddit.subreddit('SUBREDDIT').collections('some_uuid')
  234.           collection = reddit.subreddit('SUBREDDIT').collections(
  235.               permalink='https://reddit.com/r/SUBREDDIT/collection/some_uuid')
  236.  
  237.        """
  238.         return self._subreddit_collections_class(self._reddit, self)
  239.  
  240.     @cachedproperty
  241.     def contributor(self):
  242.         """Provide an instance of :class:`.ContributorRelationship`.
  243.  
  244.        Contributors are also known as approved submitters.
  245.  
  246.        To add a contributor try:
  247.  
  248.        .. code-block:: python
  249.  
  250.           reddit.subreddit('SUBREDDIT').contributor.add('NAME')
  251.  
  252.        """
  253.         return ContributorRelationship(self, "contributor")
  254.  
  255.     @cachedproperty
  256.     def emoji(self):
  257.         """Provide an instance of :class:`.SubredditEmoji`.
  258.  
  259.        This attribute can be used to discover all emoji for a subreddit:
  260.  
  261.        .. code:: python
  262.  
  263.           for emoji in reddit.subreddit('iama').emoji:
  264.               print(emoji)
  265.  
  266.        A single emoji can be lazily retrieved via:
  267.  
  268.        .. code:: python
  269.  
  270.           reddit.subreddit('blah').emoji['emoji_name']
  271.  
  272.        .. note:: Attempting to access attributes of an nonexistent emoji will
  273.           result in a :class:`.ClientException`.
  274.  
  275.        """
  276.         return SubredditEmoji(self)
  277.  
  278.     @cachedproperty
  279.     def filters(self):
  280.         """Provide an instance of :class:`.SubredditFilters`."""
  281.         return SubredditFilters(self)
  282.  
  283.     @cachedproperty
  284.     def flair(self):
  285.         """Provide an instance of :class:`.SubredditFlair`.
  286.  
  287.        Use this attribute for interacting with a subreddit's flair. For
  288.        example to list all the flair for a subreddit which you have the
  289.        ``flair`` moderator permission on try:
  290.  
  291.        .. code-block:: python
  292.  
  293.           for flair in reddit.subreddit('NAME').flair():
  294.               print(flair)
  295.  
  296.        Flair templates can be interacted with through this attribute via:
  297.  
  298.        .. code-block:: python
  299.  
  300.           for template in reddit.subreddit('NAME').flair.templates:
  301.               print(template)
  302.  
  303.        """
  304.         return SubredditFlair(self)
  305.  
  306.     @cachedproperty
  307.     def mod(self):
  308.         """Provide an instance of :class:`.SubredditModeration`."""
  309.         return SubredditModeration(self)
  310.  
  311.     @cachedproperty
  312.     def moderator(self):
  313.         """Provide an instance of :class:`.ModeratorRelationship`.
  314.  
  315.        For example to add a moderator try:
  316.  
  317.        .. code-block:: python
  318.  
  319.           reddit.subreddit('SUBREDDIT').moderator.add('NAME')
  320.  
  321.        To list the moderators along with their permissions try:
  322.  
  323.        .. code-block:: python
  324.  
  325.           for moderator in reddit.subreddit('SUBREDDIT').moderator():
  326.               print('{}: {}'.format(moderator, moderator.mod_permissions))
  327.  
  328.        """
  329.         return ModeratorRelationship(self, "moderator")
  330.  
  331.     @cachedproperty
  332.     def modmail(self):
  333.         """Provide an instance of :class:`.Modmail`."""
  334.         return Modmail(self)
  335.  
  336.     @cachedproperty
  337.     def muted(self):
  338.         """Provide an instance of :class:`.SubredditRelationship`."""
  339.         return SubredditRelationship(self, "muted")
  340.  
  341.     @cachedproperty
  342.     def quaran(self):
  343.         """Provide an instance of :class:`.SubredditQuarantine`.
  344.  
  345.        This property is named ``quaran`` because ``quarantine`` is a
  346.        Subreddit attribute returned by Reddit to indicate whether or not a
  347.        Subreddit is quarantined.
  348.  
  349.        """
  350.         return SubredditQuarantine(self)
  351.  
  352.     @cachedproperty
  353.     def stream(self):
  354.         """Provide an instance of :class:`.SubredditStream`.
  355.  
  356.        Streams can be used to indefinitely retrieve new comments made to a
  357.        subreddit, like:
  358.  
  359.        .. code:: python
  360.  
  361.           for comment in reddit.subreddit('iama').stream.comments():
  362.               print(comment)
  363.  
  364.        Additionally, new submissions can be retrieved via the stream. In the
  365.        following example all submissions are fetched via the special subreddit
  366.        ``all``:
  367.  
  368.        .. code:: python
  369.  
  370.           for submission in reddit.subreddit('all').stream.submissions():
  371.               print(submission)
  372.  
  373.        """
  374.         return SubredditStream(self)
  375.  
  376.     @cachedproperty
  377.     def stylesheet(self):
  378.         """Provide an instance of :class:`.SubredditStylesheet`."""
  379.         return SubredditStylesheet(self)
  380.  
  381.     @cachedproperty
  382.     def widgets(self):
  383.         """Provide an instance of :class:`.SubredditWidgets`.
  384.  
  385.        **Example usage**
  386.  
  387.        Get all sidebar widgets:
  388.  
  389.        .. code-block:: python
  390.  
  391.           for widget in reddit.subreddit('redditdev').widgets.sidebar:
  392.               print(widget)
  393.  
  394.        Get ID card widget:
  395.  
  396.        .. code-block:: python
  397.  
  398.           print(reddit.subreddit('redditdev').widgets.id_card)
  399.  
  400.        """
  401.         return SubredditWidgets(self)
  402.  
  403.     @cachedproperty
  404.     def wiki(self):
  405.         """Provide an instance of :class:`.SubredditWiki`.
  406.  
  407.        This attribute can be used to discover all wikipages for a subreddit:
  408.  
  409.        .. code:: python
  410.  
  411.           for wikipage in reddit.subreddit('iama').wiki:
  412.               print(wikipage)
  413.  
  414.        To fetch the content for a given wikipage try:
  415.  
  416.        .. code:: python
  417.  
  418.           wikipage = reddit.subreddit('iama').wiki['proof']
  419.           print(wikipage.content_md)
  420.  
  421.        """
  422.         return SubredditWiki(self)
  423.  
  424.     def __init__(self, reddit, display_name=None, _data=None):
  425.         """Initialize a Subreddit instance.
  426.  
  427.        :param reddit: An instance of :class:`~.Reddit`.
  428.        :param display_name: The name of the subreddit.
  429.  
  430.        .. note:: This class should not be initialized directly. Instead obtain
  431.           an instance via: ``reddit.subreddit('subreddit_name')``
  432.  
  433.        """
  434.         if bool(display_name) == bool(_data):
  435.             raise TypeError(
  436.                 "Either `display_name` or `_data` must be provided."
  437.             )
  438.         super(Subreddit, self).__init__(reddit, _data=_data)
  439.         if display_name:
  440.             self.display_name = display_name
  441.         self._path = API_PATH["subreddit"].format(subreddit=self)
  442.  
  443.     def _fetch_info(self):
  444.         return ("subreddit_about", {"subreddit": self}, None)
  445.  
  446.     def _fetch_data(self):
  447.         name, fields, params = self._fetch_info()
  448.         path = API_PATH[name].format(**fields)
  449.         return self._reddit.request("GET", path, params)
  450.  
  451.     def _fetch(self):
  452.         data = self._fetch_data()
  453.         data = data["data"]
  454.         other = type(self)(self._reddit, _data=data)
  455.         self.__dict__.update(other.__dict__)
  456.         self._fetched = True
  457.  
  458.     def _submit_media(self, data, timeout, without_websockets):
  459.         """Submit and return an `image`, `video`, or `videogif`.
  460.  
  461.        This is a helper method for submitting posts that are not link posts or
  462.        self posts.
  463.        """
  464.         response = self._reddit.post(API_PATH["submit"], data=data)
  465.  
  466.         # About the websockets:
  467.         #
  468.         # Reddit responds to this request with only two fields: a link to
  469.         # the user's /submitted page, and a websocket URL. We can use the
  470.         # websocket URL to get a link to the new post once it is created.
  471.         #
  472.         # An important note to PRAW contributors or anyone who would
  473.         # wish to step through this section with a debugger: This block
  474.         # of code is NOT debugger-friendly. If there is *any*
  475.         # significant time between the POST request just above this
  476.         # comment and the creation of the websocket connection just
  477.         # below, the code will become stuck in an infinite loop at the
  478.         # socket.recv() call. I believe this is because only one message is
  479.         # sent over the websocket, and if the client doesn't connect
  480.         # soon enough, it will miss the message and get stuck forever
  481.         # waiting for another.
  482.         #
  483.         # So if you need to debug this section of code, please let the
  484.         # websocket creation happen right after the POST request,
  485.         # otherwise you will have trouble.
  486.  
  487.         if not isinstance(response, dict):
  488.             raise ClientException(
  489.                 "Something went wrong with your post: {!r}".format(response)
  490.             )
  491.  
  492.         if without_websockets:
  493.             return
  494.  
  495.         try:
  496.             socket = websocket.create_connection(
  497.                 response["json"]["data"]["websocket_url"], timeout=timeout
  498.             )
  499.             ws_update = loads(socket.recv())
  500.             socket.close()
  501.         except websocket.WebSocketTimeoutException:
  502.             raise ClientException(
  503.                 "Websocket error. Check your media file. "
  504.                 "Your post may still have been created."
  505.             )
  506.         url = ws_update["payload"]["redirect"]
  507.         return self._reddit.submission(url=url)
  508.  
  509.     def _upload_media(self, media_path):
  510.         """Upload media and return its URL. Uses undocumented endpoint."""
  511.         if media_path is None:
  512.             media_path = join(
  513.                 dirname(dirname(dirname(__file__))), "images", "PRAW logo.png"
  514.             )
  515.  
  516.         file_name = basename(media_path).lower()
  517.         mime_type = {
  518.             "png": "image/png",
  519.             "mov": "video/quicktime",
  520.             "mp4": "video/mp4",
  521.             "jpg": "image/jpeg",
  522.             "jpeg": "image/jpeg",
  523.             "gif": "image/gif",
  524.         }.get(
  525.             file_name.rpartition(".")[2], "image/jpeg"
  526.         )  # default to JPEG
  527.         img_data = {"filepath": file_name, "mimetype": mime_type}
  528.  
  529.         url = API_PATH["media_asset"]
  530.         # until we learn otherwise, assume this request always succeeds
  531.         upload_lease = self._reddit.post(url, data=img_data)["args"]
  532.         upload_url = "https:{}".format(upload_lease["action"])
  533.         upload_data = {
  534.             item["name"]: item["value"] for item in upload_lease["fields"]
  535.         }
  536.  
  537.         with open(media_path, "rb") as media:
  538.             response = self._reddit._core._requestor._http.post(
  539.                 upload_url, data=upload_data, files={"file": media}
  540.             )
  541.         response.raise_for_status()
  542.  
  543.         return upload_url + "/" + upload_data["key"]
  544.  
  545.     def random(self):
  546.         """Return a random Submission.
  547.  
  548.        Returns ``None`` on subreddits that do not support the random feature.
  549.        One example, at the time of writing, is /r/wallpapers.
  550.        """
  551.         url = API_PATH["subreddit_random"].format(subreddit=self)
  552.         try:
  553.             self._reddit.get(url, params={"unique": self._reddit._next_unique})
  554.         except Redirect as redirect:
  555.             path = redirect.path
  556.         try:
  557.             return self._submission_class(
  558.                 self._reddit, url=urljoin(self._reddit.config.reddit_url, path)
  559.             )
  560.         except ClientException:
  561.             return None
  562.  
  563.     def rules(self):
  564.         """Return rules for the subreddit.
  565.  
  566.        For example to show the rules of ``/r/redditdev`` try:
  567.  
  568.        .. code:: python
  569.  
  570.           reddit.subreddit('redditdev').rules()
  571.  
  572.        """
  573.         return self._reddit.get(API_PATH["rules"].format(subreddit=self))
  574.  
  575.     def search(
  576.         self,
  577.         query,
  578.         sort="relevance",
  579.         syntax="lucene",
  580.         time_filter="all",
  581.         **generator_kwargs
  582.     ):
  583.         """Return a ListingGenerator for items that match ``query``.
  584.  
  585.        :param query: The query string to search for.
  586.        :param sort: Can be one of: relevance, hot, top, new,
  587.            comments. (default: relevance).
  588.        :param syntax: Can be one of: cloudsearch, lucene, plain
  589.            (default: lucene).
  590.        :param time_filter: Can be one of: all, day, hour, month, week, year
  591.            (default: all).
  592.  
  593.        For more information on building a search query see:
  594.            https://www.reddit.com/wiki/search
  595.  
  596.        For example to search all subreddits for ``praw`` try:
  597.  
  598.        .. code:: python
  599.  
  600.           for submission in reddit.subreddit('all').search('praw'):
  601.               print(submission.title)
  602.  
  603.        """
  604.         self._validate_time_filter(time_filter)
  605.         not_all = self.display_name.lower() != "all"
  606.         self._safely_add_arguments(
  607.             generator_kwargs,
  608.             "params",
  609.             q=query,
  610.             restrict_sr=not_all,
  611.             sort=sort,
  612.             syntax=syntax,
  613.             t=time_filter,
  614.         )
  615.         url = API_PATH["search"].format(subreddit=self)
  616.         return ListingGenerator(self._reddit, url, **generator_kwargs)
  617.  
  618.     def sticky(self, number=1):
  619.         """Return a Submission object for a sticky of the subreddit.
  620.  
  621.        :param number: Specify which sticky to return. 1 appears at the top
  622.            (default: 1).
  623.  
  624.        Raises ``prawcore.NotFound`` if the sticky does not exist.
  625.  
  626.        """
  627.         url = API_PATH["about_sticky"].format(subreddit=self)
  628.         try:
  629.             self._reddit.get(url, params={"num": number})
  630.         except Redirect as redirect:
  631.             path = redirect.path
  632.         return self._submission_class(
  633.             self._reddit, url=urljoin(self._reddit.config.reddit_url, path)
  634.         )
  635.  
  636.     def submit(
  637.         self,
  638.         title,
  639.         selftext=None,
  640.         url=None,
  641.         flair_id=None,
  642.         flair_text=None,
  643.         resubmit=True,
  644.         send_replies=True,
  645.         nsfw=False,
  646.         spoiler=False,
  647.         collection_id=None,
  648.     ):
  649.         """Add a submission to the subreddit.
  650.  
  651.        :param title: The title of the submission.
  652.        :param selftext: The markdown formatted content for a ``text``
  653.            submission. Use an empty string, ``''``, to make a title-only
  654.            submission.
  655.        :param url: The URL for a ``link`` submission.
  656.        :param collection_id: The UUID of a :class:`.Collection` to add the
  657.            newly-submitted post to.
  658.        :param flair_id: The flair template to select (default: None).
  659.        :param flair_text: If the template's ``flair_text_editable`` value is
  660.            True, this value will set a custom text (default: None).
  661.        :param resubmit: When False, an error will occur if the URL has already
  662.            been submitted (default: True).
  663.        :param send_replies: When True, messages will be sent to the submission
  664.            author when comments are made to the submission (default: True).
  665.        :param nsfw: Whether or not the submission should be marked NSFW
  666.            (default: False).
  667.        :param spoiler: Whether or not the submission should be marked as
  668.            a spoiler (default: False).
  669.        :returns: A :class:`~.Submission` object for the newly created
  670.            submission.
  671.  
  672.        Either ``selftext`` or ``url`` can be provided, but not both.
  673.  
  674.        For example to submit a URL to ``/r/reddit_api_test`` do:
  675.  
  676.        .. code:: python
  677.  
  678.           title = 'PRAW documentation'
  679.           url = 'https://praw.readthedocs.io'
  680.           reddit.subreddit('reddit_api_test').submit(title, url=url)
  681.  
  682.        .. note ::
  683.  
  684.           For submitting images, videos, and videogifs,
  685.           see :meth:`.submit_image` and :meth:`.submit_video`.
  686.  
  687.        """
  688.         if (bool(selftext) or selftext == "") == bool(url):
  689.             raise TypeError("Either `selftext` or `url` must be provided.")
  690.  
  691.         data = {
  692.             "sr": str(self),
  693.             "resubmit": bool(resubmit),
  694.             "sendreplies": bool(send_replies),
  695.             "title": title,
  696.             "nsfw": bool(nsfw),
  697.             "spoiler": bool(spoiler),
  698.         }
  699.         for key, value in (
  700.             ("flair_id", flair_id),
  701.             ("flair_text", flair_text),
  702.             ("collection_id", collection_id),
  703.         ):
  704.             if value is not None:
  705.                 data[key] = value
  706.         if selftext is not None:
  707.             data.update(kind="self", text=selftext)
  708.         else:
  709.             data.update(kind="link", url=url)
  710.  
  711.         return self._reddit.post(API_PATH["submit"], data=data)
  712.  
  713.     def submit_image(
  714.         self,
  715.         title,
  716.         image_path,
  717.         flair_id=None,
  718.         flair_text=None,
  719.         resubmit=True,
  720.         send_replies=True,
  721.         nsfw=False,
  722.         spoiler=False,
  723.         timeout=10,
  724.         collection_id=None,
  725.         without_websockets=False,
  726.     ):
  727.         """Add an image submission to the subreddit.
  728.  
  729.        :param title: The title of the submission.
  730.        :param image_path: The path to an image, to upload and post.
  731.        :param collection_id: The UUID of a :class:`.Collection` to add the
  732.            newly-submitted post to.
  733.        :param flair_id: The flair template to select (default: None).
  734.        :param flair_text: If the template's ``flair_text_editable`` value is
  735.            True, this value will set a custom text (default: None).
  736.        :param resubmit: When False, an error will occur if the URL has already
  737.            been submitted (default: True).
  738.        :param send_replies: When True, messages will be sent to the submission
  739.            author when comments are made to the submission (default: True).
  740.        :param nsfw: Whether or not the submission should be marked NSFW
  741.            (default: False).
  742.        :param spoiler: Whether or not the submission should be marked as
  743.            a spoiler (default: False).
  744.        :param timeout: Specifies a particular timeout, in seconds. Use to
  745.            avoid "Websocket error" exceptions (default: 10).
  746.        :param without_websockets: Set to ``True`` to disable use of WebSockets
  747.            (see note below for an explanation). If ``True``, this method
  748.            doesn't return anything. (default: ``False``).
  749.  
  750.        :returns: A :class:`.Submission` object for the newly created
  751.            submission, unless ``without_websockets`` is ``True``.
  752.  
  753.        .. note::
  754.  
  755.           Reddit's API uses WebSockets to respond with the link of the
  756.           newly created post. If this fails, the method will raise
  757.           :class:`.ClientException`. Occasionally, the Reddit post will still
  758.           be created. More often, there is an error with the image file. If
  759.           you frequently get exceptions but successfully created posts, try
  760.           setting the ``timeout`` parameter to a value above 10.
  761.  
  762.           To disable the use of WebSockets, set ``without_websockets=True``.
  763.           This will make the method return ``None``, though the post will
  764.           still be created. You may wish to do this if you are running your
  765.           program in a restricted network environment, or using a proxy
  766.           that doesn't support WebSockets connections.
  767.  
  768.        For example to submit an image to ``/r/reddit_api_test`` do:
  769.  
  770.        .. code:: python
  771.  
  772.           title = 'My favorite picture'
  773.           image = '/path/to/image.png'
  774.           reddit.subreddit('reddit_api_test').submit_image(title, image)
  775.  
  776.        """
  777.         data = {
  778.             "sr": str(self),
  779.             "resubmit": bool(resubmit),
  780.             "sendreplies": bool(send_replies),
  781.             "title": title,
  782.             "nsfw": bool(nsfw),
  783.             "spoiler": bool(spoiler),
  784.         }
  785.         for key, value in (
  786.             ("flair_id", flair_id),
  787.             ("flair_text", flair_text),
  788.             ("collection_id", collection_id),
  789.         ):
  790.             if value is not None:
  791.                 data[key] = value
  792.         data.update(kind="image", url=self._upload_media(image_path))
  793.         return self._submit_media(
  794.             data, timeout, without_websockets=without_websockets
  795.         )
  796.  
  797.     def submit_video(
  798.         self,
  799.         title,
  800.         video_path,
  801.         videogif=False,
  802.         thumbnail_path=None,
  803.         flair_id=None,
  804.         flair_text=None,
  805.         resubmit=True,
  806.         send_replies=True,
  807.         nsfw=False,
  808.         spoiler=False,
  809.         timeout=10,
  810.         collection_id=None,
  811.         without_websockets=False,
  812.     ):
  813.         """Add a video or videogif submission to the subreddit.
  814.  
  815.        :param title: The title of the submission.
  816.        :param video_path: The path to a video, to upload and post.
  817.        :param videogif: A ``bool`` value. If ``True``, the video is
  818.            uploaded as a videogif, which is essentially a silent video
  819.            (default: ``False``).
  820.        :param thumbnail_path: (Optional) The path to an image, to be uploaded
  821.            and used as the thumbnail for this video. If not provided, the
  822.            PRAW logo will be used as the thumbnail.
  823.        :param collection_id: The UUID of a :class:`.Collection` to add the
  824.            newly-submitted post to.
  825.        :param flair_id: The flair template to select (default: ``None``).
  826.        :param flair_text: If the template's ``flair_text_editable`` value is
  827.            True, this value will set a custom text (default: ``None``).
  828.        :param resubmit: When False, an error will occur if the URL has already
  829.            been submitted (default: ``True``).
  830.        :param send_replies: When True, messages will be sent to the submission
  831.            author when comments are made to the submission
  832.            (default: ``True``).
  833.        :param nsfw: Whether or not the submission should be marked NSFW
  834.            (default: False).
  835.        :param spoiler: Whether or not the submission should be marked as
  836.            a spoiler (default: False).
  837.        :param timeout: Specifies a particular timeout, in seconds. Use to
  838.            avoid "Websocket error" exceptions (default: 10).
  839.        :param without_websockets: Set to ``True`` to disable use of WebSockets
  840.            (see note below for an explanation). If ``True``, this method
  841.            doesn't return anything. (default: ``False``).
  842.  
  843.        :returns: A :class:`.Submission` object for the newly created
  844.            submission, unless ``without_websockets`` is ``True``.
  845.  
  846.        .. note::
  847.  
  848.           Reddit's API uses WebSockets to respond with the link of the
  849.           newly created post. If this fails, the method will raise
  850.           :class:`.ClientException`. Occasionally, the Reddit post will still
  851.           be created. More often, there is an error with the video file. If
  852.           you frequently get exceptions but successfully created posts, try
  853.           setting the ``timeout`` parameter to a value above 10.
  854.  
  855.           To disable the use of WebSockets, set ``without_websockets=True``.
  856.           This will make the method return ``None``, though the post will
  857.           still be created. You may wish to do this if you are running your
  858.           program in a restricted network environment, or using a proxy
  859.           that doesn't support WebSockets connections.
  860.  
  861.        For example to submit a video to ``/r/reddit_api_test`` do:
  862.  
  863.        .. code:: python
  864.  
  865.           title = 'My favorite movie'
  866.           video = '/path/to/video.mp4'
  867.           reddit.subreddit('reddit_api_test').submit_video(title, video)
  868.  
  869.        """
  870.         data = {
  871.             "sr": str(self),
  872.             "resubmit": bool(resubmit),
  873.             "sendreplies": bool(send_replies),
  874.             "title": title,
  875.             "nsfw": bool(nsfw),
  876.             "spoiler": bool(spoiler),
  877.         }
  878.         for key, value in (
  879.             ("flair_id", flair_id),
  880.             ("flair_text", flair_text),
  881.             ("collection_id", collection_id),
  882.         ):
  883.             if value is not None:
  884.                 data[key] = value
  885.         data.update(
  886.             kind="videogif" if videogif else "video",
  887.             url=self._upload_media(video_path),
  888.             # if thumbnail_path is None, it uploads the PRAW logo
  889.             video_poster_url=self._upload_media(thumbnail_path),
  890.         )
  891.         return self._submit_media(
  892.             data, timeout, without_websockets=without_websockets
  893.         )
  894.  
  895.     def subscribe(self, other_subreddits=None):
  896.         """Subscribe to the subreddit.
  897.  
  898.        :param other_subreddits: When provided, also subscribe to the provided
  899.            list of subreddits.
  900.  
  901.        """
  902.         data = {
  903.             "action": "sub",
  904.             "skip_inital_defaults": True,
  905.             "sr_name": self._subreddit_list(self, other_subreddits),
  906.         }
  907.         self._reddit.post(API_PATH["subscribe"], data=data)
  908.  
  909.     def traffic(self):
  910.         """Return a dictionary of the subreddit's traffic statistics.
  911.  
  912.        Raises ``prawcore.NotFound`` when the traffic stats aren't available to
  913.        the authenticated user, that is, they are not public and the
  914.        authenticated user is not a moderator of the subreddit.
  915.  
  916.        """
  917.         return self._reddit.get(
  918.             API_PATH["about_traffic"].format(subreddit=self)
  919.         )
  920.  
  921.     def unsubscribe(self, other_subreddits=None):
  922.         """Unsubscribe from the subreddit.
  923.  
  924.        :param other_subreddits: When provided, also unsubscribe to the
  925.            provided list of subreddits.
  926.  
  927.        """
  928.         data = {
  929.             "action": "unsub",
  930.             "sr_name": self._subreddit_list(self, other_subreddits),
  931.         }
  932.         self._reddit.post(API_PATH["subscribe"], data=data)
  933.  
  934.  
  935. WidgetEncoder._subreddit_class = Subreddit
  936.  
  937.  
  938. class SubredditFilters:
  939.     """Provide functions to interact with the special Subreddit's filters.
  940.  
  941.    Members of this class should be utilized via ``Subreddit.filters``. For
  942.    example to add a filter run:
  943.  
  944.    .. code:: python
  945.  
  946.       reddit.subreddit('all').filters.add('subreddit_name')
  947.  
  948.    """
  949.  
  950.     def __init__(self, subreddit):
  951.         """Create a SubredditFilters instance.
  952.  
  953.        :param subreddit: The special subreddit whose filters to work with.
  954.  
  955.        As of this writing filters can only be used with the special subreddits
  956.        ``all`` and ``mod``.
  957.  
  958.        """
  959.         self.subreddit = subreddit
  960.  
  961.     def __iter__(self):
  962.         """Iterate through the special subreddit's filters.
  963.  
  964.        This method should be invoked as:
  965.  
  966.        .. code:: python
  967.  
  968.           for subreddit in reddit.subreddit('NAME').filters:
  969.               ...
  970.  
  971.        """
  972.         url = API_PATH["subreddit_filter_list"].format(
  973.             special=self.subreddit, user=self.subreddit._reddit.user.me()
  974.         )
  975.         params = {"unique": self.subreddit._reddit._next_unique}
  976.         response_data = self.subreddit._reddit.get(url, params=params)
  977.         for subreddit in response_data.subreddits:
  978.             yield subreddit
  979.  
  980.     def add(self, subreddit):
  981.         """Add ``subreddit`` to the list of filtered subreddits.
  982.  
  983.        :param subreddit: The subreddit to add to the filter list.
  984.  
  985.        Items from subreddits added to the filtered list will no longer be
  986.        included when obtaining listings for ``/r/all``.
  987.  
  988.        Alternatively, you can filter a subreddit temporarily from a special
  989.        listing in a manner like so:
  990.  
  991.        .. code:: python
  992.  
  993.           reddit.subreddit('all-redditdev-learnpython')
  994.  
  995.        Raises ``prawcore.NotFound`` when calling on a non-special subreddit.
  996.  
  997.        """
  998.         url = API_PATH["subreddit_filter"].format(
  999.             special=self.subreddit,
  1000.             user=self.subreddit._reddit.user.me(),
  1001.             subreddit=subreddit,
  1002.         )
  1003.         self.subreddit._reddit.request(
  1004.             "PUT", url, data={"model": dumps({"name": str(subreddit)})}
  1005.         )
  1006.  
  1007.     def remove(self, subreddit):
  1008.         """Remove ``subreddit`` from the list of filtered subreddits.
  1009.  
  1010.        :param subreddit: The subreddit to remove from the filter list.
  1011.  
  1012.        Raises ``prawcore.NotFound`` when calling on a non-special subreddit.
  1013.  
  1014.        """
  1015.         url = API_PATH["subreddit_filter"].format(
  1016.             special=self.subreddit,
  1017.             user=self.subreddit._reddit.user.me(),
  1018.             subreddit=str(subreddit),
  1019.         )
  1020.         self.subreddit._reddit.request("DELETE", url, data={})
  1021.  
  1022.  
  1023. class SubredditFlair:
  1024.     """Provide a set of functions to interact with a Subreddit's flair."""
  1025.  
  1026.     @cachedproperty
  1027.     def link_templates(self):
  1028.         """Provide an instance of :class:`.SubredditLinkFlairTemplates`.
  1029.  
  1030.        Use this attribute for interacting with a subreddit's link flair
  1031.        templates. For example to list all the link flair templates for a
  1032.        subreddit which you have the ``flair`` moderator permission on try:
  1033.  
  1034.        .. code-block:: python
  1035.  
  1036.           for template in reddit.subreddit('NAME').flair.link_templates:
  1037.               print(template)
  1038.  
  1039.        """
  1040.         return SubredditLinkFlairTemplates(self.subreddit)
  1041.  
  1042.     @cachedproperty
  1043.     def templates(self):
  1044.         """Provide an instance of :class:`.SubredditRedditorFlairTemplates`.
  1045.  
  1046.        Use this attribute for interacting with a subreddit's flair
  1047.        templates. For example to list all the flair templates for a subreddit
  1048.        which you have the ``flair`` moderator permission on try:
  1049.  
  1050.        .. code-block:: python
  1051.  
  1052.           for template in reddit.subreddit('NAME').flair.templates:
  1053.               print(template)
  1054.  
  1055.        """
  1056.         return SubredditRedditorFlairTemplates(self.subreddit)
  1057.  
  1058.     def __call__(self, redditor=None, **generator_kwargs):
  1059.         """Return a generator for Redditors and their associated flair.
  1060.  
  1061.        :param redditor: When provided, yield at most a single
  1062.            :class:`~.Redditor` instance (default: None).
  1063.  
  1064.        This method is intended to be used like:
  1065.  
  1066.        .. code-block:: python
  1067.  
  1068.           for flair in reddit.subreddit('NAME').flair(limit=None):
  1069.               print(flair)
  1070.  
  1071.        """
  1072.         Subreddit._safely_add_arguments(
  1073.             generator_kwargs, "params", name=redditor
  1074.         )
  1075.         generator_kwargs.setdefault("limit", None)
  1076.         url = API_PATH["flairlist"].format(subreddit=self.subreddit)
  1077.         return ListingGenerator(
  1078.             self.subreddit._reddit, url, **generator_kwargs
  1079.         )
  1080.  
  1081.     def __init__(self, subreddit):
  1082.         """Create a SubredditFlair instance.
  1083.  
  1084.        :param subreddit: The subreddit whose flair to work with.
  1085.  
  1086.        """
  1087.         self.subreddit = subreddit
  1088.  
  1089.     def configure(
  1090.         self,
  1091.         position="right",
  1092.         self_assign=False,
  1093.         link_position="left",
  1094.         link_self_assign=False,
  1095.         **settings
  1096.     ):
  1097.         """Update the subreddit's flair configuration.
  1098.  
  1099.        :param position: One of left, right, or False to disable (default:
  1100.            right).
  1101.        :param self_assign: (boolean) Permit self assignment of user flair
  1102.            (default: False).
  1103.        :param link_position: One of left, right, or False to disable
  1104.            (default: left).
  1105.        :param link_self_assign: (boolean) Permit self assignment
  1106.               of link flair (default: False).
  1107.  
  1108.        Additional keyword arguments can be provided to handle new settings as
  1109.        Reddit introduces them.
  1110.  
  1111.        """
  1112.         data = {
  1113.             "flair_enabled": bool(position),
  1114.             "flair_position": position or "right",
  1115.             "flair_self_assign_enabled": self_assign,
  1116.             "link_flair_position": link_position or "",
  1117.             "link_flair_self_assign_enabled": link_self_assign,
  1118.         }
  1119.         data.update(settings)
  1120.         url = API_PATH["flairconfig"].format(subreddit=self.subreddit)
  1121.         self.subreddit._reddit.post(url, data=data)
  1122.  
  1123.     def delete(self, redditor):
  1124.         """Delete flair for a Redditor.
  1125.  
  1126.        :param redditor: A redditor name (e.g., ``'spez'``) or
  1127.            :class:`~.Redditor` instance.
  1128.  
  1129.        .. note:: To delete the flair of many Redditors at once, please see
  1130.                  :meth:`~praw.models.reddit.subreddit.SubredditFlair.update`.
  1131.  
  1132.        """
  1133.         url = API_PATH["deleteflair"].format(subreddit=self.subreddit)
  1134.         self.subreddit._reddit.post(url, data={"name": str(redditor)})
  1135.  
  1136.     def delete_all(self):
  1137.         """Delete all Redditor flair in the Subreddit.
  1138.  
  1139.        :returns: List of dictionaries indicating the success or failure of
  1140.            each delete.
  1141.  
  1142.        """
  1143.         return self.update(x["user"] for x in self())
  1144.  
  1145.     def set(
  1146.         self, redditor=None, text="", css_class="", flair_template_id=None
  1147.     ):
  1148.         """Set flair for a Redditor.
  1149.  
  1150.        :param redditor: (Required) A redditor name (e.g., ``'spez'``) or
  1151.            :class:`~.Redditor` instance.
  1152.        :param text: The flair text to associate with the Redditor or
  1153.            Submission (default: '').
  1154.        :param css_class: The css class to associate with the flair html
  1155.            (default: ''). Use either this or ``flair_template_id``.
  1156.        :param flair_template_id: The ID of the flair template to be used
  1157.            (default: ``None``). Use either this or ``css_class``.
  1158.  
  1159.        This method can only be used by an authenticated user who is a
  1160.        moderator of the associated Subreddit.
  1161.  
  1162.        For example:
  1163.  
  1164.        .. code:: python
  1165.  
  1166.           reddit.subreddit('redditdev').flair.set('bboe', 'PRAW author',
  1167.                                                   css_class='mods')
  1168.           template = '6bd28436-1aa7-11e9-9902-0e05ab0fad46'
  1169.           reddit.subreddit('redditdev').flair.set('spez', 'Reddit CEO',
  1170.                                                   flair_template_id=template)
  1171.  
  1172.        """
  1173.         if css_class and flair_template_id is not None:
  1174.             raise TypeError(
  1175.                 "Parameter `css_class` cannot be used in "
  1176.                 "conjunction with `flair_template_id`."
  1177.             )
  1178.         data = {"name": str(redditor), "text": text}
  1179.         if flair_template_id is not None:
  1180.             data["flair_template_id"] = flair_template_id
  1181.             url = API_PATH["select_flair"].format(subreddit=self.subreddit)
  1182.         else:
  1183.             data["css_class"] = css_class
  1184.             url = API_PATH["flair"].format(subreddit=self.subreddit)
  1185.         self.subreddit._reddit.post(url, data=data)
  1186.  
  1187.     def update(self, flair_list, text="", css_class=""):
  1188.         """Set or clear the flair for many Redditors at once.
  1189.  
  1190.        :param flair_list: Each item in this list should be either: the name of
  1191.            a Redditor, an instance of :class:`.Redditor`, or a dictionary
  1192.            mapping keys ``user``, ``flair_text``, and ``flair_css_class`` to
  1193.            their respective values. The ``user`` key should map to a Redditor,
  1194.            as described above. When a dictionary isn't provided, or the
  1195.            dictionary is missing one of ``flair_text``, or ``flair_css_class``
  1196.            attributes the default values will come from the the following
  1197.            arguments.
  1198.  
  1199.        :param text: The flair text to use when not explicitly provided in
  1200.            ``flair_list`` (default: '').
  1201.        :param css_class: The css class to use when not explicitly provided in
  1202.            ``flair_list`` (default: '').
  1203.        :returns: List of dictionaries indicating the success or failure of
  1204.            each update.
  1205.  
  1206.        For example to clear the flair text, and set the ``praw`` flair css
  1207.        class on a few users try:
  1208.  
  1209.        .. code:: python
  1210.  
  1211.           subreddit.flair.update(['bboe', 'spez', 'spladug'],
  1212.                                  css_class='praw')
  1213.  
  1214.        """
  1215.         lines = []
  1216.         for item in flair_list:
  1217.             if isinstance(item, dict):
  1218.                 fmt_data = (
  1219.                     str(item["user"]),
  1220.                     item.get("flair_text", text),
  1221.                     item.get("flair_css_class", css_class),
  1222.                 )
  1223.             else:
  1224.                 fmt_data = (str(item), text, css_class)
  1225.             lines.append('"{}","{}","{}"'.format(*fmt_data))
  1226.  
  1227.         response = []
  1228.         url = API_PATH["flaircsv"].format(subreddit=self.subreddit)
  1229.         while lines:
  1230.             data = {"flair_csv": "\n".join(lines[:100])}
  1231.             response.extend(self.subreddit._reddit.post(url, data=data))
  1232.             lines = lines[100:]
  1233.         return response
  1234.  
  1235.  
  1236. class SubredditFlairTemplates:
  1237.     """Provide functions to interact with a Subreddit's flair templates."""
  1238.  
  1239.     @staticmethod
  1240.     def flair_type(is_link):
  1241.         """Return LINK_FLAIR or USER_FLAIR depending on ``is_link`` value."""
  1242.         return "LINK_FLAIR" if is_link else "USER_FLAIR"
  1243.  
  1244.     def __init__(self, subreddit):
  1245.         """Create a SubredditFlairTemplate instance.
  1246.  
  1247.        :param subreddit: The subreddit whose flair templates to work with.
  1248.  
  1249.        .. note:: This class should not be initialized directly. Instead obtain
  1250.           an instance via:
  1251.           ``reddit.subreddit('subreddit_name').flair.templates`` or
  1252.           ``reddit.subreddit('subreddit_name').flair.link_templates``.
  1253.  
  1254.        """
  1255.         self.subreddit = subreddit
  1256.  
  1257.     def _add(
  1258.         self,
  1259.         text,
  1260.         css_class="",
  1261.         text_editable=False,
  1262.         is_link=None,
  1263.         background_color=None,
  1264.         text_color=None,
  1265.         mod_only=None,
  1266.         max_emojis=10,
  1267.     ):
  1268.         url = API_PATH["flairtemplate_v2"].format(subreddit=self.subreddit)
  1269.         data = {
  1270.             "css_class": css_class,
  1271.             "background_color": background_color,
  1272.             "text_color": text_color,
  1273.             "flair_type": self.flair_type(is_link),
  1274.             "text": text,
  1275.             "text_editable": bool(text_editable),
  1276.             "mod_only": bool(mod_only),
  1277.             "max_emojis": max_emojis,
  1278.         }
  1279.         self.subreddit._reddit.post(url, data=data)
  1280.  
  1281.     def _clear(self, is_link=None):
  1282.         url = API_PATH["flairtemplateclear"].format(subreddit=self.subreddit)
  1283.         self.subreddit._reddit.post(
  1284.             url, data={"flair_type": self.flair_type(is_link)}
  1285.         )
  1286.  
  1287.     def delete(self, template_id):
  1288.         """Remove a flair template provided by ``template_id``.
  1289.  
  1290.        For example, to delete the first Redditor flair template listed, try:
  1291.  
  1292.        .. code-block:: python
  1293.  
  1294.           template_info = list(subreddit.flair.templates)[0]
  1295.           subreddit.flair.templates.delete(template_info['id'])
  1296.  
  1297.        """
  1298.         url = API_PATH["flairtemplatedelete"].format(subreddit=self.subreddit)
  1299.         self.subreddit._reddit.post(
  1300.             url, data={"flair_template_id": template_id}
  1301.         )
  1302.  
  1303.     def update(
  1304.         self,
  1305.         template_id,
  1306.         text,
  1307.         css_class="",
  1308.         text_editable=False,
  1309.         background_color=None,
  1310.         text_color=None,
  1311.         mod_only=None,
  1312.         max_emojis=10,
  1313.     ):
  1314.         """Update the flair template provided by ``template_id``.
  1315.  
  1316.        :param template_id: The flair template to update. If not valid then
  1317.            a new flair template will be made.
  1318.        :param text: The flair template's new text (required).
  1319.        :param css_class: The flair template's new css_class (default: '').
  1320.        :param text_editable: (boolean) Indicate if the flair text can be
  1321.            modified for each Redditor that sets it (default: False).
  1322.        :param background_color: The flair template's new background color,
  1323.            as a hex color.
  1324.        :param text_color: The flair template's new text color, either
  1325.            ``'light'`` or ``'dark'``.
  1326.        :param mod_only: (boolean) Indicate if the flair can only be used by
  1327.            moderators.
  1328.  
  1329.        For example to make a user flair template text_editable, try:
  1330.  
  1331.        .. code-block:: python
  1332.  
  1333.           template_info = list(subreddit.flair.templates)[0]
  1334.           subreddit.flair.templates.update(
  1335.               template_info['id'],
  1336.               template_info['flair_text'],
  1337.               text_editable=True)
  1338.  
  1339.        .. note::
  1340.  
  1341.           Any parameters not provided will be set to default values (usually
  1342.           ``None`` or ``False``) on Reddit's end.
  1343.  
  1344.        """
  1345.         url = API_PATH["flairtemplate_v2"].format(subreddit=self.subreddit)
  1346.         data = {
  1347.             "flair_template_id": template_id,
  1348.             "text": text,
  1349.             "css_class": css_class,
  1350.             "background_color": background_color,
  1351.             "text_color": text_color,
  1352.             "text_editable": text_editable,
  1353.             "mod_only": mod_only,
  1354.             "max_emojis":max_emojis,
  1355.         }
  1356.         self.subreddit._reddit.post(url, data=data)
  1357.  
  1358.  
  1359. class SubredditRedditorFlairTemplates(SubredditFlairTemplates):
  1360.     """Provide functions to interact with Redditor flair templates."""
  1361.  
  1362.     def __iter__(self):
  1363.         """Iterate through the user flair templates.
  1364.  
  1365.        For example:
  1366.  
  1367.        .. code-block:: python
  1368.  
  1369.           for template in reddit.subreddit('NAME').flair.templates:
  1370.               print(template)
  1371.  
  1372.  
  1373.        """
  1374.         url = API_PATH["user_flair"].format(subreddit=self.subreddit)
  1375.         params = {"unique": self.subreddit._reddit._next_unique}
  1376.         for template in self.subreddit._reddit.get(url, params=params):
  1377.             yield template
  1378.  
  1379.     def add(
  1380.         self,
  1381.         text,
  1382.         css_class="",
  1383.         text_editable=False,
  1384.         background_color=None,
  1385.         text_color=None,
  1386.         mod_only=None,
  1387.         max_emojis=10,
  1388.     ):
  1389.         """Add a Redditor flair template to the associated subreddit.
  1390.  
  1391.        :param text: The flair template's text (required).
  1392.        :param css_class: The flair template's css_class (default: '').
  1393.        :param text_editable: (boolean) Indicate if the flair text can be
  1394.            modified for each Redditor that sets it (default: False).
  1395.        :param background_color: The flair template's new background color,
  1396.            as a hex color.
  1397.        :param text_color: The flair template's new text color, either
  1398.            ``'light'`` or ``'dark'``.
  1399.        :param mod_only: (boolean) Indicate if the flair can only be used by
  1400.            moderators.
  1401.  
  1402.        For example, to add an editable Redditor flair try:
  1403.  
  1404.        .. code-block:: python
  1405.  
  1406.           reddit.subreddit('NAME').flair.templates.add(
  1407.               css_class='praw', text_editable=True)
  1408.  
  1409.        """
  1410.         self._add(
  1411.             text,
  1412.             css_class=css_class,
  1413.             text_editable=text_editable,
  1414.             is_link=False,
  1415.             background_color=background_color,
  1416.             text_color=text_color,
  1417.             mod_only=mod_only,
  1418.             max_emojis=max_emojis,
  1419.         )
  1420.  
  1421.     def clear(self):
  1422.         """Remove all Redditor flair templates from the subreddit.
  1423.  
  1424.        For example:
  1425.  
  1426.        .. code-block:: python
  1427.  
  1428.           reddit.subreddit('NAME').flair.templates.clear()
  1429.  
  1430.        """
  1431.         self._clear(is_link=False)
  1432.  
  1433.  
  1434. class SubredditLinkFlairTemplates(SubredditFlairTemplates):
  1435.     """Provide functions to interact with link flair templates."""
  1436.  
  1437.     def __iter__(self):
  1438.         """Iterate through the link flair templates.
  1439.  
  1440.        For example:
  1441.  
  1442.        .. code-block:: python
  1443.  
  1444.           for template in reddit.subreddit('NAME').flair.link_templates:
  1445.               print(template)
  1446.  
  1447.  
  1448.        """
  1449.         url = API_PATH["link_flair"].format(subreddit=self.subreddit)
  1450.         for template in self.subreddit._reddit.get(url):
  1451.             yield template
  1452.  
  1453.     def add(
  1454.         self,
  1455.         text,
  1456.         css_class="",
  1457.         text_editable=False,
  1458.         background_color=None,
  1459.         text_color=None,
  1460.         mod_only=None,
  1461.         max_emojis=10,
  1462.     ):
  1463.         """Add a link flair template to the associated subreddit.
  1464.  
  1465.        :param text: The flair template's text (required).
  1466.        :param css_class: The flair template's css_class (default: '').
  1467.        :param text_editable: (boolean) Indicate if the flair text can be
  1468.            modified for each Redditor that sets it (default: False).
  1469.        :param background_color: The flair template's new background color,
  1470.            as a hex color.
  1471.        :param text_color: The flair template's new text color, either
  1472.            ``'light'`` or ``'dark'``.
  1473.        :param mod_only: (boolean) Indicate if the flair can only be used by
  1474.            moderators.
  1475.  
  1476.        For example, to add an editable link flair try:
  1477.  
  1478.        .. code-block:: python
  1479.  
  1480.           reddit.subreddit('NAME').flair.link_templates.add(
  1481.               css_class='praw', text_editable=True)
  1482.  
  1483.        """
  1484.         self._add(
  1485.             text,
  1486.             css_class=css_class,
  1487.             text_editable=text_editable,
  1488.             is_link=True,
  1489.             background_color=background_color,
  1490.             text_color=text_color,
  1491.             mod_only=mod_only,
  1492.             max_emojis=max_emojis,
  1493.         )
  1494.  
  1495.     def clear(self):
  1496.         """Remove all link flair templates from the subreddit.
  1497.  
  1498.        For example:
  1499.  
  1500.        .. code-block:: python
  1501.  
  1502.           reddit.subreddit('NAME').flair.link_templates.clear()
  1503.  
  1504.        """
  1505.         self._clear(is_link=True)
  1506.  
  1507.  
  1508. class SubredditModeration:
  1509.     """Provides a set of moderation functions to a Subreddit."""
  1510.  
  1511.     @staticmethod
  1512.     def _handle_only(only, generator_kwargs):
  1513.         if only is not None:
  1514.             if only == "submissions":
  1515.                 only = "links"
  1516.             RedditBase._safely_add_arguments(
  1517.                 generator_kwargs, "params", only=only
  1518.             )
  1519.  
  1520.     def __init__(self, subreddit):
  1521.         """Create a SubredditModeration instance.
  1522.  
  1523.        :param subreddit: The subreddit to moderate.
  1524.  
  1525.        """
  1526.         self.subreddit = subreddit
  1527.  
  1528.     def accept_invite(self):
  1529.         """Accept an invitation as a moderator of the community."""
  1530.         url = API_PATH["accept_mod_invite"].format(subreddit=self.subreddit)
  1531.         self.subreddit._reddit.post(url)
  1532.  
  1533.     def edited(self, only=None, **generator_kwargs):
  1534.         """Return a ListingGenerator for edited comments and submissions.
  1535.  
  1536.        :param only: If specified, one of ``'comments'``, or ``'submissions'``
  1537.            to yield only results of that type.
  1538.  
  1539.        Additional keyword arguments are passed in the initialization of
  1540.        :class:`.ListingGenerator`.
  1541.  
  1542.        To print all items in the edited queue try:
  1543.  
  1544.        .. code:: python
  1545.  
  1546.           for item in reddit.subreddit('mod').mod.edited(limit=None):
  1547.               print(item)
  1548.  
  1549.        """
  1550.         self._handle_only(only, generator_kwargs)
  1551.         return ListingGenerator(
  1552.             self.subreddit._reddit,
  1553.             API_PATH["about_edited"].format(subreddit=self.subreddit),
  1554.             **generator_kwargs
  1555.         )
  1556.  
  1557.     def inbox(self, **generator_kwargs):
  1558.         """Return a ListingGenerator for moderator messages.
  1559.  
  1560.        Additional keyword arguments are passed in the initialization of
  1561.        :class:`.ListingGenerator`.
  1562.  
  1563.        See ``unread`` for unread moderator messages.
  1564.  
  1565.        To print the last 5 moderator mail messages and their replies, try:
  1566.  
  1567.        .. code:: python
  1568.  
  1569.           for message in reddit.subreddit('mod').mod.inbox(limit=5):
  1570.               print("From: {}, Body: {}".format(message.author, message.body))
  1571.               for reply in message.replies:
  1572.                   print("From: {}, Body: {}".format(reply.author, reply.body))
  1573.  
  1574.        """
  1575.         return ListingGenerator(
  1576.             self.subreddit._reddit,
  1577.             API_PATH["moderator_messages"].format(subreddit=self.subreddit),
  1578.             **generator_kwargs
  1579.         )
  1580.  
  1581.     def log(self, action=None, mod=None, **generator_kwargs):
  1582.         """Return a ListingGenerator for moderator log entries.
  1583.  
  1584.        :param action: If given, only return log entries for the specified
  1585.            action.
  1586.        :param mod: If given, only return log entries for actions made by the
  1587.            passed in Redditor.
  1588.  
  1589.        To print the moderator and subreddit of the last 5 modlog entries try:
  1590.  
  1591.        .. code:: python
  1592.  
  1593.           for log in reddit.subreddit('mod').mod.log(limit=5):
  1594.               print("Mod: {}, Subreddit: {}".format(log.mod, log.subreddit))
  1595.  
  1596.        """
  1597.         params = {"mod": str(mod) if mod else mod, "type": action}
  1598.         Subreddit._safely_add_arguments(generator_kwargs, "params", **params)
  1599.         return ListingGenerator(
  1600.             self.subreddit._reddit,
  1601.             API_PATH["about_log"].format(subreddit=self.subreddit),
  1602.             **generator_kwargs
  1603.         )
  1604.  
  1605.     def modqueue(self, only=None, **generator_kwargs):
  1606.         """Return a ListingGenerator for comments/submissions in the modqueue.
  1607.  
  1608.        :param only: If specified, one of ``'comments'``, or ``'submissions'``
  1609.            to yield only results of that type.
  1610.  
  1611.        Additional keyword arguments are passed in the initialization of
  1612.        :class:`.ListingGenerator`.
  1613.  
  1614.        To print all modqueue items try:
  1615.  
  1616.        .. code:: python
  1617.  
  1618.           for item in reddit.subreddit('mod').mod.modqueue(limit=None):
  1619.               print(item)
  1620.  
  1621.        """
  1622.         self._handle_only(only, generator_kwargs)
  1623.         return ListingGenerator(
  1624.             self.subreddit._reddit,
  1625.             API_PATH["about_modqueue"].format(subreddit=self.subreddit),
  1626.             **generator_kwargs
  1627.         )
  1628.  
  1629.     def reports(self, only=None, **generator_kwargs):
  1630.         """Return a ListingGenerator for reported comments and submissions.
  1631.  
  1632.        :param only: If specified, one of ``'comments'``, or ``'submissions'``
  1633.            to yield only results of that type.
  1634.  
  1635.        Additional keyword arguments are passed in the initialization of
  1636.        :class:`.ListingGenerator`.
  1637.  
  1638.        To print the user and mod report reasons in the report queue try:
  1639.  
  1640.        .. code:: python
  1641.  
  1642.           for reported_item in reddit.subreddit('mod').mod.reports():
  1643.               print("User Reports: {}".format(reported_item.user_reports))
  1644.               print("Mod Reports: {}".format(reported_item.mod_reports))
  1645.  
  1646.        """
  1647.         self._handle_only(only, generator_kwargs)
  1648.         return ListingGenerator(
  1649.             self.subreddit._reddit,
  1650.             API_PATH["about_reports"].format(subreddit=self.subreddit),
  1651.             **generator_kwargs
  1652.         )
  1653.  
  1654.     def settings(self):
  1655.         """Return a dictionary of the subreddit's current settings."""
  1656.         url = API_PATH["subreddit_settings"].format(subreddit=self.subreddit)
  1657.         return self.subreddit._reddit.get(url)["data"]
  1658.  
  1659.     def spam(self, only=None, **generator_kwargs):
  1660.         """Return a ListingGenerator for spam comments and submissions.
  1661.  
  1662.        :param only: If specified, one of ``'comments'``, or ``'submissions'``
  1663.            to yield only results of that type.
  1664.  
  1665.        Additional keyword arguments are passed in the initialization of
  1666.        :class:`.ListingGenerator`.
  1667.  
  1668.        To print the items in the spam queue try:
  1669.  
  1670.        .. code:: python
  1671.  
  1672.           for item in reddit.subreddit('mod').mod.spam():
  1673.               print(item)
  1674.  
  1675.        """
  1676.         self._handle_only(only, generator_kwargs)
  1677.         return ListingGenerator(
  1678.             self.subreddit._reddit,
  1679.             API_PATH["about_spam"].format(subreddit=self.subreddit),
  1680.             **generator_kwargs
  1681.         )
  1682.  
  1683.     def unmoderated(self, **generator_kwargs):
  1684.         """Return a ListingGenerator for unmoderated submissions.
  1685.  
  1686.        Additional keyword arguments are passed in the initialization of
  1687.        :class:`.ListingGenerator`.
  1688.  
  1689.        To print the items in the unmoderated queue try:
  1690.  
  1691.        .. code:: python
  1692.  
  1693.           for item in reddit.subreddit('mod').mod.unmoderated():
  1694.               print(item)
  1695.  
  1696.        """
  1697.         return ListingGenerator(
  1698.             self.subreddit._reddit,
  1699.             API_PATH["about_unmoderated"].format(subreddit=self.subreddit),
  1700.             **generator_kwargs
  1701.         )
  1702.  
  1703.     def unread(self, **generator_kwargs):
  1704.         """Return a ListingGenerator for unread moderator messages.
  1705.  
  1706.        Additional keyword arguments are passed in the initialization of
  1707.        :class:`.ListingGenerator`.
  1708.  
  1709.        See ``inbox`` for all messages.
  1710.  
  1711.        To print the mail in the unread modmail queue try:
  1712.  
  1713.        .. code:: python
  1714.  
  1715.           for message in reddit.subreddit('mod').mod.unread():
  1716.               print("From: {}, To: {}".format(message.author, message.dest))
  1717.  
  1718.        """
  1719.         return ListingGenerator(
  1720.             self.subreddit._reddit,
  1721.             API_PATH["moderator_unread"].format(subreddit=self.subreddit),
  1722.             **generator_kwargs
  1723.         )
  1724.  
  1725.     def update(self, **settings):
  1726.         """Update the subreddit's settings.
  1727.  
  1728.        :param allow_images: Allow users to upload images using the native
  1729.            image hosting. Only applies to link-only subreddits.
  1730.        :param allow_post_crossposts: Allow users to crosspost submissions from
  1731.            other subreddits.
  1732.        :param allow_top: Allow the subreddit to appear on ``/r/all`` as well
  1733.            as the default and trending lists.
  1734.        :param collapse_deleted_comments: Collapse deleted and removed comments
  1735.            on comments pages by default.
  1736.        :param comment_score_hide_mins: The number of minutes to hide comment
  1737.            scores.
  1738.        :param description: Shown in the sidebar of your subreddit.
  1739.        :param disable_contributor_requests: Specifies whether redditors may
  1740.            send automated modmail messages requesting approval as a submitter.
  1741.        :type disable_contributor_requests: bool
  1742.        :param domain: Domain name with a cname that points to
  1743.            {subreddit}.reddit.com.
  1744.        :param exclude_banned_modqueue: Exclude posts by site-wide banned users
  1745.            from modqueue/unmoderated.
  1746.        :param header_hover_text: The text seen when hovering over the snoo.
  1747.        :param hide_ads: Don't show ads within this subreddit. Only applies to
  1748.            gold-user only subreddits.
  1749.        :param key_color: A 6-digit rgb hex color (e.g. ``'#AABBCC'``), used as
  1750.            a thematic color for your subreddit on mobile.
  1751.        :param lang: A valid IETF language tag (underscore separated).
  1752.        :param link_type: The types of submissions users can make.
  1753.            One of ``any``, ``link``, ``self``.
  1754.        :param over_18: Viewers must be over 18 years old (i.e. NSFW).
  1755.        :param public_description: Public description blurb. Appears in search
  1756.            results and on the landing page for private subreddits.
  1757.        :param public_traffic: Make the traffic stats page public.
  1758.        :param restrict_commenting: Specifies whether approved users have the
  1759.            ability to comment.
  1760.        :type restrict_commenting: bool
  1761.        :param restrict_posting: Specifies whether approved users have the
  1762.            ability to submit posts.
  1763.        :type restrict_posting: bool
  1764.        :param show_media: Show thumbnails on submissions.
  1765.        :param show_media_preview: Expand media previews on comments pages.
  1766.        :param spam_comments: Spam filter strength for comments.
  1767.            One of ``all``, ``low``, ``high``.
  1768.        :param spam_links: Spam filter strength for links.
  1769.            One of ``all``, ``low``, ``high``.
  1770.        :param spam_selfposts: Spam filter strength for selfposts.
  1771.            One of ``all``, ``low``, ``high``.
  1772.        :param spoilers_enabled: Enable marking posts as containing spoilers.
  1773.        :param sr: The fullname of the subreddit whose settings will be
  1774.            updated.
  1775.        :param submit_link_label: Custom label for submit link button
  1776.            (None for default).
  1777.        :param submit_text: Text to show on submission page.
  1778.        :param submit_text_label: Custom label for submit text post button
  1779.            (None for default).
  1780.        :param subreddit_type: One of ``archived``, ``employees_only``,
  1781.            ``gold_only``, ``gold_restricted``, ``private``, ``public``,
  1782.            ``restricted``.
  1783.        :param suggested_comment_sort: All comment threads will use this
  1784.            sorting method by default. Leave None, or choose one of
  1785.            ``confidence``, ``controversial``, ``new``, ``old``, ``qa``,
  1786.            ``random``, ``top``.
  1787.        :param title: The title of the subreddit.
  1788.        :param wiki_edit_age: Account age, in days, required to edit and create
  1789.            wiki pages.
  1790.        :param wiki_edit_karma: Subreddit karma required to edit and create
  1791.            wiki pages.
  1792.        :param wikimode: One of  ``anyone``, ``disabled``, ``modonly``.
  1793.  
  1794.        Additional keyword arguments can be provided to handle new settings as
  1795.        Reddit introduces them.
  1796.  
  1797.        Settings that are documented here and aren't explicitly set by you in a
  1798.        call to :meth:`.SubredditModeration.update` should retain their current
  1799.        value. If they do not please file a bug.
  1800.  
  1801.        .. warning:: Undocumented settings, or settings that were very recently
  1802.                     documented, may not retain their current value when
  1803.                     updating. This often occurs when Reddit adds a new setting
  1804.                     but forgets to add that setting to the API endpoint that
  1805.                     is used to fetch the current settings.
  1806.  
  1807.        """
  1808.         current_settings = self.settings()
  1809.         fullname = current_settings.pop("subreddit_id")
  1810.  
  1811.         # These attributes come out using different names than they go in.
  1812.         remap = {
  1813.             "allow_top": "default_set",
  1814.             "lang": "language",
  1815.             "link_type": "content_options",
  1816.         }
  1817.         for (new, old) in remap.items():
  1818.             current_settings[new] = current_settings.pop(old)
  1819.  
  1820.         current_settings.update(settings)
  1821.         return Subreddit._create_or_update(
  1822.             _reddit=self.subreddit._reddit, sr=fullname, **current_settings
  1823.         )
  1824.  
  1825.  
  1826. class SubredditQuarantine:
  1827.     """Provides subreddit quarantine related methods."""
  1828.  
  1829.     def __init__(self, subreddit):
  1830.         """Create a SubredditQuarantine instance.
  1831.  
  1832.        :param subreddit: The subreddit associated with the quarantine.
  1833.  
  1834.        """
  1835.         self.subreddit = subreddit
  1836.  
  1837.     def opt_in(self):
  1838.         """Permit your user access to the quarantined subreddit.
  1839.  
  1840.        Usage:
  1841.  
  1842.        .. code:: python
  1843.  
  1844.           subreddit = reddit.subreddit('QUESTIONABLE')
  1845.           next(subreddit.hot())  # Raises prawcore.Forbidden
  1846.  
  1847.           subreddit.quaran.opt_in()
  1848.           next(subreddit.hot())  # Returns Submission
  1849.  
  1850.        """
  1851.         data = {"sr_name": self.subreddit}
  1852.         try:
  1853.             self.subreddit._reddit.post(
  1854.                 API_PATH["quarantine_opt_in"], data=data
  1855.             )
  1856.         except Redirect:
  1857.             pass
  1858.  
  1859.     def opt_out(self):
  1860.         """Remove access to the quarantined subreddit.
  1861.  
  1862.        Usage:
  1863.  
  1864.        .. code:: python
  1865.  
  1866.           subreddit = reddit.subreddit('QUESTIONABLE')
  1867.           next(subreddit.hot())  # Returns Submission
  1868.  
  1869.           subreddit.quaran.opt_out()
  1870.           next(subreddit.hot())  # Raises prawcore.Forbidden
  1871.  
  1872.        """
  1873.         data = {"sr_name": self.subreddit}
  1874.         try:
  1875.             self.subreddit._reddit.post(
  1876.                 API_PATH["quarantine_opt_out"], data=data
  1877.             )
  1878.         except Redirect:
  1879.             pass
  1880.  
  1881.  
  1882. class SubredditRelationship:
  1883.     """Represents a relationship between a redditor and subreddit.
  1884.  
  1885.    Instances of this class can be iterated through in order to discover the
  1886.    Redditors that make up the relationship.
  1887.  
  1888.    For example, banned users of a subreddit can be iterated through like so:
  1889.  
  1890.    .. code-block:: python
  1891.  
  1892.       for ban in reddit.subreddit('redditdev').banned():
  1893.           print('{}: {}'.format(ban, ban.note))
  1894.  
  1895.    """
  1896.  
  1897.     def __call__(self, redditor=None, **generator_kwargs):
  1898.         """Return a generator for Redditors belonging to this relationship.
  1899.  
  1900.        :param redditor: When provided, yield at most a single
  1901.            :class:`~.Redditor` instance. This is useful to confirm if a
  1902.            relationship exists, or to fetch the metadata associated with a
  1903.            particular relationship (default: None).
  1904.  
  1905.        Additional keyword arguments are passed in the initialization of
  1906.        :class:`.ListingGenerator`.
  1907.  
  1908.        """
  1909.         Subreddit._safely_add_arguments(
  1910.             generator_kwargs, "params", user=redditor
  1911.         )
  1912.         url = API_PATH["list_{}".format(self.relationship)].format(
  1913.             subreddit=self.subreddit
  1914.         )
  1915.         return ListingGenerator(
  1916.             self.subreddit._reddit, url, **generator_kwargs
  1917.         )
  1918.  
  1919.     def __init__(self, subreddit, relationship):
  1920.         """Create a SubredditRelationship instance.
  1921.  
  1922.        :param subreddit: The subreddit for the relationship.
  1923.        :param relationship: The name of the relationship.
  1924.  
  1925.        """
  1926.         self.relationship = relationship
  1927.         self.subreddit = subreddit
  1928.  
  1929.     def add(self, redditor, **other_settings):
  1930.         """Add ``redditor`` to this relationship.
  1931.  
  1932.        :param redditor: A redditor name (e.g., ``'spez'``) or
  1933.            :class:`~.Redditor` instance.
  1934.  
  1935.        """
  1936.         data = {"name": str(redditor), "type": self.relationship}
  1937.         data.update(other_settings)
  1938.         url = API_PATH["friend"].format(subreddit=self.subreddit)
  1939.         self.subreddit._reddit.post(url, data=data)
  1940.  
  1941.     def remove(self, redditor):
  1942.         """Remove ``redditor`` from this relationship.
  1943.  
  1944.        :param redditor: A redditor name (e.g., ``'spez'``) or
  1945.            :class:`~.Redditor` instance.
  1946.  
  1947.        """
  1948.         data = {"name": str(redditor), "type": self.relationship}
  1949.         url = API_PATH["unfriend"].format(subreddit=self.subreddit)
  1950.         self.subreddit._reddit.post(url, data=data)
  1951.  
  1952.  
  1953. class ContributorRelationship(SubredditRelationship):
  1954.     """Provides methods to interact with a Subreddit's contributors.
  1955.  
  1956.    Contributors are also known as approved submitters.
  1957.  
  1958.    Contributors of a subreddit can be iterated through like so:
  1959.  
  1960.    .. code-block:: python
  1961.  
  1962.       for contributor in reddit.subreddit('redditdev').contributor():
  1963.           print(contributor)
  1964.  
  1965.    """
  1966.  
  1967.     def leave(self):
  1968.         """Abdicate the contributor position."""
  1969.         self.subreddit._reddit.post(
  1970.             API_PATH["leavecontributor"], data={"id": self.subreddit.fullname}
  1971.         )
  1972.  
  1973.  
  1974. class ModeratorRelationship(SubredditRelationship):
  1975.     """Provides methods to interact with a Subreddit's moderators.
  1976.  
  1977.    Moderators of a subreddit can be iterated through like so:
  1978.  
  1979.    .. code-block:: python
  1980.  
  1981.       for moderator in reddit.subreddit('redditdev').moderator():
  1982.           print(moderator)
  1983.  
  1984.    """
  1985.  
  1986.     PERMISSIONS = {"access", "config", "flair", "mail", "posts", "wiki"}
  1987.  
  1988.     @staticmethod
  1989.     def _handle_permissions(permissions, other_settings):
  1990.         other_settings = deepcopy(other_settings) if other_settings else {}
  1991.         other_settings["permissions"] = permissions_string(
  1992.             permissions, ModeratorRelationship.PERMISSIONS
  1993.         )
  1994.         return other_settings
  1995.  
  1996.     def __call__(self, redditor=None):  # pylint: disable=arguments-differ
  1997.         """Return a list of Redditors who are moderators.
  1998.  
  1999.        :param redditor: When provided, return a list containing at most one
  2000.            :class:`~.Redditor` instance. This is useful to confirm if a
  2001.            relationship exists, or to fetch the metadata associated with a
  2002.            particular relationship (default: None).
  2003.  
  2004.        .. note:: Unlike other relationship callables, this relationship is not
  2005.                  paginated. Thus it simply returns the full list, rather than
  2006.                  an iterator for the results.
  2007.  
  2008.        To be used like:
  2009.  
  2010.        .. code:: python
  2011.  
  2012.           moderators = reddit.subreddit('nameofsub').moderator()
  2013.  
  2014.        For example, to list the moderators along with their permissions try:
  2015.  
  2016.        .. code:: python
  2017.  
  2018.           for moderator in reddit.subreddit('SUBREDDIT').moderator():
  2019.               print('{}: {}'.format(moderator, moderator.mod_permissions))
  2020.  
  2021.  
  2022.        """
  2023.         params = {} if redditor is None else {"user": redditor}
  2024.         url = API_PATH["list_{}".format(self.relationship)].format(
  2025.             subreddit=self.subreddit
  2026.         )
  2027.         return self.subreddit._reddit.get(url, params=params)
  2028.  
  2029.     # pylint: disable=arguments-differ
  2030.     def add(self, redditor, permissions=None, **other_settings):
  2031.         """Add or invite ``redditor`` to be a moderator of the subreddit.
  2032.  
  2033.        :param redditor: A redditor name (e.g., ``'spez'``) or
  2034.            :class:`~.Redditor` instance.
  2035.        :param permissions: When provided (not ``None``), permissions should be
  2036.            a list of strings specifying which subset of permissions to
  2037.            grant. An empty list ``[]`` indicates no permissions, and when not
  2038.            provided ``None``, indicates full permissions.
  2039.  
  2040.        An invite will be sent unless the user making this call is an admin
  2041.        user.
  2042.  
  2043.        For example, to invite ``'spez'`` with ``'posts'`` and ``'mail'``
  2044.            permissions to ``'/r/test/``, try:
  2045.  
  2046.        .. code:: python
  2047.  
  2048.           reddit.subreddit('test').moderator.add('spez', ['posts', 'mail'])
  2049.  
  2050.        """
  2051.         other_settings = self._handle_permissions(permissions, other_settings)
  2052.         super(ModeratorRelationship, self).add(redditor, **other_settings)
  2053.  
  2054.     # pylint: enable=arguments-differ
  2055.  
  2056.     def invite(self, redditor, permissions=None, **other_settings):
  2057.         """Invite ``redditor`` to be a moderator of the subreddit.
  2058.  
  2059.        :param redditor: A redditor name (e.g., ``'spez'``) or
  2060.            :class:`~.Redditor` instance.
  2061.        :param permissions: When provided (not ``None``), permissions should be
  2062.            a list of strings specifying which subset of permissions to
  2063.            grant. An empty list ``[]`` indicates no permissions, and when not
  2064.            provided ``None``, indicates full permissions.
  2065.  
  2066.        For example, to invite ``'spez'`` with ``'posts'`` and ``'mail'``
  2067.            permissions to ``'/r/test/``, try:
  2068.  
  2069.        .. code:: python
  2070.  
  2071.           reddit.subreddit('test').moderator.invite('spez', ['posts', 'mail'])
  2072.  
  2073.        """
  2074.         data = self._handle_permissions(permissions, other_settings)
  2075.         data.update({"name": str(redditor), "type": "moderator_invite"})
  2076.         url = API_PATH["friend"].format(subreddit=self.subreddit)
  2077.         self.subreddit._reddit.post(url, data=data)
  2078.  
  2079.     def leave(self):
  2080.         """Abdicate the moderator position (use with care).
  2081.  
  2082.        For example:
  2083.  
  2084.        .. code:: python
  2085.  
  2086.           reddit.subreddit('subredditname').moderator.leave()
  2087.  
  2088.        """
  2089.         self.remove(self.subreddit._reddit.config.username)
  2090.  
  2091.     def remove_invite(self, redditor):
  2092.         """Remove the moderator invite for ``redditor``.
  2093.  
  2094.        :param redditor: A redditor name (e.g., ``'spez'``) or
  2095.            :class:`~.Redditor` instance.
  2096.  
  2097.        For example:
  2098.  
  2099.        .. code:: python
  2100.  
  2101.           reddit.subreddit('subredditname').moderator.remove_invite('spez')
  2102.  
  2103.        """
  2104.         data = {"name": str(redditor), "type": "moderator_invite"}
  2105.         url = API_PATH["unfriend"].format(subreddit=self.subreddit)
  2106.         self.subreddit._reddit.post(url, data=data)
  2107.  
  2108.     def update(self, redditor, permissions=None):
  2109.         """Update the moderator permissions for ``redditor``.
  2110.  
  2111.        :param redditor: A redditor name (e.g., ``'spez'``) or
  2112.            :class:`~.Redditor` instance.
  2113.        :param permissions: When provided (not ``None``), permissions should be
  2114.            a list of strings specifying which subset of permissions to
  2115.            grant. An empty list ``[]`` indicates no permissions, and when not
  2116.            provided, ``None``, indicates full permissions.
  2117.  
  2118.        For example, to add all permissions to the moderator, try:
  2119.  
  2120.        .. code:: python
  2121.  
  2122.           subreddit.moderator.update('spez')
  2123.  
  2124.        To remove all permissions from the moderator, try:
  2125.  
  2126.        .. code:: python
  2127.  
  2128.           subreddit.moderator.update('spez', [])
  2129.  
  2130.        """
  2131.         url = API_PATH["setpermissions"].format(subreddit=self.subreddit)
  2132.         data = self._handle_permissions(
  2133.             permissions, {"name": str(redditor), "type": "moderator"}
  2134.         )
  2135.         self.subreddit._reddit.post(url, data=data)
  2136.  
  2137.     def update_invite(self, redditor, permissions=None):
  2138.         """Update the moderator invite permissions for ``redditor``.
  2139.  
  2140.        :param redditor: A redditor name (e.g., ``'spez'``) or
  2141.            :class:`~.Redditor` instance.
  2142.        :param permissions: When provided (not ``None``), permissions should be
  2143.            a list of strings specifying which subset of permissions to
  2144.            grant. An empty list ``[]`` indicates no permissions, and when not
  2145.            provided, ``None``, indicates full permissions.
  2146.  
  2147.        For example, to grant the flair and mail permissions to the moderator
  2148.        invite, try:
  2149.  
  2150.        .. code:: python
  2151.  
  2152.           subreddit.moderator.update_invite('spez', ['flair', 'mail'])
  2153.  
  2154.        """
  2155.         url = API_PATH["setpermissions"].format(subreddit=self.subreddit)
  2156.         data = self._handle_permissions(
  2157.             permissions, {"name": str(redditor), "type": "moderator_invite"}
  2158.         )
  2159.         self.subreddit._reddit.post(url, data=data)
  2160.  
  2161.  
  2162. class Modmail:
  2163.     """Provides modmail functions for a subreddit."""
  2164.  
  2165.     def __call__(self, id=None, mark_read=False):  # noqa: D207, D301
  2166.         """Return an individual conversation.
  2167.  
  2168.        :param id: A reddit base36 conversation ID, e.g., ``2gmz``.
  2169.        :param mark_read: If True, conversation is marked as read
  2170.            (default: False).
  2171.  
  2172.        For example:
  2173.  
  2174.        .. code:: python
  2175.  
  2176.           reddit.subreddit('redditdev').modmail('2gmz', mark_read=True)
  2177.  
  2178.        To print all messages from a conversation as Markdown source:
  2179.  
  2180.        .. code:: python
  2181.  
  2182.           conversation = reddit.subreddit('redditdev').modmail('2gmz', \
  2183. mark_read=True)
  2184.           for message in conversation.messages:
  2185.               print(message.body_markdown)
  2186.  
  2187.        ``ModmailConversation.user`` is a special instance of
  2188.        :class:`.Redditor` with extra attributes describing the non-moderator
  2189.        user's recent posts, comments, and modmail messages within the
  2190.        subreddit, as well as information on active bans and mutes. This
  2191.        attribute does not exist on internal moderator discussions.
  2192.  
  2193.        For example, to print the user's ban status:
  2194.  
  2195.        .. code:: python
  2196.  
  2197.           conversation = reddit.subreddit('redditdev').modmail('2gmz', \
  2198. mark_read=True)
  2199.           print(conversation.user.ban_status)
  2200.  
  2201.        To print a list of recent submissions by the user:
  2202.  
  2203.        .. code:: python
  2204.  
  2205.           conversation = reddit.subreddit('redditdev').modmail('2gmz', \
  2206. mark_read=True)
  2207.           print(conversation.user.recent_posts)
  2208.  
  2209.        """
  2210.         # pylint: disable=invalid-name,redefined-builtin
  2211.         return ModmailConversation(
  2212.             self.subreddit._reddit, id=id, mark_read=mark_read
  2213.         )
  2214.  
  2215.     def __init__(self, subreddit):
  2216.         """Construct an instance of the Modmail object."""
  2217.         self.subreddit = subreddit
  2218.  
  2219.     def _build_subreddit_list(self, other_subreddits):
  2220.         """Return a comma-separated list of subreddit display names."""
  2221.         subreddits = [self.subreddit] + (other_subreddits or [])
  2222.         return ",".join(str(subreddit) for subreddit in subreddits)
  2223.  
  2224.     def bulk_read(self, other_subreddits=None, state=None):
  2225.         """Mark conversations for subreddit(s) as read.
  2226.  
  2227.        Due to server-side restrictions, 'all' is not a valid subreddit for
  2228.        this method. Instead, use :meth:`~.Modmail.subreddits` to get a list of
  2229.        subreddits using the new modmail.
  2230.  
  2231.        :param other_subreddits: A list of :class:`.Subreddit` instances for
  2232.            which to mark conversations (default: None).
  2233.        :param state: Can be one of: all, archived, highlighted, inprogress,
  2234.            mod, new, notifications, (default: all). "all" does not include
  2235.            internal or archived conversations.
  2236.        :returns: A list of :class:`.ModmailConversation` instances that were
  2237.            marked read.
  2238.  
  2239.        For example, to mark all notifications for a subreddit as read:
  2240.  
  2241.        .. code:: python
  2242.  
  2243.           subreddit = reddit.subreddit('redditdev')
  2244.           subreddit.modmail.bulk_read(state='notifications')
  2245.  
  2246.        """
  2247.         params = {"entity": self._build_subreddit_list(other_subreddits)}
  2248.         if state:
  2249.             params["state"] = state
  2250.         response = self.subreddit._reddit.post(
  2251.             API_PATH["modmail_bulk_read"], params=params
  2252.         )
  2253.         return [
  2254.             self(conversation_id)
  2255.             for conversation_id in response["conversation_ids"]
  2256.         ]
  2257.  
  2258.     def conversations(
  2259.         self,
  2260.         after=None,
  2261.         limit=None,
  2262.         other_subreddits=None,
  2263.         sort=None,
  2264.         state=None,
  2265.     ):  # noqa: D207, D301
  2266.         """Generate :class:`.ModmailConversation` objects for subreddit(s).
  2267.  
  2268.        :param after: A base36 modmail conversation id. When provided, the
  2269.            listing begins after this conversation (default: None).
  2270.        :param limit: The maximum number of conversations to fetch. If None,
  2271.            the server-side default is 25 at the time of writing
  2272.            (default: None).
  2273.        :param other_subreddits: A list of :class:`.Subreddit` instances for
  2274.            which to fetch conversations (default: None).
  2275.        :param sort: Can be one of: mod, recent, unread, user
  2276.            (default: recent).
  2277.        :param state: Can be one of: all, archived, highlighted, inprogress,
  2278.            mod, new, notifications, (default: all). "all" does not include
  2279.            internal or archived conversations.
  2280.  
  2281.  
  2282.        For example:
  2283.  
  2284.        .. code:: python
  2285.  
  2286.            conversations = reddit.subreddit('all').modmail.conversations(\
  2287. state='mod')
  2288.  
  2289.        """
  2290.         params = {}
  2291.         if self.subreddit != "all":
  2292.             params["entity"] = self._build_subreddit_list(other_subreddits)
  2293.  
  2294.         for name, value in {
  2295.             "after": after,
  2296.             "limit": limit,
  2297.             "sort": sort,
  2298.             "state": state,
  2299.         }.items():
  2300.             if value:
  2301.                 params[name] = value
  2302.  
  2303.         response = self.subreddit._reddit.get(
  2304.             API_PATH["modmail_conversations"], params=params
  2305.         )
  2306.         for conversation_id in response["conversationIds"]:
  2307.             data = {
  2308.                 "conversation": response["conversations"][conversation_id],
  2309.                 "messages": response["messages"],
  2310.             }
  2311.             yield ModmailConversation.parse(
  2312.                 data, self.subreddit._reddit, convert_objects=False
  2313.             )
  2314.  
  2315.     def create(self, subject, body, recipient, author_hidden=False):
  2316.         """Create a new modmail conversation.
  2317.  
  2318.        :param subject: The message subject. Cannot be empty.
  2319.        :param body: The message body. Cannot be empty.
  2320.        :param recipient: The recipient; a username or an instance of
  2321.            :class:`.Redditor`.
  2322.        :param author_hidden: When True, author is hidden from non-moderators
  2323.            (default: False).
  2324.        :returns: A :class:`.ModmailConversation` object for the newly created
  2325.            conversation.
  2326.  
  2327.        .. code:: python
  2328.  
  2329.           subreddit = reddit.subreddit('redditdev')
  2330.           redditor = reddit.redditor('bboe')
  2331.           subreddit.modmail.create('Subject', 'Body', redditor)
  2332.  
  2333.        """
  2334.         data = {
  2335.             "body": body,
  2336.             "isAuthorHidden": author_hidden,
  2337.             "srName": self.subreddit,
  2338.             "subject": subject,
  2339.             "to": recipient,
  2340.         }
  2341.         return self.subreddit._reddit.post(
  2342.             API_PATH["modmail_conversations"], data=data
  2343.         )
  2344.  
  2345.     def subreddits(self):
  2346.         """Yield subreddits using the new modmail that the user moderates.
  2347.  
  2348.        For example:
  2349.  
  2350.        .. code:: python
  2351.  
  2352.           subreddits = reddit.subreddit('all').modmail.subreddits()
  2353.  
  2354.        """
  2355.         response = self.subreddit._reddit.get(API_PATH["modmail_subreddits"])
  2356.         for value in response["subreddits"].values():
  2357.             subreddit = self.subreddit._reddit.subreddit(value["display_name"])
  2358.             subreddit.last_updated = value["lastUpdated"]
  2359.             yield subreddit
  2360.  
  2361.     def unread_count(self):
  2362.         """Return unread conversation count by conversation state.
  2363.  
  2364.        At time of writing, possible states are: archived, highlighted,
  2365.        inprogress, mod, new, notifications.
  2366.  
  2367.        :returns: A dict mapping conversation states to unread counts.
  2368.  
  2369.        For example, to print the count of unread moderator discussions:
  2370.  
  2371.        .. code:: python
  2372.  
  2373.           subreddit = reddit.subreddit('redditdev')
  2374.           unread_counts = subreddit.modmail.unread_count()
  2375.           print(unread_counts['mod'])
  2376.  
  2377.        """
  2378.         return self.subreddit._reddit.get(API_PATH["modmail_unread_count"])
  2379.  
  2380.  
  2381. class SubredditStream:
  2382.     """Provides submission and comment streams."""
  2383.  
  2384.     def __init__(self, subreddit):
  2385.         """Create a SubredditStream instance.
  2386.  
  2387.        :param subreddit: The subreddit associated with the streams.
  2388.  
  2389.        """
  2390.         self.subreddit = subreddit
  2391.  
  2392.     def comments(self, **stream_options):
  2393.         """Yield new comments as they become available.
  2394.  
  2395.        Comments are yielded oldest first. Up to 100 historical comments will
  2396.        initially be returned.
  2397.  
  2398.        Keyword arguments are passed to :func:`.stream_generator`.
  2399.  
  2400.        For example, to retrieve all new comments made to the ``iama``
  2401.        subreddit, try:
  2402.  
  2403.        .. code:: python
  2404.  
  2405.           for comment in reddit.subreddit('iama').stream.comments():
  2406.               print(comment)
  2407.  
  2408.        To only retreive new submissions starting when the stream is
  2409.        created, pass `skip_existing=True`:
  2410.  
  2411.        .. code:: python
  2412.  
  2413.           subreddit = reddit.subreddit('iama')
  2414.           for comment in subreddit.stream.comments(skip_existing=True):
  2415.               print(comment)
  2416.  
  2417.        """
  2418.         return stream_generator(self.subreddit.comments, **stream_options)
  2419.  
  2420.     def submissions(self, **stream_options):
  2421.         """Yield new submissions as they become available.
  2422.  
  2423.        Submissions are yielded oldest first. Up to 100 historical submissions
  2424.        will initially be returned.
  2425.  
  2426.        Keyword arguments are passed to :func:`.stream_generator`.
  2427.  
  2428.        For example to retrieve all new submissions made to all of Reddit, try:
  2429.  
  2430.        .. code:: python
  2431.  
  2432.           for submission in reddit.subreddit('all').stream.submissions():
  2433.               print(submission)
  2434.  
  2435.        """
  2436.         return stream_generator(self.subreddit.new, **stream_options)
  2437.  
  2438.  
  2439. class SubredditStylesheet:
  2440.     """Provides a set of stylesheet functions to a Subreddit."""
  2441.  
  2442.     def __call__(self):
  2443.         """Return the subreddit's stylesheet.
  2444.  
  2445.        To be used as:
  2446.  
  2447.        .. code:: python
  2448.  
  2449.           stylesheet = reddit.subreddit('SUBREDDIT').stylesheet()
  2450.  
  2451.        """
  2452.         url = API_PATH["about_stylesheet"].format(subreddit=self.subreddit)
  2453.         return self.subreddit._reddit.get(url)
  2454.  
  2455.     def __init__(self, subreddit):
  2456.         """Create a SubredditStylesheet instance.
  2457.  
  2458.        :param subreddit: The subreddit associated with the stylesheet.
  2459.  
  2460.        An instance of this class is provided as:
  2461.  
  2462.        .. code:: python
  2463.  
  2464.           reddit.subreddit('SUBREDDIT').stylesheet
  2465.  
  2466.        """
  2467.         self.subreddit = subreddit
  2468.  
  2469.     def _update_structured_styles(self, style_data):
  2470.         url = API_PATH["structured_styles"].format(subreddit=self.subreddit)
  2471.         self.subreddit._reddit.patch(url, style_data)
  2472.  
  2473.     def _upload_image(self, image_path, data):
  2474.         with open(image_path, "rb") as image:
  2475.             header = image.read(len(JPEG_HEADER))
  2476.             image.seek(0)
  2477.             data["img_type"] = "jpg" if header == JPEG_HEADER else "png"
  2478.             url = API_PATH["upload_image"].format(subreddit=self.subreddit)
  2479.             response = self.subreddit._reddit.post(
  2480.                 url, data=data, files={"file": image}
  2481.             )
  2482.             if response["errors"]:
  2483.                 error_type = response["errors"][0]
  2484.                 error_value = response.get("errors_values", [""])[0]
  2485.                 assert error_type in [
  2486.                     "BAD_CSS_NAME",
  2487.                     "IMAGE_ERROR",
  2488.                 ], "Please file a bug with PRAW"
  2489.                 raise APIException(error_type, error_value, None)
  2490.             return response
  2491.  
  2492.     def _upload_style_asset(self, image_path, image_type):
  2493.         data = {"imagetype": image_type, "filepath": basename(image_path)}
  2494.         data["mimetype"] = "image/jpeg"
  2495.         if image_path.lower().endswith(".png"):
  2496.             data["mimetype"] = "image/png"
  2497.         url = API_PATH["style_asset_lease"].format(subreddit=self.subreddit)
  2498.  
  2499.         upload_lease = self.subreddit._reddit.post(url, data=data)[
  2500.             "s3UploadLease"
  2501.         ]
  2502.         upload_data = {
  2503.             item["name"]: item["value"] for item in upload_lease["fields"]
  2504.         }
  2505.         upload_url = "https:{}".format(upload_lease["action"])
  2506.  
  2507.         with open(image_path, "rb") as image:
  2508.             response = self.subreddit._reddit._core._requestor._http.post(
  2509.                 upload_url, data=upload_data, files={"file": image}
  2510.             )
  2511.         response.raise_for_status()
  2512.  
  2513.         return "{}/{}".format(upload_url, upload_data["key"])
  2514.  
  2515.     def delete_banner(self):
  2516.         """Remove the current subreddit (redesign) banner image.
  2517.  
  2518.        Succeeds even if there is no banner image.
  2519.  
  2520.        For example:
  2521.  
  2522.        .. code:: python
  2523.  
  2524.           reddit.subreddit('SUBREDDIT').stylesheet.delete_banner()
  2525.  
  2526.        """
  2527.         data = {"bannerBackgroundImage": ""}
  2528.         self._update_structured_styles(data)
  2529.  
  2530.     def delete_banner_additional_image(self):
  2531.         """Remove the current subreddit (redesign) banner additional image.
  2532.  
  2533.        Succeeds even if there is no additional image.  Will also delete any
  2534.        configured hover image.
  2535.  
  2536.        For example:
  2537.  
  2538.        .. code:: python
  2539.  
  2540.           reddit.subreddit('SUBREDDIT').stylesheet.delete_banner_additional_image()
  2541.  
  2542.        """
  2543.         data = {
  2544.             "bannerPositionedImage": "",
  2545.             "secondaryBannerPositionedImage": "",
  2546.         }
  2547.         self._update_structured_styles(data)
  2548.  
  2549.     def delete_banner_hover_image(self):
  2550.         """Remove the current subreddit (redesign) banner hover image.
  2551.  
  2552.        Succeeds even if there is no hover image.
  2553.  
  2554.        For example:
  2555.  
  2556.        .. code:: python
  2557.  
  2558.           reddit.subreddit('SUBREDDIT').stylesheet.delete_banner_hover_image()
  2559.  
  2560.        """
  2561.         data = {"secondaryBannerPositionedImage": ""}
  2562.         self._update_structured_styles(data)
  2563.  
  2564.     def delete_header(self):
  2565.         """Remove the current subreddit header image.
  2566.  
  2567.        Succeeds even if there is no header image.
  2568.  
  2569.        For example:
  2570.  
  2571.        .. code:: python
  2572.  
  2573.           reddit.subreddit('SUBREDDIT').stylesheet.delete_header()
  2574.  
  2575.        """
  2576.         url = API_PATH["delete_sr_header"].format(subreddit=self.subreddit)
  2577.         self.subreddit._reddit.post(url)
  2578.  
  2579.     def delete_image(self, name):
  2580.         """Remove the named image from the subreddit.
  2581.  
  2582.        Succeeds even if the named image does not exist.
  2583.  
  2584.        For example:
  2585.  
  2586.        .. code:: python
  2587.  
  2588.           reddit.subreddit('SUBREDDIT').stylesheet.delete_image('smile')
  2589.  
  2590.        """
  2591.         url = API_PATH["delete_sr_image"].format(subreddit=self.subreddit)
  2592.         self.subreddit._reddit.post(url, data={"img_name": name})
  2593.  
  2594.     def delete_mobile_header(self):
  2595.         """Remove the current subreddit mobile header.
  2596.  
  2597.        Succeeds even if there is no mobile header.
  2598.  
  2599.        For example:
  2600.  
  2601.        .. code:: python
  2602.  
  2603.           reddit.subreddit('SUBREDDIT').stylesheet.delete_mobile_header()
  2604.  
  2605.        """
  2606.         url = API_PATH["delete_sr_header"].format(subreddit=self.subreddit)
  2607.         self.subreddit._reddit.post(url)
  2608.  
  2609.     def delete_mobile_icon(self):
  2610.         """Remove the current subreddit mobile icon.
  2611.  
  2612.        Succeeds even if there is no mobile icon.
  2613.  
  2614.        For example:
  2615.  
  2616.        .. code:: python
  2617.  
  2618.           reddit.subreddit('SUBREDDIT').stylesheet.delete_mobile_icon()
  2619.  
  2620.        """
  2621.         url = API_PATH["delete_sr_icon"].format(subreddit=self.subreddit)
  2622.         self.subreddit._reddit.post(url)
  2623.  
  2624.     def update(self, stylesheet, reason=None):
  2625.         """Update the subreddit's stylesheet.
  2626.  
  2627.        :param stylesheet: The CSS for the new stylesheet.
  2628.  
  2629.        For example:
  2630.  
  2631.        .. code:: python
  2632.  
  2633.           reddit.subreddit('SUBREDDIT').stylesheet.update(
  2634.               'p { color: green; }', 'color text green')
  2635.  
  2636.        """
  2637.         data = {
  2638.             "op": "save",
  2639.             "reason": reason,
  2640.             "stylesheet_contents": stylesheet,
  2641.         }
  2642.         url = API_PATH["subreddit_stylesheet"].format(subreddit=self.subreddit)
  2643.         self.subreddit._reddit.post(url, data=data)
  2644.  
  2645.     def upload(self, name, image_path):
  2646.         """Upload an image to the Subreddit.
  2647.  
  2648.        :param name: The name to use for the image. If an image already exists
  2649.            with the same name, it will be replaced.
  2650.        :param image_path: A path to a jpeg or png image.
  2651.        :returns: A dictionary containing a link to the uploaded image under
  2652.            the key ``img_src``.
  2653.  
  2654.        Raises ``prawcore.TooLarge`` if the overall request body is too large.
  2655.  
  2656.        Raises :class:`.APIException` if there are other issues with the
  2657.        uploaded image. Unfortunately the exception info might not be very
  2658.        specific, so try through the website with the same image to see what
  2659.        the problem actually might be.
  2660.  
  2661.        For example:
  2662.  
  2663.        .. code:: python
  2664.  
  2665.           reddit.subreddit('SUBREDDIT').stylesheet.upload('smile', 'img.png')
  2666.  
  2667.        """
  2668.         return self._upload_image(
  2669.             image_path, {"name": name, "upload_type": "img"}
  2670.         )
  2671.  
  2672.     def upload_banner(self, image_path):
  2673.         """Upload an image for the subreddit's (redesign) banner image.
  2674.  
  2675.        :param image_path: A path to a jpeg or png image.
  2676.  
  2677.        Raises ``prawcore.TooLarge`` if the overall request body is too large.
  2678.  
  2679.        Raises :class:`.APIException` if there are other issues with the
  2680.        uploaded image. Unfortunately the exception info might not be very
  2681.        specific, so try through the website with the same image to see what
  2682.        the problem actually might be.
  2683.  
  2684.        For example:
  2685.  
  2686.        .. code:: python
  2687.  
  2688.           reddit.subreddit('SUBREDDIT').stylesheet.upload_banner('banner.png')
  2689.  
  2690.        """
  2691.         image_type = "bannerBackgroundImage"
  2692.         image_url = self._upload_style_asset(image_path, image_type)
  2693.         self._update_structured_styles({image_type: image_url})
  2694.  
  2695.     def upload_banner_additional_image(self, image_path, align=None):
  2696.         """Upload an image for the subreddit's (redesign) additional image.
  2697.  
  2698.        :param image_path: A path to a jpeg or png image.
  2699.        :param align: Either ``left``, ``centered``, or ``right``. (default:
  2700.            ``left``).
  2701.  
  2702.        Raises ``prawcore.TooLarge`` if the overall request body is too large.
  2703.  
  2704.        Raises :class:`.APIException` if there are other issues with the
  2705.        uploaded image. Unfortunately the exception info might not be very
  2706.        specific, so try through the website with the same image to see what
  2707.        the problem actually might be.
  2708.  
  2709.        For example:
  2710.  
  2711.        .. code:: python
  2712.  
  2713.           reddit.subreddit('SUBREDDIT').stylesheet.upload_banner_additional_image('banner.png')
  2714.  
  2715.        """
  2716.         alignment = {}
  2717.         if align is not None:
  2718.             if align not in {"left", "centered", "right"}:
  2719.                 raise ValueError(
  2720.                     "align argument must be either "
  2721.                     "`left`, `centered`, or `right`"
  2722.                 )
  2723.             alignment["bannerPositionedImagePosition"] = align
  2724.  
  2725.         image_type = "bannerPositionedImage"
  2726.         image_url = self._upload_style_asset(image_path, image_type)
  2727.         style_data = {image_type: image_url}
  2728.         if alignment:
  2729.             style_data.update(alignment)
  2730.         self._update_structured_styles(style_data)
  2731.  
  2732.     def upload_banner_hover_image(self, image_path):
  2733.         """Upload an image for the subreddit's (redesign) additional image.
  2734.  
  2735.        :param image_path: A path to a jpeg or png image.
  2736.  
  2737.        Fails if the Subreddit does not have an additional image defined
  2738.  
  2739.        Raises ``prawcore.TooLarge`` if the overall request body is too large.
  2740.  
  2741.        Raises :class:`.APIException` if there are other issues with the
  2742.        uploaded image. Unfortunately the exception info might not be very
  2743.        specific, so try through the website with the same image to see what
  2744.        the problem actually might be.
  2745.  
  2746.        For example:
  2747.  
  2748.        .. code:: python
  2749.  
  2750.           reddit.subreddit('SUBREDDIT').stylesheet.upload_banner_hover_image('banner.png')
  2751.  
  2752.        """
  2753.         image_type = "secondaryBannerPositionedImage"
  2754.         image_url = self._upload_style_asset(image_path, image_type)
  2755.         self._update_structured_styles({image_type: image_url})
  2756.  
  2757.     def upload_header(self, image_path):
  2758.         """Upload an image to be used as the Subreddit's header image.
  2759.  
  2760.        :param image_path: A path to a jpeg or png image.
  2761.        :returns: A dictionary containing a link to the uploaded image under
  2762.            the key ``img_src``.
  2763.  
  2764.        Raises ``prawcore.TooLarge`` if the overall request body is too large.
  2765.  
  2766.        Raises :class:`.APIException` if there are other issues with the
  2767.        uploaded image. Unfortunately the exception info might not be very
  2768.        specific, so try through the website with the same image to see what
  2769.        the problem actually might be.
  2770.  
  2771.        For example:
  2772.  
  2773.        .. code:: python
  2774.  
  2775.           reddit.subreddit('SUBREDDIT').stylesheet.upload_header('header.png')
  2776.  
  2777.        """
  2778.         return self._upload_image(image_path, {"upload_type": "header"})
  2779.  
  2780.     def upload_mobile_header(self, image_path):
  2781.         """Upload an image to be used as the Subreddit's mobile header.
  2782.  
  2783.        :param image_path: A path to a jpeg or png image.
  2784.        :returns: A dictionary containing a link to the uploaded image under
  2785.            the key ``img_src``.
  2786.  
  2787.        Raises ``prawcore.TooLarge`` if the overall request body is too large.
  2788.  
  2789.        Raises :class:`.APIException` if there are other issues with the
  2790.        uploaded image. Unfortunately the exception info might not be very
  2791.        specific, so try through the website with the same image to see what
  2792.        the problem actually might be.
  2793.  
  2794.        For example:
  2795.  
  2796.        .. code:: python
  2797.  
  2798.           reddit.subreddit('SUBREDDIT').stylesheet.upload_mobile_header(
  2799.               'header.png')
  2800.  
  2801.        """
  2802.         return self._upload_image(image_path, {"upload_type": "banner"})
  2803.  
  2804.     def upload_mobile_icon(self, image_path):
  2805.         """Upload an image to be used as the Subreddit's mobile icon.
  2806.  
  2807.        :param image_path: A path to a jpeg or png image.
  2808.        :returns: A dictionary containing a link to the uploaded image under
  2809.            the key ``img_src``.
  2810.  
  2811.        Raises ``prawcore.TooLarge`` if the overall request body is too large.
  2812.  
  2813.        Raises :class:`.APIException` if there are other issues with the
  2814.        uploaded image. Unfortunately the exception info might not be very
  2815.        specific, so try through the website with the same image to see what
  2816.        the problem actually might be.
  2817.  
  2818.        For example:
  2819.  
  2820.        .. code:: python
  2821.  
  2822.           reddit.subreddit('SUBREDDIT').stylesheet.upload_mobile_icon(
  2823.               'icon.png')
  2824.  
  2825.        """
  2826.         return self._upload_image(image_path, {"upload_type": "icon"})
  2827.  
  2828.  
  2829. class SubredditWiki:
  2830.     """Provides a set of wiki functions to a Subreddit."""
  2831.  
  2832.     def __getitem__(self, page_name):
  2833.         """Lazily return the WikiPage for the subreddit named ``page_name``.
  2834.  
  2835.        This method is to be used to fetch a specific wikipage, like so:
  2836.  
  2837.        .. code:: python
  2838.  
  2839.           wikipage = reddit.subreddit('iama').wiki['proof']
  2840.           print(wikipage.content_md)
  2841.  
  2842.        """
  2843.         return WikiPage(
  2844.             self.subreddit._reddit, self.subreddit, page_name.lower()
  2845.         )
  2846.  
  2847.     def __init__(self, subreddit):
  2848.         """Create a SubredditWiki instance.
  2849.  
  2850.        :param subreddit: The subreddit whose wiki to work with.
  2851.  
  2852.        """
  2853.         self.banned = SubredditRelationship(subreddit, "wikibanned")
  2854.         self.contributor = SubredditRelationship(subreddit, "wikicontributor")
  2855.         self.subreddit = subreddit
  2856.  
  2857.     def __iter__(self):
  2858.         """Iterate through the pages of the wiki.
  2859.  
  2860.        This method is to be used to discover all wikipages for a subreddit:
  2861.  
  2862.        .. code:: python
  2863.  
  2864.           for wikipage in reddit.subreddit('iama').wiki:
  2865.               print(wikipage)
  2866.  
  2867.        """
  2868.         response = self.subreddit._reddit.get(
  2869.             API_PATH["wiki_pages"].format(subreddit=self.subreddit),
  2870.             params={"unique": self.subreddit._reddit._next_unique},
  2871.         )
  2872.         for page_name in response["data"]:
  2873.             yield WikiPage(self.subreddit._reddit, self.subreddit, page_name)
  2874.  
  2875.     def create(self, name, content, reason=None, **other_settings):
  2876.         """Create a new wiki page.
  2877.  
  2878.        :param name: The name of the new WikiPage. This name will be
  2879.            normalized.
  2880.        :param content: The content of the new WikiPage.
  2881.        :param reason: (Optional) The reason for the creation.
  2882.        :param other_settings: Additional keyword arguments to pass.
  2883.  
  2884.        To create the wiki page ``'praw_test'`` in ``'/r/test'`` try:
  2885.  
  2886.        .. code:: python
  2887.  
  2888.           reddit.subreddit('test').wiki.create(
  2889.               'praw_test', 'wiki body text', reason='PRAW Test Creation')
  2890.  
  2891.        """
  2892.         name = name.replace(" ", "_").lower()
  2893.         new = WikiPage(self.subreddit._reddit, self.subreddit, name)
  2894.         new.edit(content=content, reason=reason, **other_settings)
  2895.         return new
  2896.  
  2897.     def revisions(self, **generator_kwargs):
  2898.         """Return a generator for recent wiki revisions.
  2899.  
  2900.        Additional keyword arguments are passed in the initialization of
  2901.        :class:`.ListingGenerator`.
  2902.  
  2903.        To view the wiki revisions for ``'praw_test'`` in ``'/r/test'`` try:
  2904.  
  2905.        .. code:: python
  2906.  
  2907.           for item in reddit.subreddit('test').wiki['praw_test'].revisions():
  2908.               print(item)
  2909.  
  2910.        """
  2911.         url = API_PATH["wiki_revisions"].format(subreddit=self.subreddit)
  2912.         return WikiPage._revision_generator(
  2913.             self.subreddit, url, generator_kwargs
  2914.         )
Add Comment
Please, Sign In to add comment