Skip to content
Open
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
18 changes: 16 additions & 2 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ precommit.shell = "poe format && poe lint && poe coverage"

[tool.poetry.dependencies]
python = ">=3.8,<3.15"
requests = "*"
requests = { extras = ["socks"], version = "*" }
defusedxml = "^0.7.1"

[tool.poetry.group.test]
Expand Down Expand Up @@ -93,4 +93,4 @@ exclude_lines = [
"@abstractclassmethod",
"@abstractstaticmethod"
]
show_missing = true
show_missing = true
13 changes: 12 additions & 1 deletion youtube_transcript_api/_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,15 @@ def run(self) -> str:
return ""

proxy_config = None
if parsed_args.http_proxy != "" or parsed_args.https_proxy != "":
if (
parsed_args.http_proxy != ""
or parsed_args.https_proxy != ""
or parsed_args.socks_proxy != ""
):
proxy_config = GenericProxyConfig(
http_url=parsed_args.http_proxy,
https_url=parsed_args.https_proxy,
socks_url=parsed_args.socks_proxy,
)

if (
Expand Down Expand Up @@ -188,6 +193,12 @@ def _parse_args(self):
metavar="URL",
help="Use the specified HTTPS proxy.",
)
parser.add_argument(
"--socks-proxy",
default="",
metavar="URL",
help="Use the specified SOCKS proxy.",
)
# Cookie auth has been temporarily disabled, as it is not working properly with
# YouTube's most recent changes.
# parser.add_argument(
Expand Down
35 changes: 22 additions & 13 deletions youtube_transcript_api/proxies.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,32 +59,41 @@ class GenericProxyConfig(ProxyConfig):
documentation to get more detailed information on how to set up proxies:
https://requests.readthedocs.io/en/latest/user/advanced/#proxies

If only an HTTP or an HTTPS proxy is provided, it will be used for both types of
connections. However, you will have to provide at least one of the two.
If only an HTTP, HTTPS, or SOCKS proxy is provided, it will be used for both
types of connections. However, you will have to provide at least one of the three.
"""

def __init__(self, http_url: Optional[str] = None, https_url: Optional[str] = None):
def __init__(
self,
http_url: Optional[str] = None,
https_url: Optional[str] = None,
socks_url: Optional[str] = None,
):
"""
If only an HTTP or an HTTPS proxy is provided, it will be used for both types of
connections. However, you will have to provide at least one of the two.
If only an HTTP, HTTPS, or SOCKS proxy is provided, it will be used for both
types of connections. However, you will have to provide at least one of the
three.

:param http_url: the proxy URL used for HTTP requests. Defaults to `https_url`
if None.
:param http_url: the proxy URL used for HTTP requests. Defaults to `socks_url`
or `https_url` if None.
:param https_url: the proxy URL used for HTTPS requests. Defaults to `http_url`
if None.
or `socks_url` if None.
:param socks_url: the proxy URL used for HTTP and HTTPS requests if `http_url`
or `https_url` are None.
"""
if not http_url and not https_url:
if not http_url and not https_url and not socks_url:
raise InvalidProxyConfig(
"GenericProxyConfig requires you to define at least one of the two: "
"http or https"
"GenericProxyConfig requires you to define at least one of the "
"following: http, https, or socks"
)
self.http_url = http_url
self.https_url = https_url
self.socks_url = socks_url

def to_requests_dict(self) -> RequestsProxyConfigDict:
return {
"http": self.http_url or self.https_url,
"https": self.https_url or self.http_url,
"http": self.http_url or self.socks_url or self.https_url,
"https": self.https_url or self.socks_url or self.http_url,
}


Expand Down
48 changes: 42 additions & 6 deletions youtube_transcript_api/test/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ def test_argument_parsing(self):
self.assertEqual(parsed_args.languages, ["de", "en"])
self.assertEqual(parsed_args.http_proxy, "")
self.assertEqual(parsed_args.https_proxy, "")
self.assertEqual(parsed_args.socks_proxy, "")

parsed_args = YouTubeTranscriptCli(
"v1 v2 --languages de en --format json".split()
Expand All @@ -77,6 +78,7 @@ def test_argument_parsing(self):
self.assertEqual(parsed_args.languages, ["de", "en"])
self.assertEqual(parsed_args.http_proxy, "")
self.assertEqual(parsed_args.https_proxy, "")
self.assertEqual(parsed_args.socks_proxy, "")

parsed_args = YouTubeTranscriptCli(
" --format json v1 v2 --languages de en".split()
Expand All @@ -86,17 +88,20 @@ def test_argument_parsing(self):
self.assertEqual(parsed_args.languages, ["de", "en"])
self.assertEqual(parsed_args.http_proxy, "")
self.assertEqual(parsed_args.https_proxy, "")
self.assertEqual(parsed_args.socks_proxy, "")

parsed_args = YouTubeTranscriptCli(
"v1 v2 --languages de en --format json "
"--http-proxy http://user:pass@domain:port "
"--https-proxy https://user:pass@domain:port".split()
"--https-proxy https://user:pass@domain:port "
"--socks-proxy socks5h://user:pass@domain:port".split()
)._parse_args()
self.assertEqual(parsed_args.video_ids, ["v1", "v2"])
self.assertEqual(parsed_args.format, "json")
self.assertEqual(parsed_args.languages, ["de", "en"])
self.assertEqual(parsed_args.http_proxy, "http://user:pass@domain:port")
self.assertEqual(parsed_args.https_proxy, "https://user:pass@domain:port")
self.assertEqual(parsed_args.socks_proxy, "socks5h://user:pass@domain:port")

parsed_args = YouTubeTranscriptCli(
"v1 v2 --languages de en --format json "
Expand Down Expand Up @@ -127,6 +132,16 @@ def test_argument_parsing(self):
self.assertEqual(parsed_args.https_proxy, "https://user:pass@domain:port")
self.assertEqual(parsed_args.http_proxy, "")

parsed_args = YouTubeTranscriptCli(
"v1 v2 --languages de en --format json --socks-proxy socks5h://user:pass@domain:port".split()
)._parse_args()
self.assertEqual(parsed_args.video_ids, ["v1", "v2"])
self.assertEqual(parsed_args.format, "json")
self.assertEqual(parsed_args.languages, ["de", "en"])
self.assertEqual(parsed_args.socks_proxy, "socks5h://user:pass@domain:port")
self.assertEqual(parsed_args.http_proxy, "")
self.assertEqual(parsed_args.https_proxy, "")

def test_argument_parsing__only_video_ids(self):
parsed_args = YouTubeTranscriptCli("v1 v2".split())._parse_args()
self.assertEqual(parsed_args.video_ids, ["v1", "v2"])
Expand Down Expand Up @@ -174,14 +189,21 @@ def test_argument_parsing__proxies(self):
self.assertEqual(parsed_args.https_proxy, "https://user:pass@domain:port")

parsed_args = YouTubeTranscriptCli(
"v1 v2 --http-proxy http://user:pass@domain:port --https-proxy https://user:pass@domain:port".split()
"v1 v2 --socks-proxy socks5h://user:pass@domain:port".split()
)._parse_args()
self.assertEqual(parsed_args.socks_proxy, "socks5h://user:pass@domain:port")

parsed_args = YouTubeTranscriptCli(
"v1 v2 --http-proxy http://user:pass@domain:port --https-proxy https://user:pass@domain:port --socks-proxy socks5h://user:pass@domain:port".split()
)._parse_args()
self.assertEqual(parsed_args.http_proxy, "http://user:pass@domain:port")
self.assertEqual(parsed_args.https_proxy, "https://user:pass@domain:port")
self.assertEqual(parsed_args.socks_proxy, "socks5h://user:pass@domain:port")

parsed_args = YouTubeTranscriptCli("v1 v2".split())._parse_args()
self.assertEqual(parsed_args.http_proxy, "")
self.assertEqual(parsed_args.https_proxy, "")
self.assertEqual(parsed_args.socks_proxy, "")

def test_argument_parsing__list_transcripts(self):
parsed_args = YouTubeTranscriptCli(
Expand Down Expand Up @@ -329,13 +351,27 @@ def test_run__generic_proxy_config(self):
self.assertEqual(proxy_config.http_url, "http://user:pass@domain:port")
self.assertEqual(proxy_config.https_url, "https://user:pass@domain:port")

def test_run__generic_socks_proxy_config(self):
YouTubeTranscriptCli(
(
"v1 v2 --languages de en --socks-proxy socks5h://user:pass@domain:port"
).split()
).run()

proxy_config = YouTubeTranscriptApi.__init__.call_args.kwargs.get(
"proxy_config"
)

self.assertIsNotNone(proxy_config)
self.assertEqual(proxy_config.socks_url, "socks5h://user:pass@domain:port")

@pytest.mark.skip(
reason="This test is temporarily disabled because cookie auth is currently not "
"working due to YouTube changes."
)
def test_run__cookies(self):
YouTubeTranscriptCli(
("v1 v2 --languages de en " "--cookies blahblah.txt").split()
("v1 v2 --languages de en --cookies blahblah.txt").split()
).run()

YouTubeTranscriptApi.__init__.assert_any_call(
Expand All @@ -358,9 +394,9 @@ def test_version_matches_metadata(self):
check=True,
).stdout.strip()

assert (
cli_version_msg == expected_version_msg
), f"Expected version '{expected_version_msg}', but got '{cli_version_msg}'"
assert cli_version_msg == expected_version_msg, (
f"Expected version '{expected_version_msg}', but got '{cli_version_msg}'"
)

def test_get_version_package_not_found(self):
with patch(
Expand Down
25 changes: 25 additions & 0 deletions youtube_transcript_api/test/test_proxies.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,31 @@ def test_to_requests_dict__only_https(self):
"https": "https://myproxy.com",
}

def test_to_requests_dict__only_socks(self):
proxy_config = GenericProxyConfig(
socks_url="socks5h://myproxy.com",
)

request_dict = proxy_config.to_requests_dict()

assert request_dict == {
"http": "socks5h://myproxy.com",
"https": "socks5h://myproxy.com",
}

def test_to_requests_dict__socks_as_fallback(self):
proxy_config = GenericProxyConfig(
http_url="http://myproxy.com",
socks_url="socks5h://myproxy.com",
)

request_dict = proxy_config.to_requests_dict()

assert request_dict == {
"http": "http://myproxy.com",
"https": "socks5h://myproxy.com",
}

def test__invalid_config(self):
with pytest.raises(InvalidProxyConfig):
GenericProxyConfig()
Expand Down
Loading