Skip to content

Commit

Permalink
Add support for mod notes
Browse files Browse the repository at this point in the history
Co-authored-by: Joel Payne <15524072+LilSpazJoekp@users.noreply.github.com>

(cherry picked from commit praw-dev/praw@b22e1f5)
  • Loading branch information
Watchful1 authored and LilSpazJoekp committed Oct 23, 2022
1 parent dfbfb55 commit b1df01b
Show file tree
Hide file tree
Showing 42 changed files with 5,072 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
2 changes: 2 additions & 0 deletions asyncpraw/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 asyncpraw/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 asyncpraw/models/listing/generator.py
Expand Up @@ -3,7 +3,7 @@
from typing import TYPE_CHECKING, Any, AsyncIterator, Dict, Optional, Union

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

if TYPE_CHECKING: # pragma: no cover
import asyncpraw
Expand Down Expand Up @@ -66,15 +66,28 @@ async def __anext__(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 Async PRAW didn't"
" recognize. File a bug report at Async PRAW."
)
return listing

async def _next_batch(self):
if self._exhausted:
raise StopAsyncIteration()

self._listing = await 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 asyncpraw/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 asyncpraw/models/mod_note.py
@@ -0,0 +1,70 @@
"""Provide the ModNote class."""

from ..endpoints import API_PATH
from .base import AsyncPRAWBase


class ModNote(AsyncPRAWBase):
"""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)

async 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,
}
await self._reddit.delete(API_PATH["mod_notes"], params=params)

0 comments on commit b1df01b

Please sign in to comment.