345 lines
12 KiB
Python
345 lines
12 KiB
Python
"""Provide models for new modmail."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING, Any
|
|
|
|
from ...const import API_PATH
|
|
from ...util import _deprecate_args, snake_case_keys
|
|
from .base import RedditBase
|
|
|
|
if TYPE_CHECKING: # pragma: no cover
|
|
import praw
|
|
|
|
|
|
class ModmailObject(RedditBase):
|
|
"""A base class for objects within a modmail conversation."""
|
|
|
|
AUTHOR_ATTRIBUTE = "author"
|
|
STR_FIELD = "id"
|
|
|
|
def __setattr__(self, attribute: str, value: Any):
|
|
"""Objectify the AUTHOR_ATTRIBUTE attribute."""
|
|
if attribute == self.AUTHOR_ATTRIBUTE:
|
|
value = self._reddit._objector.objectify(value)
|
|
super().__setattr__(attribute, value)
|
|
|
|
|
|
class ModmailConversation(RedditBase):
|
|
"""A class for modmail conversations.
|
|
|
|
.. include:: ../../typical_attributes.rst
|
|
|
|
==================== ===============================================================
|
|
Attribute Description
|
|
==================== ===============================================================
|
|
``authors`` Provides an ordered list of :class:`.Redditor` instances. The
|
|
authors of each message in the modmail conversation.
|
|
``id`` The ID of the :class:`.ModmailConversation`.
|
|
``is_highlighted`` Whether or not the :class:`.ModmailConversation` is
|
|
highlighted.
|
|
``is_internal`` Whether or not the :class:`.ModmailConversation` is a private
|
|
mod conversation.
|
|
``last_mod_update`` Time of the last mod message reply, represented in the `ISO
|
|
8601`_ standard with timezone.
|
|
``last_updated`` Time of the last message reply, represented in the `ISO 8601`_
|
|
standard with timezone.
|
|
``last_user_update`` Time of the last user message reply, represented in the `ISO
|
|
8601`_ standard with timezone.
|
|
``num_messages`` The number of messages in the :class:`.ModmailConversation`.
|
|
``obj_ids`` Provides a list of dictionaries representing mod actions on the
|
|
:class:`.ModmailConversation`. Each dict contains attributes of
|
|
``"key"`` and ``"id"``. The key can be either ``""messages"``
|
|
or ``"ModAction"``. ``"ModAction"`` represents
|
|
archiving/highlighting etc.
|
|
``owner`` Provides an instance of :class:`.Subreddit`. The subreddit that
|
|
the :class:`.ModmailConversation` belongs to.
|
|
``participant`` Provides an instance of :class:`.Redditor`. The participating
|
|
user in the :class:`.ModmailConversation`.
|
|
``subject`` The subject of the :class:`.ModmailConversation`.
|
|
==================== ===============================================================
|
|
|
|
.. _iso 8601: https://en.wikipedia.org/wiki/ISO_8601
|
|
|
|
"""
|
|
|
|
STR_FIELD = "id"
|
|
|
|
@staticmethod
|
|
def _convert_conversation_objects(data: dict[str, Any], reddit: praw.Reddit):
|
|
"""Convert messages and mod actions to PRAW objects."""
|
|
result = {"messages": [], "modActions": []}
|
|
for thing in data["objIds"]:
|
|
key = thing["key"]
|
|
thing_data = data[key][thing["id"]]
|
|
result[key].append(reddit._objector.objectify(thing_data))
|
|
data.update(result)
|
|
|
|
@staticmethod
|
|
def _convert_user_summary(data: dict[str, Any], reddit: praw.Reddit):
|
|
"""Convert dictionaries of recent user history to PRAW objects."""
|
|
parsers = {
|
|
"recentComments": reddit._objector.parsers[reddit.config.kinds["comment"]],
|
|
"recentConvos": ModmailConversation,
|
|
"recentPosts": reddit._objector.parsers[reddit.config.kinds["submission"]],
|
|
}
|
|
for kind, parser in parsers.items():
|
|
objects = []
|
|
for thing_id, summary in data[kind].items():
|
|
thing = parser(reddit, id=thing_id.rsplit("_", 1)[-1])
|
|
if parser is not ModmailConversation:
|
|
del summary["permalink"]
|
|
for key, value in summary.items():
|
|
setattr(thing, key, value)
|
|
objects.append(thing)
|
|
# Sort by id, oldest to newest
|
|
data[kind] = sorted(objects, key=lambda x: int(x.id, base=36), reverse=True)
|
|
|
|
@classmethod
|
|
def parse(
|
|
cls,
|
|
data: dict[str, Any],
|
|
reddit: praw.Reddit,
|
|
) -> ModmailConversation:
|
|
"""Return an instance of :class:`.ModmailConversation` from ``data``.
|
|
|
|
:param data: The structured data.
|
|
:param reddit: An instance of :class:`.Reddit`.
|
|
|
|
"""
|
|
data["authors"] = [
|
|
reddit._objector.objectify(author) for author in data["authors"]
|
|
]
|
|
for entity in "owner", "participant":
|
|
data[entity] = reddit._objector.objectify(data[entity])
|
|
|
|
if data.get("user"):
|
|
cls._convert_user_summary(data["user"], reddit)
|
|
data["user"] = reddit._objector.objectify(data["user"])
|
|
|
|
data = snake_case_keys(data)
|
|
|
|
return cls(reddit, _data=data)
|
|
|
|
def __init__(
|
|
self,
|
|
reddit: praw.Reddit,
|
|
id: str | None = None,
|
|
mark_read: bool = False,
|
|
_data: dict[str, Any] | None = None,
|
|
):
|
|
"""Initialize a :class:`.ModmailConversation` instance.
|
|
|
|
:param mark_read: If ``True``, conversation is marked as read (default:
|
|
``False``).
|
|
|
|
"""
|
|
if bool(id) == bool(_data):
|
|
msg = "Either 'id' or '_data' must be provided."
|
|
raise TypeError(msg)
|
|
|
|
if id:
|
|
self.id = id
|
|
|
|
super().__init__(reddit, _data=_data)
|
|
|
|
self._info_params = {"markRead": True} if mark_read else None
|
|
|
|
def _build_conversation_list(
|
|
self, other_conversations: list[ModmailConversation]
|
|
) -> str:
|
|
"""Return a comma-separated list of conversation IDs."""
|
|
conversations = [self] + (other_conversations or [])
|
|
return ",".join(conversation.id for conversation in conversations)
|
|
|
|
def _fetch(self):
|
|
data = self._fetch_data()
|
|
other = self._reddit._objector.objectify(data)
|
|
self.__dict__.update(other.__dict__)
|
|
super()._fetch()
|
|
|
|
def _fetch_info(self):
|
|
return "modmail_conversation", {"id": self.id}, self._info_params
|
|
|
|
def archive(self):
|
|
"""Archive the conversation.
|
|
|
|
For example:
|
|
|
|
.. code-block:: python
|
|
|
|
reddit.subreddit("test").modmail("2gmz").archive()
|
|
|
|
"""
|
|
self._reddit.post(API_PATH["modmail_archive"].format(id=self.id))
|
|
|
|
def highlight(self):
|
|
"""Highlight the conversation.
|
|
|
|
For example:
|
|
|
|
.. code-block:: python
|
|
|
|
reddit.subreddit("test").modmail("2gmz").highlight()
|
|
|
|
"""
|
|
self._reddit.post(API_PATH["modmail_highlight"].format(id=self.id))
|
|
|
|
@_deprecate_args("num_days")
|
|
def mute(self, *, num_days: int = 3):
|
|
"""Mute the non-mod user associated with the conversation.
|
|
|
|
:param num_days: Duration of mute in days. Valid options are ``3``, ``7``, or
|
|
``28`` (default: ``3``).
|
|
|
|
For example:
|
|
|
|
.. code-block:: python
|
|
|
|
reddit.subreddit("test").modmail("2gmz").mute()
|
|
|
|
To mute for 7 days:
|
|
|
|
.. code-block:: python
|
|
|
|
reddit.subreddit("test").modmail("2gmz").mute(num_days=7)
|
|
|
|
"""
|
|
params = {"num_hours": num_days * 24} if num_days != 3 else {}
|
|
self._reddit.request(
|
|
method="POST",
|
|
params=params,
|
|
path=API_PATH["modmail_mute"].format(id=self.id),
|
|
)
|
|
|
|
@_deprecate_args("other_conversations")
|
|
def read(self, *, other_conversations: list[ModmailConversation] | None = None):
|
|
"""Mark the conversation(s) as read.
|
|
|
|
:param other_conversations: A list of other conversations to mark (default:
|
|
``None``).
|
|
|
|
For example, to mark the conversation as read along with other recent
|
|
conversations from the same user:
|
|
|
|
.. code-block:: python
|
|
|
|
subreddit = reddit.subreddit("test")
|
|
conversation = subreddit.modmail.conversation("2gmz")
|
|
conversation.read(other_conversations=conversation.user.recent_convos)
|
|
|
|
"""
|
|
data = {"conversationIds": self._build_conversation_list(other_conversations)}
|
|
self._reddit.post(API_PATH["modmail_read"], data=data)
|
|
|
|
@_deprecate_args("body", "author_hidden", "internal")
|
|
def reply(
|
|
self, *, author_hidden: bool = False, body: str, internal: bool = False
|
|
) -> ModmailMessage:
|
|
"""Reply to the conversation.
|
|
|
|
:param author_hidden: When ``True``, author is hidden from non-moderators
|
|
(default: ``False``).
|
|
:param body: The Markdown formatted content for a message.
|
|
:param internal: When ``True``, message is a private moderator note, hidden from
|
|
non-moderators (default: ``False``).
|
|
|
|
:returns: A :class:`.ModmailMessage` object for the newly created message.
|
|
|
|
For example, to reply to the non-mod user while hiding your username:
|
|
|
|
.. code-block:: python
|
|
|
|
conversation = reddit.subreddit("test").modmail("2gmz")
|
|
conversation.reply(body="Message body", author_hidden=True)
|
|
|
|
To create a private moderator note on the conversation:
|
|
|
|
.. code-block:: python
|
|
|
|
conversation.reply(body="Message body", internal=True)
|
|
|
|
"""
|
|
data = {
|
|
"body": body,
|
|
"isAuthorHidden": author_hidden,
|
|
"isInternal": internal,
|
|
}
|
|
response = self._reddit.post(
|
|
API_PATH["modmail_conversation"].format(id=self.id), data=data
|
|
)
|
|
if isinstance(response, dict):
|
|
# Reddit recently changed the response format, so we need to handle both in case they change it back
|
|
message_id = response["conversation"]["objIds"][-1]["id"]
|
|
message_data = response["messages"][message_id]
|
|
return self._reddit._objector.objectify(message_data)
|
|
for message in response.messages: # noqa: RET503
|
|
if message.id == response.obj_ids[-1]["id"]:
|
|
return message
|
|
|
|
def unarchive(self):
|
|
"""Unarchive the conversation.
|
|
|
|
For example:
|
|
|
|
.. code-block:: python
|
|
|
|
reddit.subreddit("test").modmail("2gmz").unarchive()
|
|
|
|
"""
|
|
self._reddit.post(API_PATH["modmail_unarchive"].format(id=self.id))
|
|
|
|
def unhighlight(self):
|
|
"""Un-highlight the conversation.
|
|
|
|
For example:
|
|
|
|
.. code-block:: python
|
|
|
|
reddit.subreddit("test").modmail("2gmz").unhighlight()
|
|
|
|
"""
|
|
self._reddit.delete(API_PATH["modmail_highlight"].format(id=self.id))
|
|
|
|
def unmute(self):
|
|
"""Unmute the non-mod user associated with the conversation.
|
|
|
|
For example:
|
|
|
|
.. code-block:: python
|
|
|
|
reddit.subreddit("test").modmail("2gmz").unmute()
|
|
|
|
"""
|
|
self._reddit.request(
|
|
method="POST", path=API_PATH["modmail_unmute"].format(id=self.id)
|
|
)
|
|
|
|
@_deprecate_args("other_conversations")
|
|
def unread(self, *, other_conversations: list[ModmailConversation] | None = None):
|
|
"""Mark the conversation(s) as unread.
|
|
|
|
:param other_conversations: A list of other conversations to mark (default:
|
|
``None``).
|
|
|
|
For example, to mark the conversation as unread along with other recent
|
|
conversations from the same user:
|
|
|
|
.. code-block:: python
|
|
|
|
subreddit = reddit.subreddit("test")
|
|
conversation = subreddit.modmail.conversation("2gmz")
|
|
conversation.unread(other_conversations=conversation.user.recent_convos)
|
|
|
|
"""
|
|
data = {"conversationIds": self._build_conversation_list(other_conversations)}
|
|
self._reddit.post(API_PATH["modmail_unread"], data=data)
|
|
|
|
|
|
class ModmailAction(ModmailObject):
|
|
"""A class for moderator actions on modmail conversations."""
|
|
|
|
|
|
class ModmailMessage(ModmailObject):
|
|
"""A class for modmail messages."""
|