Skip to content

Commit

Permalink
Merge pull request #34 from praw-dev/inline_media
Browse files Browse the repository at this point in the history
Add ability to submit selttext with inline media
  • Loading branch information
LilSpazJoekp committed Jan 20, 2021
2 parents 3ec5d62 + 3798559 commit c636604
Show file tree
Hide file tree
Showing 14 changed files with 871 additions and 9 deletions.
1 change: 1 addition & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Unreleased
* Add method :meth:`.invited` to get invited moderators of a subreddit.
* Add method :meth:`~.Submission.award` and :meth:`~.Comment.award` with the ability to
specify type of award, anonymity, and message when awarding a submission or comment.
* Ability to submit text/self posts with inline media.
* Added :meth:`.Reddit.close` to close the requestor session.
* Ability to use :class:`.Reddit` as an asynchronous context manager that automatically
closes the requestor session on exit.
Expand Down
1 change: 1 addition & 0 deletions asyncpraw/endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"comment_replies": "message/comments/",
"compose": "api/compose/",
"contest_mode": "api/set_contest_mode/",
"convert_rte_body": "api/convert_rte_body_format",
"del": "api/del/",
"delete_message": "api/del_msg",
"delete_sr_banner": "r/{subreddit}/api/delete_sr_banner",
Expand Down
1 change: 1 addition & 0 deletions asyncpraw/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from .reddit.collections import Collection
from .reddit.comment import Comment
from .reddit.emoji import Emoji
from .reddit.inline_media import InlineGif, InlineImage, InlineMedia, InlineVideo
from .reddit.live import LiveThread, LiveUpdate
from .reddit.message import Message, SubredditMessage
from .reddit.modmail import ModmailAction, ModmailConversation, ModmailMessage
Expand Down
53 changes: 53 additions & 0 deletions asyncpraw/models/reddit/inline_media.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
"""Provide classes related to inline media."""


class InlineMedia:
"""Provides a way to embed media in self posts."""

TYPE = None

def __init__(self, path: str, caption: str = None):
"""Create an InlineMedia instance.
:param path: The path to a media file.
:param caption: An optional caption to add to the image. (default: None)
"""
self.path = path
self.caption = caption
self.media_id = None

def __eq__(self, other: "InlineMedia"):
"""Return whether the other instance equals the current."""
return all(
[
getattr(self, attr) == getattr(other, attr)
for attr in ["TYPE", "path", "caption", "media_id"]
]
)

def __repr__(self) -> str:
"""Return a string representation of the InlineMedia instance."""
return f"<{self.__class__.__name__} caption={self.caption!r}>"

def __str__(self):
"""Return a string representation of the media in Markdown format."""
return f'\n\n![{self.TYPE}]({self.media_id} "{self.caption if self.caption else ""}")\n\n'


class InlineGif(InlineMedia):
"""Class to provide a gif to embed in text."""

TYPE = "gif"


class InlineVideo(InlineMedia):
"""Class to provide a video to embed in text."""

TYPE = "video"


class InlineImage(InlineMedia):
"""Class to provide am image to embed in text."""

TYPE = "img"
99 changes: 91 additions & 8 deletions asyncpraw/models/reddit/subreddit.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from ..util import permissions_string, stream_generator
from .base import RedditBase
from .emoji import SubredditEmoji
from .inline_media import InlineMedia
from .mixins import FullnameMixin, MessageableMixin
from .modmail import ModmailConversation
from .removal_reasons import SubredditRemovalReasons
Expand Down Expand Up @@ -223,6 +224,11 @@ def _validate_gallery(images):
if not len(image.get("caption", "")) <= 180:
raise TypeError("Caption must be 180 characters or less.")

@staticmethod
def _validate_inline_media(inline_media: InlineMedia):
if not isfile(inline_media.path):
raise ValueError(f"{inline_media.path!r} is not a valid file path.")

@property
def _kind(self) -> str:
"""Return the class's kind."""
Expand Down Expand Up @@ -578,6 +584,17 @@ def __init__(self, reddit, display_name=None, _data=None):
self.display_name = display_name
self._path = API_PATH["subreddit"].format(subreddit=self)

async def _convert_to_fancypants(self, markdown_text: str):
"""Convert a Markdown string to a dict for use with the ``richtext_json`` param.
:param markdown_text: A Markdown string to convert.
:returns: A dict in ``richtext_json`` format.
"""
text_data = {"output_mode": "rtjson", "markdown_text": markdown_text}
rte_body = await self._reddit.post(API_PATH["convert_rte_body"], text_data)
return rte_body["output"]

def _fetch_info(self):
return ("subreddit_about", {"subreddit": self}, None)

Expand Down Expand Up @@ -657,12 +674,15 @@ async def _submit_media(self, data, timeout, without_websockets):
url = ws_update["payload"]["redirect"]
return await self._reddit.submission(url=url)

async def _upload_media(self, media_path, expected_mime_prefix=None, gallery=False):
async def _upload_media(
self, media_path, expected_mime_prefix=None, upload_type="link"
):
"""Upload media and return its URL. Uses undocumented endpoint.
:param expected_mime_prefix: If provided, enforce that the media has a
mime type that starts with the provided prefix.
:param gallery: If True, treat this as a gallery upload.
:param upload_type: One of ``link``, ``gallery'', or ``selfpost``. (default:
``link``)
"""
if media_path is None:
media_path = join(
Expand Down Expand Up @@ -706,9 +726,22 @@ async def _upload_media(self, media_path, expected_mime_prefix=None, gallery=Fal
if not response.status == 201:
self._parse_xml_response(response)
response.raise_for_status()
if gallery:
if upload_type == "link":
return f"{upload_url}/{upload_data['key']}"
else:
return upload_response["asset"]["asset_id"]
return upload_url + "/" + upload_data["key"]

async def _upload_inline_media(self, inline_media: InlineMedia):
"""Upload media for use in self posts and return ``inline_media``.
:param inline_media: An :class:`.InlineMedia` object to validate and upload.
"""
self._validate_inline_media(inline_media)
inline_media.media_id = await self._upload_media(
inline_media.path, upload_type="selfpost"
)
return inline_media

async def post_requirements(self):
"""Get the post requirements for a subreddit.
Expand Down Expand Up @@ -862,8 +895,9 @@ async def submit(
spoiler=False,
collection_id=None,
discussion_type=None,
):
"""Add a submission to the subreddit.
inline_media=None,
): # noqa: D301
r"""Add a submission to the subreddit.
:param title: The title of the submission.
:param selftext: The Markdown formatted content for a ``text``
Expand All @@ -885,6 +919,8 @@ async def submit(
a spoiler (default: False).
:param discussion_type: Set to ``CHAT`` to enable live discussion
instead of traditional comments (default: None).
:param inline_media: A dict of :class:`.InlineMedia` objects where the key is
the placeholder name in ``selftext``.
:returns: A :class:`~.Submission` object for the newly created
submission.
Expand All @@ -899,6 +935,42 @@ async def submit(
subreddit = await reddit.subreddit("reddit_api_test")
await subreddit.submit(title, url=url)
For example to submit a self post with inline media do:
.. code-block:: python
from asyncpraw.models import InlineGif, InlineImage, InlineVideo
gif = InlineGif("path/to/image.gif", "optional caption")
image = InlineImage("path/to/image.jpg", "optional caption")
video = InlineVideo("path/to/video.mp4", "optional caption")
selftext = "Text with a gif {gif1} an image {image1} and a video {video1} inline"
media = {'gif1': gif, 'image1': image, 'video1': video}
subreddit = await reddit.subreddit('redditdev')
await subreddit.submit('title', selftext=selftext, inline_media=media)
.. note::
Inserted media will have a padding of ``\\n\\n`` automatically added. This
is due to the weirdness with Reddit's API. Using the example above the
result selftext body will look like so:
.. code-block::
Text with a gif
![gif](u1rchuphryq51 "optional caption")
an image
![img](srnr8tshryq51 "optional caption")
and video
![video](gmc7rvthryq51 "optional caption")
inline
.. seealso ::
* :meth:`.submit_image` to submit images
Expand Down Expand Up @@ -928,7 +1000,18 @@ async def submit(
if value is not None:
data[key] = value
if selftext is not None:
data.update(kind="self", text=selftext)
data.update(kind="self")
if inline_media:
body = selftext.format(
**{
placeholder: await self._upload_inline_media(media)
for placeholder, media in inline_media.items()
}
)
converted = await self._convert_to_fancypants(body)
data.update(richtext_json=dumps(converted))
else:
data.update(text=selftext)
else:
data.update(kind="link", url=url)

Expand Down Expand Up @@ -1032,7 +1115,7 @@ async def submit_gallery(
"media_id": await self._upload_media(
image["image_path"],
expected_mime_prefix="image",
gallery=True,
upload_type="gallery",
),
}
)
Expand Down
4 changes: 4 additions & 0 deletions docs/code_overview/other.rst
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ instances of them bound to an attribute of one of the Async PRAW models.
other/listinggenerator
other/image
other/imagedata
other/inlinemedia
other/inlinegif
other/inlineimage
other/inlinevideo
other/menulink
other/modmail
other/modmailmessage
Expand Down
5 changes: 5 additions & 0 deletions docs/code_overview/other/inlinegif.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
InlineGif
=========

.. autoclass:: asyncpraw.models.InlineGif
:inherited-members:
5 changes: 5 additions & 0 deletions docs/code_overview/other/inlineimage.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
InlineImage
===========

.. autoclass:: asyncpraw.models.InlineImage
:inherited-members:
5 changes: 5 additions & 0 deletions docs/code_overview/other/inlinemedia.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
InlineMedia
===========

.. autoclass:: asyncpraw.models.InlineMedia
:inherited-members:
5 changes: 5 additions & 0 deletions docs/code_overview/other/inlinevideo.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
InlineVideo
===========

.. autoclass:: asyncpraw.models.InlineVideo
:inherited-members:
Loading

0 comments on commit c636604

Please sign in to comment.