fix(downloads): a shared or feed-opened Facebook group post downloads its own video
A Facebook share link (/share/v/…) redirects to a group post, but only the permalink form went through the post-page read, so yt-dlp followed the share link itself and downloaded an ad from the signed-in group feed (prod job 471). The link of a post opened from the group's feed (/groups/<g>/?multi_permalinks=<id>) was not recognised at all, and yt-dlp answers it with "Unsupported URL" (prod job 472). With a cookie, a share link is now followed in the same request that reads the post page, and a feed link is read as the post it names. Without one, the feed link goes to yt-dlp in the permalink form it knows. A post page's "no single video" verdict now names the post for a share link too.
This commit is contained in:
@@ -27,6 +27,18 @@ _GROUP_LINK = f"https://www.facebook.com/groups/983554479435596/permalink/{_POST
|
||||
_POST_VIDEO = "1408015447857753"
|
||||
_FEED_VIDEO = "1056963063816966" # what yt-dlp downloaded from this post's link: a feed video
|
||||
_CHROME = ImpersonateTarget.from_str("chrome")
|
||||
# The two other ways a group post reaches us (prod jobs 471 and 472, the same post): Facebook's "Share"
|
||||
# link, which redirects to the post's permalink (measured), and the link of a post opened from the
|
||||
# group's feed, which yt-dlp does not know at all ("Unsupported URL").
|
||||
_SHARE_LINK = "https://www.facebook.com/share/v/19bCAHKyrd/"
|
||||
_LANDED = (
|
||||
f"https://www.facebook.com/groups/it.humor.and.memes/permalink/{_POST}/"
|
||||
"?rdid=y9HAMntF7aCoAKZE&share_url=https%3A%2F%2Fwww.facebook.com%2Fshare%2Fv%2F19bCAHKyrd%2F"
|
||||
)
|
||||
_MULTI_LINK = (
|
||||
f"https://www.facebook.com/groups/983554479435596/?multi_permalinks={_POST}"
|
||||
"&hoisted_section_header_type=recently_seen&__cft__[0]=AZgK&__tn__=%2CO%2CP-R"
|
||||
)
|
||||
|
||||
|
||||
def _video_node(video_id: str) -> str:
|
||||
@@ -86,6 +98,9 @@ def ydl(monkeypatch):
|
||||
body = web["page"].encode()
|
||||
|
||||
class Response:
|
||||
# Where the request ended up after its redirects (yt-dlp's `Response.url`).
|
||||
url = web.get("landed") or web["opened"][-1]
|
||||
|
||||
def read(self, amt=None):
|
||||
web["reads"].append(amt)
|
||||
return body
|
||||
@@ -228,12 +243,79 @@ def test_without_a_cookie_the_group_post_link_goes_to_ytdlp_unchanged(ydl, tmp_p
|
||||
assert web["extracted"] == [_GROUP_LINK]
|
||||
|
||||
|
||||
def test_a_share_link_to_a_group_post_downloads_the_posts_own_video(ydl, tmp_path):
|
||||
# Prod job 471: handed the share link, yt-dlp followed it to the group page and downloaded an
|
||||
# ad from the signed-in feed. Followed here first, it is the group post it leads to.
|
||||
_, outcomes, web = ydl
|
||||
web["page"], web["landed"] = _page(_POST_VIDEO), _LANDED
|
||||
outcomes += [{"id": _POST_VIDEO}]
|
||||
|
||||
assert _run(tmp_path, "/cookies.txt", _SHARE_LINK) == {"id": _POST_VIDEO}
|
||||
|
||||
assert web["opened"] == [_SHARE_LINK] # one request: the redirect and the post page at once
|
||||
assert web["reads"] == [adapters.FacebookAdapter._POST_PAGE_MAX_BYTES]
|
||||
assert web["extracted"] == [f"https://www.facebook.com/watch/?v={_POST_VIDEO}"]
|
||||
|
||||
|
||||
def test_a_share_link_to_a_reel_goes_to_ytdlp_unchanged(ydl, tmp_path):
|
||||
_, outcomes, web = ydl
|
||||
web["landed"] = _URL
|
||||
outcomes += [{"id": "806379462330494"}]
|
||||
|
||||
_run(tmp_path, "/cookies.txt", _SHARE_LINK)
|
||||
|
||||
assert web["reads"] == [] # not a post: its page is not read
|
||||
assert web["extracted"] == [_SHARE_LINK] # the path that already downloads shared reels
|
||||
|
||||
|
||||
def test_a_share_link_that_fails_as_a_group_post_says_why(ydl, tmp_path):
|
||||
_, _, web = ydl
|
||||
web["page"], web["landed"] = _page(), _LANDED
|
||||
|
||||
with pytest.raises(RuntimeError) as exc:
|
||||
_run(tmp_path, "/cookies.txt", _SHARE_LINK)
|
||||
|
||||
assert web["extracted"] == []
|
||||
# The job's own link is the share link: the verdict still names the post.
|
||||
assert dlerrors.diagnose(str(exc.value), url=_SHARE_LINK, cookie_attached=True) == (
|
||||
"format_unavailable",
|
||||
"facebook.group_post_video",
|
||||
)
|
||||
|
||||
|
||||
def test_a_feed_opened_group_post_downloads_the_posts_own_video(ydl, tmp_path):
|
||||
# Prod job 472: yt-dlp answers this link with "Unsupported URL".
|
||||
_, outcomes, web = ydl
|
||||
web["page"] = _page(_POST_VIDEO)
|
||||
outcomes += [{"id": _POST_VIDEO}]
|
||||
|
||||
assert _run(tmp_path, "/cookies.txt", _MULTI_LINK) == {"id": _POST_VIDEO}
|
||||
|
||||
assert web["opened"] == [_MULTI_LINK]
|
||||
assert web["extracted"] == [f"https://www.facebook.com/watch/?v={_POST_VIDEO}"]
|
||||
|
||||
|
||||
def test_without_a_cookie_a_feed_opened_group_post_goes_as_its_permalink(ydl, tmp_path):
|
||||
# Signed out there is no page to read, but yt-dlp knows the permalink form and not this one.
|
||||
_, outcomes, web = ydl
|
||||
outcomes += [{"id": _POST_VIDEO}]
|
||||
|
||||
_run(tmp_path, None, _MULTI_LINK)
|
||||
|
||||
assert web["opened"] == []
|
||||
assert web["extracted"] == [
|
||||
f"https://www.facebook.com/groups/983554479435596/permalink/{_POST}/"
|
||||
]
|
||||
|
||||
|
||||
def test_the_group_post_link_forms_are_recognised():
|
||||
for url in (
|
||||
_GROUP_LINK,
|
||||
f"https://www.facebook.com/groups/983554479435596/posts/{_POST}/",
|
||||
f"https://m.facebook.com/groups/983554479435596/permalink/{_POST}/?",
|
||||
f"https://www.facebook.com/groups/chatgptmagyarul/posts/{_POST}",
|
||||
_MULTI_LINK,
|
||||
f"https://www.facebook.com/groups/983554479435596?multi_permalinks={_POST}",
|
||||
):
|
||||
assert adapters.facebook_group_post_id(url) == _POST, url
|
||||
for url in (
|
||||
@@ -241,6 +323,10 @@ def test_the_group_post_link_forms_are_recognised():
|
||||
f"https://www.facebook.com/watch/?v={_POST_VIDEO}",
|
||||
f"https://facebook.com.evil/groups/x/permalink/{_POST}/",
|
||||
"https://www.facebook.com/groups/983554479435596/",
|
||||
# More than one post in the link: which one was meant is not ours to guess.
|
||||
f"https://www.facebook.com/groups/983554479435596/?multi_permalinks={_POST},{_POST_VIDEO}",
|
||||
f"https://www.facebook.com/groups/983554479435596/members/?multi_permalinks={_POST}",
|
||||
_SHARE_LINK, # a share link is a post only once it is followed (`resolve_url`)
|
||||
):
|
||||
assert adapters.facebook_group_post_id(url) is None, url
|
||||
assert adapters.SourceAdapter().resolve_url(_GROUP_LINK, {"cookiefile": "/c"}) == _GROUP_LINK
|
||||
|
||||
Reference in New Issue
Block a user