Skip to content

Commit

Permalink
Add support for new share URL format created from Reddit's mobile apps.
Browse files Browse the repository at this point in the history
  • Loading branch information
LilSpazJoekp committed Nov 9, 2023
1 parent 884ffdd commit ea69554
Show file tree
Hide file tree
Showing 11 changed files with 926 additions and 109 deletions.
1 change: 1 addition & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Unreleased
flair templates.
- :func:`.stream_generator` now accepts the ``continue_after_id`` parameter, which
starts the stream after a given item ID.
- Support for new share URL format created from Reddit's mobile apps.

**Fixed**

Expand Down
32 changes: 19 additions & 13 deletions praw/models/reddit/comment.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

from typing import TYPE_CHECKING, Any

from prawcore import Redirect

from ...const import API_PATH
from ...exceptions import ClientException, InvalidURL
from ...util.cache import cachedproperty
Expand Down Expand Up @@ -61,19 +63,6 @@ class Comment(InboxableMixin, UserContentMixin, FullnameMixin, RedditBase):
MISSING_COMMENT_MESSAGE = "This comment does not appear to be in the comment tree"
STR_FIELD = "id"

@staticmethod
def id_from_url(url: str) -> str:
"""Get the ID of a comment from the full URL."""
parts = RedditBase._url_parts(url)
try:
comment_index = parts.index("comments")
except ValueError:
raise InvalidURL(url) from None

if len(parts) - 4 != comment_index:
raise InvalidURL(url)
return parts[-1]

@cachedproperty
def mod(self) -> praw.models.reddit.comment.CommentModeration:
"""Provide an instance of :class:`.CommentModeration`.
Expand Down Expand Up @@ -202,6 +191,23 @@ def _fetch(self):
def _fetch_info(self):
return "info", {}, {"id": self.fullname}

def id_from_url(self, url: str) -> str:
"""Get the ID of a comment from the full URL."""
parts = self._url_parts(url)
if "s" in parts: # handling new share urls from mobile apps
try:
self._reddit.get(url)
except Redirect as e:
parts = self._url_parts(e.response.next.url)
try:
comment_index = parts.index("comments")
except ValueError:
raise InvalidURL(url) from None

if len(parts) - 4 != comment_index:
raise InvalidURL(url)
return parts[-1]

def parent(
self,
) -> Comment | praw.models.Submission:
Expand Down
85 changes: 48 additions & 37 deletions praw/models/reddit/submission.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from urllib.parse import urljoin
from warnings import warn

from prawcore import Conflict
from prawcore import Conflict, Redirect

from ...const import API_PATH
from ...exceptions import InvalidURL
Expand Down Expand Up @@ -457,42 +457,6 @@ class Submission(SubmissionListingMixin, UserContentMixin, FullnameMixin, Reddit

STR_FIELD = "id"

@staticmethod
def id_from_url(url: str) -> str:
"""Return the ID contained within a submission URL.
:param url: A url to a submission in one of the following formats (http urls
will also work):
- ``"https://redd.it/2gmzqe"``
- ``"https://reddit.com/comments/2gmzqe/"``
- ``"https://www.reddit.com/r/redditdev/comments/2gmzqe/praw_https/"``
- ``"https://www.reddit.com/gallery/2gmzqe"``
:raises: :class:`.InvalidURL` if ``url`` is not a valid submission URL.
"""
parts = RedditBase._url_parts(url)
if "comments" not in parts and "gallery" not in parts:
submission_id = parts[-1]
if "r" in parts:
raise InvalidURL(
url, message="Invalid URL (subreddit, not submission): {}"
)

elif "gallery" in parts:
submission_id = parts[parts.index("gallery") + 1]

elif parts[-1] == "comments":
raise InvalidURL(url, message="Invalid URL (submission ID not present): {}")

else:
submission_id = parts[parts.index("comments") + 1]

if not submission_id.isalnum():
raise InvalidURL(url)
return submission_id

@cachedproperty
def flair(self) -> SubmissionFlair:
"""Provide an instance of :class:`.SubmissionFlair`.
Expand Down Expand Up @@ -601,6 +565,7 @@ def __init__(
if id:
self.id = id
elif url:
self._reddit = reddit
self.id = self.id_from_url(url)

super().__init__(reddit, _data=_data)
Expand Down Expand Up @@ -905,6 +870,52 @@ def hide(self, *, other_submissions: list[praw.models.Submission] | None = None)
):
self._reddit.post(API_PATH["hide"], data={"id": submissions})

def id_from_url(self, url: str) -> str:
"""Return the ID contained within a submission URL.
:param url: A url to a submission in one of the following formats (http urls
will also work):
- ``"https://redd.it/2gmzqe"``
- ``"https://reddit.com/comments/2gmzqe/"``
- ``"https://www.reddit.com/r/redditdev/comments/2gmzqe/praw_https/"``
- ``"https://www.reddit.com/gallery/2gmzqe"``
- ``"https://www.reddit.com/r/space/s/skZgKbbMDq"``
:raises: :class:`.InvalidURL` if ``url`` is not a valid submission URL.
"""
parts = self._url_parts(url)

if "s" in parts: # handling new share urls from mobile apps
try:
self._reddit.get(url)
except Redirect as e:
parts = self._url_parts(e.response.next.url)

if not {"comments", "gallery", "s"}.intersection(parts):
submission_id = parts[-1]
if "r" in parts:
raise InvalidURL(
url, message="Invalid URL (subreddit, not submission): {}"
)

elif "gallery" in parts:
submission_id = parts[parts.index("gallery") + 1]

elif parts[-1] == "comments":
raise InvalidURL(url, message="Invalid URL (submission ID not present): {}")

elif parts[-1] == "s":
raise InvalidURL(url, message="Invalid URL (missing shortlink id): {}")

else:
submission_id = parts[parts.index("comments") + 1]

if not submission_id.isalnum():
raise InvalidURL(url)
return submission_id

def mark_visited(self):
"""Mark submission as visited.
Expand Down
198 changes: 198 additions & 0 deletions tests/integration/cassettes/TestComment.test_id_from_url.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
{
"http_interactions": [
{
"recorded_at": "2023-11-09T19:03:42",
"request": {
"body": {
"encoding": "utf-8",
"string": "grant_type=client_credentials"
},
"headers": {
"Accept": [
"*/*"
],
"Accept-Encoding": [
"identity"
],
"Authorization": [
"Basic <BASIC_AUTH>"
],
"Connection": [
"close"
],
"Content-Length": [
"29"
],
"Content-Type": [
"application/x-www-form-urlencoded"
],
"User-Agent": [
"<USER_AGENT> PRAW/7.7.2.dev0 prawcore/2.3.0"
]
},
"method": "POST",
"uri": "https://www.reddit.com/api/v1/access_token"
},
"response": {
"body": {
"encoding": "UTF-8",
"string": "{\"access_token\": \"<ACCESS_TOKEN>\", \"token_type\": \"bearer\", \"expires_in\": 86400, \"scope\": \"*\"}"
},
"headers": {
"Accept-Ranges": [
"bytes"
],
"Cache-Control": [
"private, max-age=3600"
],
"Connection": [
"close"
],
"Content-Length": [
"812"
],
"Date": [
"Thu, 09 Nov 2023 19:03:42 GMT"
],
"NEL": [
"{\"report_to\": \"w3-reporting-nel\", \"max_age\": 14400, \"include_subdomains\": false, \"success_fraction\": 1.0, \"failure_fraction\": 1.0}"
],
"Report-To": [
"{\"group\": \"w3-reporting-nel\", \"max_age\": 14400, \"include_subdomains\": true, \"endpoints\": [{ \"url\": \"https://w3-reporting-nel.reddit.com/reports\" }]}, {\"group\": \"w3-reporting\", \"max_age\": 14400, \"include_subdomains\": true, \"endpoints\": [{ \"url\": \"https://w3-reporting.reddit.com/reports\" }]}, {\"group\": \"w3-reporting-csp\", \"max_age\": 14400, \"include_subdomains\": true, \"endpoints\": [{ \"url\": \"https://w3-reporting-csp.reddit.com/reports\" }]}"
],
"Server": [
"snooserv"
],
"Set-Cookie": [
"edgebucket=ZiuasF2t2VM3rHunbP; Domain=reddit.com; Max-Age=63071999; Path=/; secure"
],
"Strict-Transport-Security": [
"max-age=31536000; includeSubdomains"
],
"Vary": [
"accept-encoding, Accept-Encoding"
],
"Via": [
"1.1 varnish"
],
"X-Content-Type-Options": [
"nosniff"
],
"X-Frame-Options": [
"SAMEORIGIN"
],
"X-XSS-Protection": [
"1; mode=block"
],
"content-type": [
"application/json; charset=UTF-8"
],
"x-reddit-loid": [
"000000000njwzxoaiz.2.1699556622239.Z0FBQUFBQmxUUzBPZm1TOUx4SElwWW1rMG1rSXhZdmRCb2REMm50Vlo3V2xyaUltbnpKZTM4ZVd2cmNmdHRHbktzazFwS2FUNzIxeW5HTmN3MVIxM084NERFdnNHd2RaY2RFTlg1dHBmVFJqQjdBYVZuV1RHRGpzZjk5S3RBcG5laDE1ZFA2WF9vSmc"
]
},
"status": {
"code": 200,
"message": "OK"
},
"url": "https://www.reddit.com/api/v1/access_token"
}
},
{
"recorded_at": "2023-11-09T19:03:42",
"request": {
"body": {
"encoding": "utf-8",
"string": ""
},
"headers": {
"Accept": [
"*/*"
],
"Accept-Encoding": [
"identity"
],
"Authorization": [
"bearer <ACCESS_TOKEN>"
],
"Connection": [
"keep-alive"
],
"Cookie": [
"edgebucket=ZiuasF2t2VM3rHunbP"
],
"User-Agent": [
"<USER_AGENT> PRAW/7.7.2.dev0 prawcore/2.3.0"
]
},
"method": "GET",
"uri": "https://www.reddit.com/r/redditdev/s/nGnQE1QkLC?raw_json=1"
},
"response": {
"body": {
"encoding": "utf-8",
"string": "<a href=\"https://www.reddit.com/r/redditdev/comments/2gmzqe/praw_https_enabled_praw_testing_needed/cklhv0f/?context=3&amp;share_id=vi8DOF9B0U5cJSzhFjDE9&amp;utm_content=1&amp;utm_medium=ios_app&amp;utm_name=ioscss&amp;utm_source=share&amp;utm_term=1\">Moved Permanently</a>.\n\n"
},
"headers": {
"Accept-Ranges": [
"bytes"
],
"Cache-Control": [
"private, max-age=3600"
],
"Connection": [
"keep-alive"
],
"Content-Length": [
"275"
],
"Date": [
"Thu, 09 Nov 2023 19:03:42 GMT"
],
"NEL": [
"{\"report_to\": \"w3-reporting-nel\", \"max_age\": 14400, \"include_subdomains\": false, \"success_fraction\": 1.0, \"failure_fraction\": 1.0}"
],
"Report-To": [
"{\"group\": \"w3-reporting-nel\", \"max_age\": 14400, \"include_subdomains\": true, \"endpoints\": [{ \"url\": \"https://w3-reporting-nel.reddit.com/reports\" }]}, {\"group\": \"w3-reporting\", \"max_age\": 14400, \"include_subdomains\": true, \"endpoints\": [{ \"url\": \"https://w3-reporting.reddit.com/reports\" }]}, {\"group\": \"w3-reporting-csp\", \"max_age\": 14400, \"include_subdomains\": true, \"endpoints\": [{ \"url\": \"https://w3-reporting-csp.reddit.com/reports\" }]}"
],
"Server": [
"snooserv"
],
"Set-Cookie": [
"csv=2; Max-Age=63072000; Domain=.reddit.com; Path=/; Secure; SameSite=None"
],
"Strict-Transport-Security": [
"max-age=31536000; includeSubdomains"
],
"Vary": [
"Accept-Encoding"
],
"Via": [
"1.1 varnish"
],
"X-Content-Type-Options": [
"nosniff"
],
"X-Frame-Options": [
"SAMEORIGIN"
],
"X-XSS-Protection": [
"1; mode=block"
],
"content-type": [
"text/html; charset=utf-8"
],
"location": [
"https://www.reddit.com/r/redditdev/comments/2gmzqe/praw_https_enabled_praw_testing_needed/cklhv0f/?context=3&share_id=vi8DOF9B0U5cJSzhFjDE9&utm_content=1&utm_medium=ios_app&utm_name=ioscss&utm_source=share&utm_term=1"
]
},
"status": {
"code": 301,
"message": "Moved Permanently"
},
"url": "https://www.reddit.com/r/redditdev/s/nGnQE1QkLC?raw_json=1"
}
}
],
"recorded_with": "betamax/0.8.1"
}
Loading

0 comments on commit ea69554

Please sign in to comment.