Skip to content

Commit

Permalink
Merge pull request #1856 from Watchful1/mod_notes
Browse files Browse the repository at this point in the history
Mod notes
  • Loading branch information
LilSpazJoekp committed May 10, 2022
2 parents 275e722 + b22e1f5 commit 7363ca1
Show file tree
Hide file tree
Showing 50 changed files with 10,319 additions and 10 deletions.
20 changes: 20 additions & 0 deletions CHANGES.rst
Expand Up @@ -14,6 +14,26 @@ Unreleased
- :meth:`.SubredditCollectionsModeration.create` keyword argument ``display_layout`` for
specifying a display layout when creating a :class:`.Collection`.
- :attr:`~.Message.parent` to get the parent of a :class:`.Message`.
- :class:`.ModNote` to represent a moderator note.
- :meth:`.ModNote.delete` to delete a single moderator note.
- :class:`.RedditModNotes` to interact with moderator notes from a :class:`.Reddit`
instance. This provides the ability to create and fetch notes for one or more
redditors from one or more subreddits.
- :class:`.RedditorModNotes` to interact with moderator notes from a :class:`.Redditor`
instance.
- :meth:`.RedditorModNotes.subreddits` to obtain moderator notes from multiple
subreddits for a single redditor.
- :class:`.SubredditModNotes` to interact with moderator notes from a
:class:`.Subreddit` instance.
- :meth:`.SubredditModNotes.redditors` to obtain moderator notes for multiple redditors
from a single subreddit.
- :meth:`~.BaseModNotes.create` to create a moderator note.
- :attr:`.Redditor.notes` to interact with :class:`.RedditorModNotes`.
- :attr:`.SubredditModeration.notes` to interact with :class:`.SubredditModNotes`.
- :meth:`~.ModNoteMixin.create_note` create a moderator note from a :class:`.Comment` or
:class:`.Submission`.
- :meth:`~.ModNoteMixin.author_notes` to view the moderator notes for the author of a
:class:`.Comment` or :class:`.Submission`.

**Changed**

Expand Down
12 changes: 12 additions & 0 deletions docs/code_overview/other.rst
Expand Up @@ -33,6 +33,17 @@ them bound to an attribute of one of the PRAW models.
other/inlinemedia
other/inlinevideo

.. toctree::
:maxdepth: 2
:caption: ModNotes

other/base_mod_notes
other/mod_note
other/mod_note_mixin
other/reddit_mod_notes
other/redditor_mod_notes
other/subreddit_mod_notes

.. toctree::
:maxdepth: 2
:caption: Moderation Helpers
Expand Down Expand Up @@ -121,6 +132,7 @@ them bound to an attribute of one of the PRAW models.
other/inboxablemixin
other/listinggenerator
other/mod_action
other/mod_note
other/moderatedlist
other/modmail
other/modmailmessage
Expand Down
5 changes: 5 additions & 0 deletions docs/code_overview/other/base_mod_notes.rst
@@ -0,0 +1,5 @@
BaseModNotes
============

.. autoclass:: praw.models.mod_notes.BaseModNotes
:inherited-members:
5 changes: 5 additions & 0 deletions docs/code_overview/other/mod_note.rst
@@ -0,0 +1,5 @@
ModNote
=======

.. autoclass:: praw.models.ModNote
:inherited-members:
5 changes: 5 additions & 0 deletions docs/code_overview/other/mod_note_mixin.rst
@@ -0,0 +1,5 @@
ModNoteMixin
============

.. autoclass:: praw.models.reddit.mixins.ModNoteMixin
:members:
5 changes: 5 additions & 0 deletions docs/code_overview/other/reddit_mod_notes.rst
@@ -0,0 +1,5 @@
RedditModNotes
==============

.. autoclass:: praw.models.RedditModNotes
:inherited-members:
5 changes: 5 additions & 0 deletions docs/code_overview/other/redditor_mod_notes.rst
@@ -0,0 +1,5 @@
RedditorModNotes
================

.. autoclass:: praw.models.RedditorModNotes
:inherited-members:
5 changes: 5 additions & 0 deletions docs/code_overview/other/subreddit_mod_notes.rst
@@ -0,0 +1,5 @@
SubredditModNotes
=================

.. autoclass:: praw.models.SubredditModNotes
:inherited-members:
2 changes: 2 additions & 0 deletions praw/endpoints.py
Expand Up @@ -108,6 +108,8 @@
"mentions": "message/mentions",
"message": "message/messages/{id}/",
"messages": "message/messages/",
"mod_notes": "api/mod/notes",
"mod_notes_bulk": "api/mod/notes/recent",
"moderated": "user/{user}/moderated_subreddits/",
"moderator_messages": "r/{subreddit}/message/moderator/",
"moderator_unread": "r/{subreddit}/message/moderator/unread/",
Expand Down
2 changes: 2 additions & 0 deletions praw/models/__init__.py
Expand Up @@ -11,6 +11,8 @@
from .listing.generator import ListingGenerator
from .listing.listing import Listing, ModeratorListing, ModmailConversationsListing
from .mod_action import ModAction
from .mod_note import ModNote
from .mod_notes import RedditModNotes, RedditorModNotes, SubredditModNotes
from .preferences import Preferences
from .reddit.collections import Collection
from .reddit.comment import Comment
Expand Down
23 changes: 18 additions & 5 deletions praw/models/listing/generator.py
Expand Up @@ -3,7 +3,7 @@
from typing import TYPE_CHECKING, Any, Dict, Iterator, Optional, Union

from ..base import PRAWBase
from .listing import FlairListing
from .listing import FlairListing, ModNoteListing

if TYPE_CHECKING: # pragma: no cover
import praw
Expand Down Expand Up @@ -66,15 +66,28 @@ def __next__(self) -> Any:
self.yielded += 1
return self._listing[self._list_index - 1]

def _extract_sublist(self, listing):
if isinstance(listing, list):
return listing[1] # for submission duplicates
elif isinstance(listing, dict):
classes = [FlairListing, ModNoteListing]

for listing_type in classes:
if listing_type.CHILD_ATTRIBUTE in listing:
return listing_type(self._reddit, listing)
else:
raise ValueError(
"The generator returned a dictionary PRAW didn't recognize."
" File a bug report at PRAW."
)
return listing

def _next_batch(self):
if self._exhausted:
raise StopIteration()

self._listing = self._reddit.get(self.url, params=self.params)
if isinstance(self._listing, list):
self._listing = self._listing[1] # for submission duplicates
elif isinstance(self._listing, dict):
self._listing = FlairListing(self._reddit, self._listing)
self._listing = self._extract_sublist(self._listing)
self._list_index = 0

if not self._listing:
Expand Down
13 changes: 13 additions & 0 deletions praw/models/listing/listing.py
Expand Up @@ -41,6 +41,19 @@ class ModeratorListing(Listing):
CHILD_ATTRIBUTE = "moderators"


class ModNoteListing(Listing):
"""Special Listing for handling :class:`.ModNote` lists."""

CHILD_ATTRIBUTE = "mod_notes"

@property
def after(self) -> Optional[Any]:
"""Return the next attribute or None."""
if not getattr(self, "has_next_page", True):
return None
return getattr(self, "end_cursor", None)


class ModmailConversationsListing(Listing):
"""Special Listing for handling :class:`.ModmailConversation` lists."""

Expand Down
70 changes: 70 additions & 0 deletions praw/models/mod_note.py
@@ -0,0 +1,70 @@
"""Provide the ModNote class."""
from praw.endpoints import API_PATH

from .base import PRAWBase


class ModNote(PRAWBase):
"""Represent a moderator note.
.. include:: ../../typical_attributes.rst
=============== ====================================================================
Attribute Description
=============== ====================================================================
``action`` If this note represents a moderator action, this field indicates the
type of action. For example, ``"banuser"`` if the action was banning
a user.
``created_at`` Time the moderator note was created, represented in `Unix Time`_.
``description`` If this note represents a moderator action, this field indicates the
description of the action. For example, if the action was banning
the user, this is the ban reason.
``details`` If this note represents a moderator action, this field indicates the
details of the action. For example, if the action was banning the
user, this is the duration of the ban.
``id`` The ID of the moderator note.
``label`` The label applied to the note, currently one of:
``"ABUSE_WARNING"``, ``"BAN"``, ``"BOT_BAN"``, ``"HELPFUL_USER"``,
``"PERMA_BAN"``, ``"SOLID_CONTRIBUTOR"``, ``"SPAM_WARNING"``,
``"SPAM_WATCH"``, or ``None``.
``moderator`` The moderator who created the note.
``note`` The text of the note.
``reddit_id`` The fullname of the object this note is attributed to, or ``None``
if not set. If this note represents a moderators action, this is the
fullname of the object the action was performed on.
``subreddit`` The subreddit this note belongs to.
``type`` The type of note, currently one of: ``"APPROVAL"``, ``"BAN"``,
``"CONTENT_CHANGE"``, ``"INVITE"``, ``"MUTE"``, ``"NOTE"``,
``"REMOVAL"``, or ``"SPAM"``.
``user`` The redditor the note is for.
=============== ====================================================================
.. _unix time: https://en.wikipedia.org/wiki/Unix_time
"""

def __eq__(self, other):
"""Return whether the other instance equals the current."""
if isinstance(other, self.__class__):
return self.id == other.id
if isinstance(other, str):
return self.id == other
return super().__eq__(other)

def delete(self):
"""Delete this note.
For example, to delete the last note for u/spez from r/test, try:
.. code-block:: python
for note in reddit.subreddit("test").mod.notes("spez"):
note.delete()
"""
params = {
"user": str(self.user),
"subreddit": str(self.subreddit),
"note_id": self.id,
}
self._reddit.delete(API_PATH["mod_notes"], params=params)

0 comments on commit 7363ca1

Please sign in to comment.