Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions src/mediaforge/models/s_to/episode.py
Original file line number Diff line number Diff line change
Expand Up @@ -303,15 +303,19 @@ def redirect_url(self):
@property
def provider_url(self):
if self.__provider_url is None:
from urllib.parse import urlparse
from ...mirrors import site_for_url
try:
from ...playwright.captcha import solve_sto_modal
except ImportError:
from mediaforge.playwright.captcha import solve_sto_modal

# Try plain HTTP first β€” works when no modal is shown
resp = GLOBAL_SESSION.get(self.redirect_url)
if urlparse(resp.url).netloc != urlparse(self.redirect_url).netloc:
# Compare against every s.to mirror, not the redirect's own host:
# the mirror failover may answer from another one (serienstream.to
# for a dead s.to, or the bare-IP mirror), and that is still the
# s.to page with its captcha modal, not the provider.
if site_for_url(resp.url) != "sto":
# Redirect left s.to β€” no modal, plain session worked
self.__provider_url = resp.url
else:
Expand Down
46 changes: 46 additions & 0 deletions tests/test_sto_provider_redirect.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"""SerienstreamEpisode.provider_url must only skip the captcha modal once the
redirect has actually left s.to -- not when the mirror failover merely answered
from another s.to mirror, whose page still carries the modal and no provider.
"""

from types import SimpleNamespace

import pytest

from mediaforge.models.s_to import episode as episode_module
from mediaforge.playwright import captcha

EPISODE = "https://serienstream.to/serie/dragonball-z/staffel-7/episode-19"
PROVIDER = "https://voe.sx/e/abc123"


@pytest.fixture
def resolve(monkeypatch):
"""provider_url for a redirect built on *redirect_host* that the session
answered from *final_url*; returns (provider_url, solver was called)."""
def run(redirect_host, final_url):
solved = []
monkeypatch.setattr(episode_module, "GLOBAL_SESSION",
SimpleNamespace(get=lambda url, **kw: SimpleNamespace(url=final_url)))
monkeypatch.setattr(captcha, "solve_sto_modal", lambda *a, **kw: solved.append(a) or PROVIDER)
ep = episode_module.SerienstreamEpisode(url=EPISODE, selected_language="German Dub",
selected_provider="VOE")
ep._SerienstreamEpisode__redirect_url = f"https://{redirect_host}/r?t=token"
return ep.provider_url, bool(solved)
return run


def test_a_redirect_that_reached_the_provider_is_taken_as_is(resolve):
assert resolve("serienstream.to", PROVIDER) == (PROVIDER, False)


def test_the_bare_ip_mirror_is_still_sto_and_gets_the_modal_solved(resolve):
assert resolve("serienstream.to", "https://186.2.175.5/r?t=token") == (PROVIDER, True)


def test_a_dead_primary_failing_over_to_serienstream_is_still_sto(resolve):
assert resolve("s.to", "https://serienstream.to/r?t=token") == (PROVIDER, True)


def test_staying_on_the_same_mirror_still_gets_the_modal_solved(resolve):
assert resolve("serienstream.to", "https://serienstream.to/r?t=token") == (PROVIDER, True)
Loading