Initial commit
This commit is contained in:
@@ -0,0 +1,322 @@
|
||||
"""Package providing reddit class mixins."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from json import dumps
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
from ....const import API_PATH
|
||||
from ....util import _deprecate_args
|
||||
from .editable import EditableMixin
|
||||
from .fullname import FullnameMixin
|
||||
from .gildable import GildableMixin
|
||||
from .inboxable import InboxableMixin
|
||||
from .inboxtoggleable import InboxToggleableMixin
|
||||
from .messageable import MessageableMixin
|
||||
from .modnote import ModNoteMixin
|
||||
from .replyable import ReplyableMixin
|
||||
from .reportable import ReportableMixin
|
||||
from .savable import SavableMixin
|
||||
from .votable import VotableMixin
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
import praw.models
|
||||
|
||||
|
||||
class ThingModerationMixin(ModNoteMixin):
|
||||
r"""Provides moderation methods for :class:`.Comment`\ s and :class:`.Submission`\ s."""
|
||||
|
||||
REMOVAL_MESSAGE_API = None
|
||||
|
||||
def _add_removal_reason(self, *, mod_note: str = "", reason_id: str | None = None):
|
||||
"""Add a removal reason for a :class:`.Comment` or :class:`.Submission`.
|
||||
|
||||
:param mod_note: A message for the other moderators.
|
||||
:param reason_id: The removal reason ID.
|
||||
|
||||
It is necessary to first call :meth:`.remove` on the :class:`.Comment` or
|
||||
:class:`.Submission`.
|
||||
|
||||
If ``reason_id`` is not specified, ``mod_note`` cannot be blank.
|
||||
|
||||
"""
|
||||
if not reason_id and not mod_note:
|
||||
msg = "mod_note cannot be blank if reason_id is not specified"
|
||||
raise ValueError(msg)
|
||||
# Only the first element of the item_id list is used.
|
||||
data = {
|
||||
"item_ids": [self.thing.fullname],
|
||||
"mod_note": mod_note,
|
||||
"reason_id": reason_id,
|
||||
}
|
||||
self.thing._reddit.post(API_PATH["removal_reasons"], data={"json": dumps(data)})
|
||||
|
||||
def approve(self):
|
||||
"""Approve a :class:`.Comment` or :class:`.Submission`.
|
||||
|
||||
Approving a comment or submission reverts a removal, resets the report counter,
|
||||
adds a green check mark indicator (only visible to other moderators) on the
|
||||
website view, and sets the ``approved_by`` attribute to the authenticated user.
|
||||
|
||||
Example usage:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# approve a comment:
|
||||
comment = reddit.comment("dkk4qjd")
|
||||
comment.mod.approve()
|
||||
# approve a submission:
|
||||
submission = reddit.submission("5or86n")
|
||||
submission.mod.approve()
|
||||
|
||||
"""
|
||||
self.thing._reddit.post(API_PATH["approve"], data={"id": self.thing.fullname})
|
||||
|
||||
@_deprecate_args("how", "sticky")
|
||||
def distinguish(self, *, how: str = "yes", sticky: bool = False):
|
||||
"""Distinguish a :class:`.Comment` or :class:`.Submission`.
|
||||
|
||||
:param how: One of ``"yes"``, ``"no"``, ``"admin"``, or ``"special"``. ``"yes"``
|
||||
adds a moderator level distinguish. ``"no"`` removes any distinction.
|
||||
``"admin"`` and ``"special"`` require special user privileges to use
|
||||
(default ``"yes"``).
|
||||
:param sticky: :class:`.Comment` is stickied if ``True``, placing it at the top
|
||||
of the comment page regardless of score. If thing is not a top-level
|
||||
comment, this parameter is silently ignored (default ``False``).
|
||||
|
||||
Example usage:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# distinguish and sticky a comment:
|
||||
comment = reddit.comment("dkk4qjd")
|
||||
comment.mod.distinguish(sticky=True)
|
||||
# undistinguish a submission:
|
||||
submission = reddit.submission("5or86n")
|
||||
submission.mod.distinguish(how="no")
|
||||
|
||||
.. seealso::
|
||||
|
||||
:meth:`.undistinguish`
|
||||
|
||||
"""
|
||||
data = {"how": how, "id": self.thing.fullname}
|
||||
if sticky and getattr(self.thing, "is_root", False):
|
||||
data["sticky"] = True
|
||||
self.thing._reddit.post(API_PATH["distinguish"], data=data)
|
||||
|
||||
def ignore_reports(self):
|
||||
"""Ignore future reports on a :class:`.Comment` or :class:`.Submission`.
|
||||
|
||||
Calling this method will prevent future reports on this :class:`.Comment` or
|
||||
:class:`.Submission` from both triggering notifications and appearing in the
|
||||
various moderation listings. The report count will still increment on the
|
||||
:class:`.Comment` or :class:`.Submission`.
|
||||
|
||||
Example usage:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# ignore future reports on a comment:
|
||||
comment = reddit.comment("dkk4qjd")
|
||||
comment.mod.ignore_reports()
|
||||
# ignore future reports on a submission:
|
||||
submission = reddit.submission("5or86n")
|
||||
submission.mod.ignore_reports()
|
||||
|
||||
.. seealso::
|
||||
|
||||
:meth:`.unignore_reports`
|
||||
|
||||
"""
|
||||
self.thing._reddit.post(
|
||||
API_PATH["ignore_reports"], data={"id": self.thing.fullname}
|
||||
)
|
||||
|
||||
def lock(self):
|
||||
"""Lock a :class:`.Comment` or :class:`.Submission`.
|
||||
|
||||
Example usage:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# lock a comment:
|
||||
comment = reddit.comment("dkk4qjd")
|
||||
comment.mod.lock()
|
||||
# lock a submission:
|
||||
submission = reddit.submission("5or86n")
|
||||
submission.mod.lock()
|
||||
|
||||
.. seealso::
|
||||
|
||||
:meth:`.unlock`
|
||||
|
||||
"""
|
||||
self.thing._reddit.post(API_PATH["lock"], data={"id": self.thing.fullname})
|
||||
|
||||
@_deprecate_args("spam", "mod_note", "reason_id")
|
||||
def remove(
|
||||
self, *, mod_note: str = "", spam: bool = False, reason_id: str | None = None
|
||||
):
|
||||
"""Remove a :class:`.Comment` or :class:`.Submission`.
|
||||
|
||||
:param mod_note: A message for the other moderators.
|
||||
:param spam: When ``True``, use the removal to help train the
|
||||
:class:`.Subreddit`'s spam filter (default: ``False``).
|
||||
:param reason_id: The removal reason ID.
|
||||
|
||||
If either ``reason_id`` or ``mod_note`` are provided, a second API call is made
|
||||
to add the removal reason.
|
||||
|
||||
Example usage:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# remove a comment and mark as spam:
|
||||
comment = reddit.comment("dkk4qjd")
|
||||
comment.mod.remove(spam=True)
|
||||
# remove a submission
|
||||
submission = reddit.submission("5or86n")
|
||||
submission.mod.remove()
|
||||
# remove a submission with a removal reason
|
||||
reason = reddit.subreddit.mod.removal_reasons["110ni21zo23ql"]
|
||||
submission = reddit.submission("5or86n")
|
||||
submission.mod.remove(reason_id=reason.id)
|
||||
|
||||
"""
|
||||
data = {"id": self.thing.fullname, "spam": bool(spam)}
|
||||
self.thing._reddit.post(API_PATH["remove"], data=data)
|
||||
if any([reason_id, mod_note]):
|
||||
self._add_removal_reason(mod_note=mod_note, reason_id=reason_id)
|
||||
|
||||
@_deprecate_args("message", "title", "type")
|
||||
def send_removal_message(
|
||||
self,
|
||||
*,
|
||||
message: str,
|
||||
title: str = "ignored",
|
||||
type: str = "public",
|
||||
) -> praw.models.Comment | None:
|
||||
"""Send a removal message for a :class:`.Comment` or :class:`.Submission`.
|
||||
|
||||
.. warning::
|
||||
|
||||
The object has to be removed before giving it a removal reason. Remove the
|
||||
object with :meth:`.remove`. Trying to add a removal reason without removing
|
||||
the object will result in :class:`.RedditAPIException` being thrown with an
|
||||
``INVALID_ID`` error_type.
|
||||
|
||||
Reddit adds human-readable information about the object to the message.
|
||||
|
||||
:param type: One of ``"public"``, ``"public_as_subreddit"``, ``"private"``, or
|
||||
``"private_exposed"``. ``"public"`` leaves a stickied comment on the post.
|
||||
``"public_as_subreddit"`` leaves a stickied comment on the post with the
|
||||
u/subreddit-ModTeam account. ``"private"`` sends a modmail message with
|
||||
hidden username. ``"private_exposed"`` sends a modmail message without
|
||||
hidden username (default: ``"public"``).
|
||||
:param title: The short reason given in the message. Ignored if type is
|
||||
``"public"`` or ``"public_as_subreddit"``.
|
||||
:param message: The body of the message.
|
||||
|
||||
:returns: The new :class:`.Comment` if ``type`` is ``"public"`` or
|
||||
``"public_as_subreddit"``.
|
||||
|
||||
"""
|
||||
# The API endpoint used to send removal messages is different for posts and
|
||||
# comments, so the derived classes specify which one.
|
||||
if self.REMOVAL_MESSAGE_API is None:
|
||||
msg = "ThingModerationMixin must be extended."
|
||||
raise NotImplementedError(msg)
|
||||
url = API_PATH[self.REMOVAL_MESSAGE_API]
|
||||
|
||||
# Only the first element of the item_id list is used.
|
||||
data = {
|
||||
"item_id": [self.thing.fullname],
|
||||
"message": message,
|
||||
"title": title,
|
||||
"type": type,
|
||||
}
|
||||
|
||||
return self.thing._reddit.post(url, data={"json": dumps(data)}) or None
|
||||
|
||||
def undistinguish(self):
|
||||
"""Remove mod, admin, or special distinguishing from an object.
|
||||
|
||||
Also unstickies the object if applicable.
|
||||
|
||||
Example usage:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# undistinguish a comment:
|
||||
comment = reddit.comment("dkk4qjd")
|
||||
comment.mod.undistinguish()
|
||||
# undistinguish a submission:
|
||||
submission = reddit.submission("5or86n")
|
||||
submission.mod.undistinguish()
|
||||
|
||||
.. seealso::
|
||||
|
||||
:meth:`.distinguish`
|
||||
|
||||
"""
|
||||
self.distinguish(how="no")
|
||||
|
||||
def unignore_reports(self):
|
||||
"""Resume receiving future reports on a :class:`.Comment` or :class:`.Submission`.
|
||||
|
||||
Future reports on this :class:`.Comment` or :class:`.Submission` will cause
|
||||
notifications, and appear in the various moderation listings.
|
||||
|
||||
Example usage:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# accept future reports on a comment:
|
||||
comment = reddit.comment("dkk4qjd")
|
||||
comment.mod.unignore_reports()
|
||||
# accept future reports on a submission:
|
||||
submission = reddit.submission("5or86n")
|
||||
submission.mod.unignore_reports()
|
||||
|
||||
.. seealso::
|
||||
|
||||
:meth:`.ignore_reports`
|
||||
|
||||
"""
|
||||
self.thing._reddit.post(
|
||||
API_PATH["unignore_reports"], data={"id": self.thing.fullname}
|
||||
)
|
||||
|
||||
def unlock(self):
|
||||
"""Unlock a :class:`.Comment` or :class:`.Submission`.
|
||||
|
||||
Example usage:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# unlock a comment:
|
||||
comment = reddit.comment("dkk4qjd")
|
||||
comment.mod.unlock()
|
||||
# unlock a submission:
|
||||
submission = reddit.submission("5or86n")
|
||||
submission.mod.unlock()
|
||||
|
||||
.. seealso::
|
||||
|
||||
:meth:`.lock`
|
||||
|
||||
"""
|
||||
self.thing._reddit.post(API_PATH["unlock"], data={"id": self.thing.fullname})
|
||||
|
||||
|
||||
class UserContentMixin(
|
||||
EditableMixin,
|
||||
GildableMixin,
|
||||
InboxToggleableMixin,
|
||||
ReplyableMixin,
|
||||
ReportableMixin,
|
||||
SavableMixin,
|
||||
VotableMixin,
|
||||
):
|
||||
"""A convenience mixin that applies to both Comments and Submissions."""
|
||||
@@ -0,0 +1,67 @@
|
||||
"""Provide the EditableMixin class."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from ....const import API_PATH
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
import praw.models
|
||||
|
||||
|
||||
class EditableMixin:
|
||||
"""Interface for classes that can be edited and deleted."""
|
||||
|
||||
def delete(self):
|
||||
"""Delete the object.
|
||||
|
||||
Example usage:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
comment = reddit.comment("dkk4qjd")
|
||||
comment.delete()
|
||||
|
||||
submission = reddit.submission("8dmv8z")
|
||||
submission.delete()
|
||||
|
||||
"""
|
||||
self._reddit.post(API_PATH["del"], data={"id": self.fullname})
|
||||
|
||||
def edit(self, body: str) -> praw.models.Comment | praw.models.Submission:
|
||||
"""Replace the body of the object with ``body``.
|
||||
|
||||
:param body: The Markdown formatted content for the updated object.
|
||||
|
||||
:returns: The current instance after updating its attributes.
|
||||
|
||||
Example usage:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
comment = reddit.comment("dkk4qjd")
|
||||
|
||||
# construct the text of an edited comment
|
||||
# by appending to the old body:
|
||||
edited_body = comment.body + "Edit: thanks for the gold!"
|
||||
comment.edit(edited_body)
|
||||
|
||||
"""
|
||||
data = {
|
||||
"text": body,
|
||||
"thing_id": self.fullname,
|
||||
"validate_on_submit": self._reddit.validate_on_submit,
|
||||
}
|
||||
updated = self._reddit.post(API_PATH["edit"], data=data)[0]
|
||||
for attribute in [
|
||||
"_fetched",
|
||||
"_reddit",
|
||||
"_submission",
|
||||
"replies",
|
||||
"subreddit",
|
||||
]:
|
||||
if attribute in updated.__dict__:
|
||||
delattr(updated, attribute)
|
||||
self.__dict__.update(updated.__dict__)
|
||||
return self
|
||||
@@ -0,0 +1,19 @@
|
||||
"""Provide the FullnameMixin class."""
|
||||
|
||||
|
||||
class FullnameMixin:
|
||||
"""Interface for classes that have a fullname."""
|
||||
|
||||
_kind = None
|
||||
|
||||
@property
|
||||
def fullname(self) -> str:
|
||||
"""Return the object's fullname.
|
||||
|
||||
A fullname is an object's kind mapping like ``t3`` followed by an underscore and
|
||||
the object's base36 ID, e.g., ``t1_c5s96e0``.
|
||||
|
||||
"""
|
||||
if "_" in self.id:
|
||||
return self.id
|
||||
return f"{self._kind}_{self.id}"
|
||||
@@ -0,0 +1,118 @@
|
||||
"""Provide the GildableMixin class."""
|
||||
|
||||
from warnings import warn
|
||||
|
||||
from ....const import API_PATH
|
||||
from ....util import _deprecate_args
|
||||
|
||||
|
||||
class GildableMixin:
|
||||
"""Interface for classes that can be gilded."""
|
||||
|
||||
@_deprecate_args("gild_type", "is_anonymous", "message")
|
||||
def award(
|
||||
self,
|
||||
*,
|
||||
gild_type: str = "gid_2",
|
||||
is_anonymous: bool = True,
|
||||
message: str = None,
|
||||
) -> dict:
|
||||
"""Award the author of the item.
|
||||
|
||||
:param gild_type: Type of award to give. See table below for currently know
|
||||
global award types.
|
||||
:param is_anonymous: If ``True``, the authenticated user's username will not be
|
||||
revealed to the recipient.
|
||||
:param message: Message to include with the award.
|
||||
|
||||
:returns: A dict containing info similar to what is shown below:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
{
|
||||
"subreddit_balance": 85260,
|
||||
"treatment_tags": [],
|
||||
"coins": 8760,
|
||||
"gildings": {"gid_1": 0, "gid_2": 1, "gid_3": 0},
|
||||
"awarder_karma_received": 4,
|
||||
"all_awardings": [
|
||||
{
|
||||
"giver_coin_reward": 0,
|
||||
"subreddit_id": None,
|
||||
"is_new": False,
|
||||
"days_of_drip_extension": 0,
|
||||
"coin_price": 75,
|
||||
"id": "award_9663243a-e77f-44cf-abc6-850ead2cd18d",
|
||||
"penny_donate": 0,
|
||||
"coin_reward": 0,
|
||||
"icon_url": "https://www.redditstatic.com/gold/awards/icon/SnooClappingPremium_512.png",
|
||||
"days_of_premium": 0,
|
||||
"icon_height": 512,
|
||||
"tiers_by_required_awardings": None,
|
||||
"icon_width": 512,
|
||||
"static_icon_width": 512,
|
||||
"start_date": None,
|
||||
"is_enabled": True,
|
||||
"awardings_required_to_grant_benefits": None,
|
||||
"description": "For an especially amazing showing.",
|
||||
"end_date": None,
|
||||
"subreddit_coin_reward": 0,
|
||||
"count": 1,
|
||||
"static_icon_height": 512,
|
||||
"name": "Bravo Grande!",
|
||||
"icon_format": "APNG",
|
||||
"award_sub_type": "PREMIUM",
|
||||
"penny_price": 0,
|
||||
"award_type": "global",
|
||||
"static_icon_url": "https://i.redd.it/award_images/t5_q0gj4/59e02tmkl4451_BravoGrande-Static.png",
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
.. warning::
|
||||
|
||||
Requires the authenticated user to own Reddit Coins. Calling this method
|
||||
will consume Reddit Coins.
|
||||
|
||||
To award the gold award anonymously do:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
comment = reddit.comment("dkk4qjd")
|
||||
comment.award()
|
||||
|
||||
submission = reddit.submission("8dmv8z")
|
||||
submission.award()
|
||||
|
||||
To award the platinum award with the message 'Nice!' and reveal your username to
|
||||
the recipient do:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
comment = reddit.comment("dkk4qjd")
|
||||
comment.award(gild_type="gild_3", message="Nice!", is_anonymous=False)
|
||||
|
||||
submission = reddit.submission("8dmv8z")
|
||||
submission.award(gild_type="gild_3", message="Nice!", is_anonymous=False)
|
||||
|
||||
.. include:: awards.txt
|
||||
|
||||
"""
|
||||
params = {
|
||||
"api_type": "json",
|
||||
"gild_type": gild_type,
|
||||
"is_anonymous": is_anonymous,
|
||||
"thing_id": self.fullname,
|
||||
"message": message,
|
||||
}
|
||||
return self._reddit.post(API_PATH["award_thing"], params=params)
|
||||
|
||||
def gild(self) -> dict:
|
||||
"""Alias for :meth:`.award` to maintain backwards compatibility."""
|
||||
warn(
|
||||
"'.gild' has been renamed to '.award'.",
|
||||
category=DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return self.award()
|
||||
@@ -0,0 +1,153 @@
|
||||
"""Provide the InboxableMixin class."""
|
||||
|
||||
from ....const import API_PATH
|
||||
|
||||
|
||||
class InboxableMixin:
|
||||
"""Interface for :class:`.RedditBase` subclasses that originate from the inbox."""
|
||||
|
||||
def block(self):
|
||||
"""Block the user who sent the item.
|
||||
|
||||
.. note::
|
||||
|
||||
This method pertains only to objects which were retrieved via the inbox.
|
||||
|
||||
Example usage:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
comment = reddit.comment("dkk4qjd")
|
||||
comment.block()
|
||||
|
||||
# or, identically:
|
||||
|
||||
comment.author.block()
|
||||
|
||||
"""
|
||||
self._reddit.post(API_PATH["block"], data={"id": self.fullname})
|
||||
|
||||
def collapse(self):
|
||||
"""Mark the item as collapsed.
|
||||
|
||||
.. note::
|
||||
|
||||
This method pertains only to objects which were retrieved via the inbox.
|
||||
|
||||
Example usage:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
inbox = reddit.inbox()
|
||||
|
||||
# select first inbox item and collapse it message = next(inbox)
|
||||
message.collapse()
|
||||
|
||||
.. seealso::
|
||||
|
||||
:meth:`.uncollapse`
|
||||
|
||||
"""
|
||||
self._reddit.inbox.collapse([self])
|
||||
|
||||
def mark_read(self):
|
||||
"""Mark a single inbox item as read.
|
||||
|
||||
.. note::
|
||||
|
||||
This method pertains only to objects which were retrieved via the inbox.
|
||||
|
||||
Example usage:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
inbox = reddit.inbox.unread()
|
||||
|
||||
for message in inbox:
|
||||
# process unread messages
|
||||
...
|
||||
|
||||
.. seealso::
|
||||
|
||||
:meth:`.mark_unread`
|
||||
|
||||
To mark the whole inbox as read with a single network request, use
|
||||
:meth:`.Inbox.mark_all_read`
|
||||
|
||||
"""
|
||||
self._reddit.inbox.mark_read([self])
|
||||
|
||||
def mark_unread(self):
|
||||
"""Mark the item as unread.
|
||||
|
||||
.. note::
|
||||
|
||||
This method pertains only to objects which were retrieved via the inbox.
|
||||
|
||||
Example usage:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
inbox = reddit.inbox(limit=10)
|
||||
|
||||
for message in inbox:
|
||||
# process messages
|
||||
...
|
||||
|
||||
.. seealso::
|
||||
|
||||
:meth:`.mark_read`
|
||||
|
||||
"""
|
||||
self._reddit.inbox.mark_unread([self])
|
||||
|
||||
def unblock_subreddit(self):
|
||||
"""Unblock a subreddit.
|
||||
|
||||
.. note::
|
||||
|
||||
This method pertains only to objects which were retrieved via the inbox.
|
||||
|
||||
For example, to unblock all blocked subreddits that you can find by going
|
||||
through your inbox:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from praw.models import SubredditMessage
|
||||
|
||||
subs = set()
|
||||
for item in reddit.inbox.messages(limit=None):
|
||||
if isinstance(item, SubredditMessage):
|
||||
if (
|
||||
item.subject == "[message from blocked subreddit]"
|
||||
and str(item.subreddit) not in subs
|
||||
):
|
||||
item.unblock_subreddit()
|
||||
subs.add(str(item.subreddit))
|
||||
|
||||
"""
|
||||
self._reddit.post(API_PATH["unblock_subreddit"], data={"id": self.fullname})
|
||||
|
||||
def uncollapse(self):
|
||||
"""Mark the item as uncollapsed.
|
||||
|
||||
.. note::
|
||||
|
||||
This method pertains only to objects which were retrieved via the inbox.
|
||||
|
||||
Example usage:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
inbox = reddit.inbox()
|
||||
|
||||
# select first inbox item and uncollapse it
|
||||
message = next(inbox)
|
||||
message.uncollapse()
|
||||
|
||||
.. seealso::
|
||||
|
||||
:meth:`.collapse`
|
||||
|
||||
"""
|
||||
self._reddit.inbox.uncollapse([self])
|
||||
@@ -0,0 +1,59 @@
|
||||
"""Provide the InboxToggleableMixin class."""
|
||||
|
||||
from ....const import API_PATH
|
||||
|
||||
|
||||
class InboxToggleableMixin:
|
||||
"""Interface for classes that can optionally receive inbox replies."""
|
||||
|
||||
def disable_inbox_replies(self):
|
||||
"""Disable inbox replies for the item.
|
||||
|
||||
.. note::
|
||||
|
||||
This can only apply to items created by the authenticated user.
|
||||
|
||||
Example usage:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
comment = reddit.comment("dkk4qjd")
|
||||
comment.disable_inbox_replies()
|
||||
|
||||
submission = reddit.submission("8dmv8z")
|
||||
submission.disable_inbox_replies()
|
||||
|
||||
.. seealso::
|
||||
|
||||
:meth:`.enable_inbox_replies`
|
||||
|
||||
"""
|
||||
self._reddit.post(
|
||||
API_PATH["sendreplies"], data={"id": self.fullname, "state": False}
|
||||
)
|
||||
|
||||
def enable_inbox_replies(self):
|
||||
"""Enable inbox replies for the item.
|
||||
|
||||
.. note::
|
||||
|
||||
This can only apply to items created by the authenticated user.
|
||||
|
||||
Example usage:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
comment = reddit.comment("dkk4qjd")
|
||||
comment.enable_inbox_replies()
|
||||
|
||||
submission = reddit.submission("8dmv8z")
|
||||
submission.enable_inbox_replies()
|
||||
|
||||
.. seealso::
|
||||
|
||||
:meth:`.disable_inbox_replies`
|
||||
|
||||
"""
|
||||
self._reddit.post(
|
||||
API_PATH["sendreplies"], data={"id": self.fullname, "state": True}
|
||||
)
|
||||
@@ -0,0 +1,67 @@
|
||||
"""Provide the MessageableMixin class."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from ....const import API_PATH
|
||||
from ....util import _deprecate_args
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
import praw
|
||||
|
||||
|
||||
class MessageableMixin:
|
||||
"""Interface for classes that can be messaged."""
|
||||
|
||||
@_deprecate_args("subject", "message", "from_subreddit")
|
||||
def message(
|
||||
self,
|
||||
*,
|
||||
from_subreddit: praw.models.Subreddit | str | None = None,
|
||||
message: str,
|
||||
subject: str,
|
||||
):
|
||||
"""Send a message to a :class:`.Redditor` or a :class:`.Subreddit`'s moderators (modmail).
|
||||
|
||||
:param from_subreddit: A :class:`.Subreddit` instance or string to send the
|
||||
message from. When provided, messages are sent from the subreddit rather
|
||||
than from the authenticated user.
|
||||
|
||||
.. note::
|
||||
|
||||
The authenticated user must be a moderator of the subreddit and have the
|
||||
``mail`` moderator permission.
|
||||
|
||||
:param message: The message content.
|
||||
:param subject: The subject of the message.
|
||||
|
||||
For example, to send a private message to u/spez, try:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
reddit.redditor("spez").message(subject="TEST", message="test message from PRAW")
|
||||
|
||||
To send a message to u/spez from the moderators of r/test try:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
reddit.redditor("spez").message(
|
||||
subject="TEST", message="test message from r/test", from_subreddit="test"
|
||||
)
|
||||
|
||||
To send a message to the moderators of r/test, try:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
reddit.subreddit("test").message(subject="TEST", message="test PM from PRAW")
|
||||
|
||||
"""
|
||||
data = {
|
||||
"subject": subject,
|
||||
"text": message,
|
||||
"to": f"{getattr(self.__class__, 'MESSAGE_PREFIX', '')}{self}",
|
||||
}
|
||||
if from_subreddit:
|
||||
data["from_sr"] = str(from_subreddit)
|
||||
self._reddit.post(API_PATH["compose"], data=data)
|
||||
@@ -0,0 +1,61 @@
|
||||
"""Provide the ModNoteMixin class."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Any, Generator
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
import praw.models
|
||||
|
||||
|
||||
class ModNoteMixin:
|
||||
"""Interface for classes that can have a moderator note set on them."""
|
||||
|
||||
def author_notes(
|
||||
self, **generator_kwargs: Any
|
||||
) -> Generator[praw.models.ModNote, None, None]:
|
||||
"""Get the moderator notes for the author of this object in the subreddit it's posted in.
|
||||
|
||||
:param generator_kwargs: Additional keyword arguments are passed in the
|
||||
initialization of the moderator note generator.
|
||||
|
||||
:returns: A generator of :class:`.ModNote`.
|
||||
|
||||
For example, to list all notes the author of a submission, try:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
for note in reddit.submission("92dd8").mod.author_notes():
|
||||
print(f"{note.label}: {note.note}")
|
||||
|
||||
"""
|
||||
return self.thing.subreddit.mod.notes.redditors(
|
||||
self.thing.author, **generator_kwargs
|
||||
)
|
||||
|
||||
def create_note(
|
||||
self, *, label: str | None = None, note: str, **other_settings: Any
|
||||
) -> praw.models.ModNote:
|
||||
"""Create a moderator note on the author of this object in the subreddit it's posted in.
|
||||
|
||||
:param label: The label for the note. As of this writing, this can be one of the
|
||||
following: ``"ABUSE_WARNING"``, ``"BAN"``, ``"BOT_BAN"``,
|
||||
``"HELPFUL_USER"``, ``"PERMA_BAN"``, ``"SOLID_CONTRIBUTOR"``,
|
||||
``"SPAM_WARNING"``, ``"SPAM_WATCH"``, or ``None`` (default: ``None``).
|
||||
:param note: The content of the note. As of this writing, this is limited to 250
|
||||
characters.
|
||||
:param other_settings: Additional keyword arguments are passed to
|
||||
:meth:`~.BaseModNotes.create`.
|
||||
|
||||
:returns: The new :class:`.ModNote` object.
|
||||
|
||||
For example, to create a note on a :class:`.Submission`, try:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
reddit.submission("92dd8").mod.create_note(label="HELPFUL_USER", note="Test note")
|
||||
|
||||
"""
|
||||
return self.thing.subreddit.mod.notes.create(
|
||||
label=label, note=note, thing=self.thing, **other_settings
|
||||
)
|
||||
@@ -0,0 +1,48 @@
|
||||
"""Provide the ReplyableMixin class."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from ....const import API_PATH
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
import praw.models
|
||||
|
||||
|
||||
class ReplyableMixin:
|
||||
"""Interface for :class:`.RedditBase` classes that can be replied to."""
|
||||
|
||||
def reply(self, body: str) -> praw.models.Comment | praw.models.Message | None:
|
||||
"""Reply to the object.
|
||||
|
||||
:param body: The Markdown formatted content for a comment.
|
||||
|
||||
:returns: A :class:`.Comment` or :class:`.Message` object for the newly created
|
||||
comment or message or ``None`` if Reddit doesn't provide one.
|
||||
|
||||
:raises: ``prawcore.exceptions.Forbidden`` when attempting to reply to some
|
||||
items, such as locked submissions/comments or non-replyable messages.
|
||||
|
||||
A ``None`` value can be returned if the target is a comment or submission in a
|
||||
quarantined subreddit and the authenticated user has not opt-ed into viewing the
|
||||
content. When this happens the comment will be successfully created on Reddit
|
||||
and can be retried by drawing the comment from the user's comment history.
|
||||
|
||||
Example usage:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
submission = reddit.submission("5or86n")
|
||||
submission.reply("reply")
|
||||
|
||||
comment = reddit.comment("dxolpyc")
|
||||
comment.reply("reply")
|
||||
|
||||
"""
|
||||
data = {"text": body, "thing_id": self.fullname}
|
||||
comments = self._reddit.post(API_PATH["comment"], data=data)
|
||||
try:
|
||||
return comments[0]
|
||||
except IndexError:
|
||||
return None
|
||||
@@ -0,0 +1,30 @@
|
||||
"""Provide the ReportableMixin class."""
|
||||
|
||||
from ....const import API_PATH
|
||||
|
||||
|
||||
class ReportableMixin:
|
||||
"""Interface for :class:`.RedditBase` classes that can be reported."""
|
||||
|
||||
def report(self, reason: str):
|
||||
"""Report this object to the moderators of its subreddit.
|
||||
|
||||
:param reason: The reason for reporting.
|
||||
|
||||
:raises: :class:`.RedditAPIException` if ``reason`` is longer than 100
|
||||
characters.
|
||||
|
||||
Example usage:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
submission = reddit.submission("5or86n")
|
||||
submission.report("report reason")
|
||||
|
||||
comment = reddit.comment("dxolpyc")
|
||||
comment.report("report reason")
|
||||
|
||||
"""
|
||||
self._reddit.post(
|
||||
API_PATH["report"], data={"id": self.fullname, "reason": reason}
|
||||
)
|
||||
@@ -0,0 +1,56 @@
|
||||
"""Provide the SavableMixin class."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from ....const import API_PATH
|
||||
from ....util import _deprecate_args
|
||||
|
||||
|
||||
class SavableMixin:
|
||||
"""Interface for :class:`.RedditBase` classes that can be saved."""
|
||||
|
||||
@_deprecate_args("category")
|
||||
def save(self, *, category: str | None = None):
|
||||
"""Save the object.
|
||||
|
||||
:param category: The category to save to. If the authenticated user does not
|
||||
have Reddit Premium this value is ignored by Reddit (default: ``None``).
|
||||
|
||||
Example usage:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
submission = reddit.submission("5or86n")
|
||||
submission.save(category="view later")
|
||||
|
||||
comment = reddit.comment("dxolpyc")
|
||||
comment.save()
|
||||
|
||||
.. seealso::
|
||||
|
||||
:meth:`.unsave`
|
||||
|
||||
"""
|
||||
self._reddit.post(
|
||||
API_PATH["save"], data={"category": category, "id": self.fullname}
|
||||
)
|
||||
|
||||
def unsave(self):
|
||||
"""Unsave the object.
|
||||
|
||||
Example usage:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
submission = reddit.submission("5or86n")
|
||||
submission.unsave()
|
||||
|
||||
comment = reddit.comment("dxolpyc")
|
||||
comment.unsave()
|
||||
|
||||
.. seealso::
|
||||
|
||||
:meth:`.save`
|
||||
|
||||
"""
|
||||
self._reddit.post(API_PATH["unsave"], data={"id": self.fullname})
|
||||
@@ -0,0 +1,94 @@
|
||||
"""Provide the VotableMixin class."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from ....const import API_PATH
|
||||
|
||||
|
||||
class VotableMixin:
|
||||
"""Interface for :class:`.RedditBase` classes that can be voted on."""
|
||||
|
||||
def _vote(self, direction: int):
|
||||
self._reddit.post(
|
||||
API_PATH["vote"], data={"dir": str(direction), "id": self.fullname}
|
||||
)
|
||||
|
||||
def clear_vote(self):
|
||||
"""Clear the authenticated user's vote on the object.
|
||||
|
||||
.. note::
|
||||
|
||||
Votes must be cast by humans. That is, API clients proxying a human's action
|
||||
one-for-one are OK, but bots deciding how to vote on content or amplifying a
|
||||
human's vote are not. See the reddit rules for more details on what
|
||||
constitutes vote manipulation. [`Ref
|
||||
<https://www.reddit.com/dev/api#POST_api_vote>`_]
|
||||
|
||||
Example usage:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
submission = reddit.submission("5or86n")
|
||||
submission.clear_vote()
|
||||
|
||||
comment = reddit.comment("dxolpyc")
|
||||
comment.clear_vote()
|
||||
|
||||
"""
|
||||
self._vote(direction=0)
|
||||
|
||||
def downvote(self):
|
||||
"""Downvote the object.
|
||||
|
||||
.. note::
|
||||
|
||||
Votes must be cast by humans. That is, API clients proxying a human's action
|
||||
one-for-one are OK, but bots deciding how to vote on content or amplifying a
|
||||
human's vote are not. See the reddit rules for more details on what
|
||||
constitutes vote manipulation. [`Ref
|
||||
<https://www.reddit.com/dev/api#POST_api_vote>`_]
|
||||
|
||||
Example usage:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
submission = reddit.submission("5or86n")
|
||||
submission.downvote()
|
||||
|
||||
comment = reddit.comment("dxolpyc")
|
||||
comment.downvote()
|
||||
|
||||
.. seealso::
|
||||
|
||||
:meth:`.upvote`
|
||||
|
||||
"""
|
||||
self._vote(direction=-1)
|
||||
|
||||
def upvote(self):
|
||||
"""Upvote the object.
|
||||
|
||||
.. note::
|
||||
|
||||
Votes must be cast by humans. That is, API clients proxying a human's action
|
||||
one-for-one are OK, but bots deciding how to vote on content or amplifying a
|
||||
human's vote are not. See the reddit rules for more details on what
|
||||
constitutes vote manipulation. [`Ref
|
||||
<https://www.reddit.com/dev/api#POST_api_vote>`_]
|
||||
|
||||
Example usage:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
submission = reddit.submission("5or86n")
|
||||
submission.upvote()
|
||||
|
||||
comment = reddit.comment("dxolpyc")
|
||||
comment.upvote()
|
||||
|
||||
.. seealso::
|
||||
|
||||
:meth:`.downvote`
|
||||
|
||||
"""
|
||||
self._vote(direction=1)
|
||||
Reference in New Issue
Block a user