Fix clips download by fetching access token

fixes #64
This commit is contained in:
Ivan Habunek 2021-06-09 15:07:07 +02:00
parent 0dd04a7e2d
commit a49dcab419
No known key found for this signature in database
GPG Key ID: CDBD63C43A30BB95
3 changed files with 62 additions and 8 deletions

View File

@ -3,6 +3,7 @@ import re
from os import path
from twitchdl import twitch, utils
from twitchdl.commands.download import get_clip_authenticated_url
from twitchdl.download import download_file
from twitchdl.exceptions import ConsoleError
from twitchdl.output import print_out, print_clip, print_json
@ -79,7 +80,7 @@ def _clips_download(args):
for clips, _ in generator:
for clip in clips["edges"]:
clip = clip["node"]
url = clip["videoQualities"][0]["sourceURL"]
url = get_clip_authenticated_url(clip["slug"], "source")
target = _clip_target_filename(clip)
if path.exists(target):

View File

@ -7,7 +7,7 @@ import tempfile
from os import path
from pathlib import Path
from urllib.parse import urlparse
from urllib.parse import urlparse, urlencode
from twitchdl import twitch, utils
from twitchdl.download import download_file, download_files
@ -139,21 +139,21 @@ def download(args):
raise ConsoleError("Invalid input: {}".format(args.video))
def _get_clip_url(clip, args):
def _get_clip_url(clip, quality):
qualities = clip["videoQualities"]
# Quality given as an argument
if args.quality:
if args.quality == "source":
if quality:
if quality == "source":
return qualities[0]["sourceURL"]
selected_quality = args.quality.rstrip("p") # allow 720p as well as 720
selected_quality = quality.rstrip("p") # allow 720p as well as 720
for q in qualities:
if q["quality"] == selected_quality:
return q["sourceURL"]
available = ", ".join([str(q["quality"]) for q in qualities])
msg = "Quality '{}' not found. Available qualities are: {}".format(args.quality, available)
msg = "Quality '{}' not found. Available qualities are: {}".format(quality, available)
raise ConsoleError(msg)
# Ask user to select quality
@ -167,6 +167,23 @@ def _get_clip_url(clip, args):
return selected_quality["sourceURL"]
def get_clip_authenticated_url(slug, quality):
print_out("<dim>Fetching access token...</dim>")
access_token = twitch.get_clip_access_token(slug)
if not access_token:
raise ConsoleError("Access token not found for slug '{}'".format(slug))
url = _get_clip_url(access_token, quality)
query = urlencode({
"sig": access_token["playbackAccessToken"]["signature"],
"token": access_token["playbackAccessToken"]["value"],
})
return "{}?{}".format(url, query)
def _download_clip(slug, args):
print_out("<dim>Looking up clip...</dim>")
clip = twitch.get_clip(slug)
@ -174,6 +191,12 @@ def _download_clip(slug, args):
if not clip:
raise ConsoleError("Clip '{}' not found".format(slug))
print_out("<dim>Fetching access token...</dim>")
access_token = twitch.get_clip_access_token(slug)
if not access_token:
raise ConsoleError("Access token not found for slug '{}'".format(slug))
print_out("Found: <green>{}</green> by <yellow>{}</yellow>, playing <blue>{}</blue> ({})".format(
clip["title"],
clip["broadcaster"]["displayName"],
@ -181,7 +204,7 @@ def _download_clip(slug, args):
utils.format_duration(clip["durationSeconds"])
))
url = _get_clip_url(clip, args)
url = get_clip_authenticated_url(slug, args.quality)
print_out("<dim>Selected URL: {}</dim>".format(url))
target = _clip_target_filename(clip)

View File

@ -51,6 +51,16 @@ def kraken_get(url, params={}, headers={}):
return authenticated_get(url, params, headers)
def gql_post(query):
url = "https://gql.twitch.tv/gql"
response = authenticated_post(url, data=query).json()
if "errors" in response:
raise GQLError(response["errors"])
return response
def gql_query(query):
url = "https://gql.twitch.tv/gql"
response = authenticated_post(url, json={"query": query}).json()
@ -133,6 +143,26 @@ def get_clip(slug):
return response["data"]["clip"]
def get_clip_access_token(slug):
query = """
{{
"operationName": "VideoAccessToken_Clip",
"variables": {{
"slug": "{slug}"
}},
"extensions": {{
"persistedQuery": {{
"version": 1,
"sha256Hash": "36b89d2507fce29e5ca551df756d27c1cfe079e2609642b4390aa4c35796eb11"
}}
}}
}}
"""
response = gql_post(query.format(slug=slug).strip())
return response["data"]["clip"]
def get_channel_clips(channel_id, period, limit, after=None):
"""
List channel clips.