2019-08-23 07:03:30 +00:00
|
|
|
"""
|
|
|
|
Twitch API access.
|
|
|
|
"""
|
|
|
|
|
2018-01-25 10:09:20 +00:00
|
|
|
import requests
|
|
|
|
|
|
|
|
from twitchdl import CLIENT_ID
|
2019-08-12 13:14:13 +00:00
|
|
|
from twitchdl.exceptions import ConsoleError
|
2018-01-25 10:09:20 +00:00
|
|
|
|
|
|
|
|
2019-08-23 07:03:30 +00:00
|
|
|
def authenticated_get(url, params={}, headers={}):
|
|
|
|
headers['Client-ID'] = CLIENT_ID
|
2018-01-25 10:09:20 +00:00
|
|
|
|
|
|
|
response = requests.get(url, params, headers=headers)
|
2019-08-12 13:14:13 +00:00
|
|
|
if response.status_code == 400:
|
|
|
|
data = response.json()
|
|
|
|
raise ConsoleError(data["message"])
|
|
|
|
|
2018-01-25 10:09:20 +00:00
|
|
|
response.raise_for_status()
|
|
|
|
|
|
|
|
return response
|
|
|
|
|
|
|
|
|
2020-04-11 14:07:17 +00:00
|
|
|
def authenticated_post(url, data=None, json=None, headers={}):
|
|
|
|
headers['Client-ID'] = CLIENT_ID
|
|
|
|
|
|
|
|
response = requests.post(url, data=data, json=json, headers=headers)
|
|
|
|
if response.status_code == 400:
|
|
|
|
data = response.json()
|
|
|
|
raise ConsoleError(data["message"])
|
|
|
|
|
|
|
|
response.raise_for_status()
|
|
|
|
|
|
|
|
return response
|
|
|
|
|
|
|
|
|
2019-08-23 07:03:30 +00:00
|
|
|
def kraken_get(url, params={}, headers={}):
|
|
|
|
"""
|
|
|
|
Add accept header required by kraken API v5.
|
|
|
|
see: https://discuss.dev.twitch.tv/t/change-in-access-to-deprecated-kraken-twitch-apis/22241
|
|
|
|
"""
|
|
|
|
headers["Accept"] = "application/vnd.twitchtv.v5+json"
|
|
|
|
return authenticated_get(url, params, headers)
|
|
|
|
|
|
|
|
|
2018-01-25 10:09:20 +00:00
|
|
|
def get_video(video_id):
|
|
|
|
"""
|
|
|
|
https://dev.twitch.tv/docs/v5/reference/videos#get-video
|
|
|
|
"""
|
2020-04-11 14:07:17 +00:00
|
|
|
url = "https://api.twitch.tv/kraken/videos/{}".format(video_id)
|
2018-01-25 10:09:20 +00:00
|
|
|
|
2019-08-23 07:03:30 +00:00
|
|
|
return kraken_get(url).json()
|
2018-01-25 10:09:20 +00:00
|
|
|
|
|
|
|
|
2020-04-11 14:07:17 +00:00
|
|
|
def get_clip(slug):
|
|
|
|
url = "https://gql.twitch.tv/gql"
|
|
|
|
|
|
|
|
query = """
|
|
|
|
{{
|
|
|
|
clip(slug: "{}") {{
|
|
|
|
title
|
|
|
|
durationSeconds
|
|
|
|
game {{
|
|
|
|
name
|
|
|
|
}}
|
|
|
|
broadcaster {{
|
|
|
|
login
|
|
|
|
displayName
|
|
|
|
}}
|
|
|
|
videoQualities {{
|
|
|
|
frameRate
|
|
|
|
quality
|
|
|
|
sourceURL
|
|
|
|
}}
|
|
|
|
}}
|
|
|
|
}}
|
|
|
|
"""
|
|
|
|
|
|
|
|
payload = {"query": query.format(slug)}
|
|
|
|
data = authenticated_post(url, json=payload).json()
|
|
|
|
return data["data"]["clip"]
|
|
|
|
|
|
|
|
|
2020-05-17 11:35:51 +00:00
|
|
|
def get_channel_videos(channel_id, limit, sort, type="archive", after=None):
|
2020-05-17 09:56:21 +00:00
|
|
|
url = "https://gql.twitch.tv/gql"
|
|
|
|
|
|
|
|
query = """
|
|
|
|
{{
|
|
|
|
user(login: "{channel_id}") {{
|
2020-05-17 11:35:51 +00:00
|
|
|
videos(options: {{ }}, first: {limit}, type: {type}, sort: {sort}, after: "{after}") {{
|
2020-05-17 09:56:21 +00:00
|
|
|
totalCount
|
|
|
|
edges {{
|
|
|
|
cursor
|
|
|
|
node {{
|
|
|
|
id
|
|
|
|
title
|
|
|
|
publishedAt
|
|
|
|
broadcastType
|
|
|
|
lengthSeconds
|
|
|
|
game {{
|
|
|
|
name
|
|
|
|
}}
|
|
|
|
creator {{
|
|
|
|
channel {{
|
|
|
|
displayName
|
|
|
|
}}
|
|
|
|
}}
|
|
|
|
}}
|
|
|
|
}}
|
|
|
|
}}
|
|
|
|
}}
|
|
|
|
}}
|
2018-01-25 10:09:20 +00:00
|
|
|
"""
|
|
|
|
|
2020-05-17 09:56:21 +00:00
|
|
|
query = query.format(**{
|
|
|
|
"channel_id": channel_id,
|
2020-05-17 11:35:51 +00:00
|
|
|
"after": after,
|
2018-01-25 10:09:20 +00:00
|
|
|
"limit": limit,
|
2020-05-17 09:56:21 +00:00
|
|
|
"sort": sort.upper(),
|
2020-05-17 11:35:51 +00:00
|
|
|
"type": type.upper(),
|
2020-05-17 09:56:21 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
response = authenticated_post(url, json={"query": query}).json()
|
|
|
|
return response["data"]["user"]["videos"]
|
2018-01-25 10:09:20 +00:00
|
|
|
|
|
|
|
|
2020-05-17 11:35:51 +00:00
|
|
|
def channel_videos_generator(channel_id, limit, sort, type):
|
|
|
|
cursor = None
|
|
|
|
while True:
|
|
|
|
videos = get_channel_videos(channel_id, limit, sort, type, after=cursor)
|
|
|
|
cursor = videos["edges"][-1]["cursor"]
|
|
|
|
|
|
|
|
yield videos, cursor is not None
|
|
|
|
|
|
|
|
if not cursor:
|
|
|
|
break
|
|
|
|
|
|
|
|
|
2018-01-25 10:09:20 +00:00
|
|
|
def get_access_token(video_id):
|
2020-04-11 14:07:17 +00:00
|
|
|
url = "https://api.twitch.tv/api/vods/{}/access_token".format(video_id)
|
2018-01-25 10:09:20 +00:00
|
|
|
|
|
|
|
return authenticated_get(url).json()
|
|
|
|
|
|
|
|
|
|
|
|
def get_playlists(video_id, access_token):
|
2019-08-23 10:36:05 +00:00
|
|
|
"""
|
|
|
|
For a given video return a playlist which contains possible video qualities.
|
|
|
|
"""
|
2018-01-25 10:09:20 +00:00
|
|
|
url = "http://usher.twitch.tv/vod/{}".format(video_id)
|
|
|
|
|
|
|
|
response = requests.get(url, params={
|
|
|
|
"nauth": access_token['token'],
|
|
|
|
"nauthsig": access_token['sig'],
|
|
|
|
"allow_source": "true",
|
|
|
|
"player": "twitchweb",
|
|
|
|
})
|
|
|
|
response.raise_for_status()
|
2019-08-23 10:36:05 +00:00
|
|
|
return response.content.decode('utf-8')
|