95 lines
3.7 KiB
Python
95 lines
3.7 KiB
Python
import random
|
|
from urllib.parse import urlencode
|
|
|
|
import requests
|
|
|
|
from common.decorators import Plugin
|
|
from engine.plugins import BatchCheckBase, match1
|
|
from engine.plugins.base_adapter import FFmpegdl
|
|
|
|
VALID_URL_BASE = r'(?:https?://)?(?:(?:www|go|m)\.)?twitch\.tv/(?P<id>[0-9_a-zA-Z]+)'
|
|
_OPERATION_HASHES = {
|
|
'CollectionSideBar': '27111f1b382effad0b6def325caef1909c733fe6a4fbabf54f8d491ef2cf2f14',
|
|
'FilterableVideoTower_Videos': 'a937f1d22e269e39a03b509f65a7490f9fc247d7f83d6ac1421523e3b68042cb',
|
|
'ClipsCards__User': 'b73ad2bfaecfd30a9e6c28fada15bd97032c83ec77a0440766a56fe0bd632777',
|
|
'ChannelCollectionsContent': '07e3691a1bad77a36aba590c351180439a40baefc1c275356f40fc7082419a84',
|
|
'StreamMetadata': '1c719a40e481453e5c48d9bb585d971b8b372f8ebb105b17076722264dfa5b3e',
|
|
'ComscoreStreamingQuery': 'e1edae8122517d013405f237ffcc124515dc6ded82480a88daef69c83b53ac01',
|
|
'VideoPreviewOverlay': '3006e77e51b128d838fa4e835723ca4dc9a05c5efd4466c1085215c6e437e65c',
|
|
}
|
|
_CLIENT_ID = 'kimne78kx3ncx6brgo4mv6wki5h1ko'
|
|
|
|
|
|
@Plugin.download(regexp=VALID_URL_BASE)
|
|
class Twitch(FFmpegdl):
|
|
def __init__(self, fname, url, suffix='flv'):
|
|
FFmpegdl.__init__(self, fname, url, suffix=suffix)
|
|
|
|
def check_stream(self):
|
|
if not list(Twitch.BatchCheck([self.url]).check()):
|
|
return
|
|
channel_name = match1(self.url, VALID_URL_BASE)
|
|
r = requests.get(f'https://api.twitch.tv/api/channels/{channel_name}/access_token',
|
|
headers={
|
|
'Accept': 'application/vnd.twitchtv.v5+json; charset=UTF-8',
|
|
'Client-ID': _CLIENT_ID,
|
|
}, timeout=10)
|
|
r.close()
|
|
access_token = r.json()
|
|
token = access_token['token']
|
|
query = {
|
|
'allow_source': 'true',
|
|
'allow_audio_only': 'true',
|
|
'allow_spectre': 'true',
|
|
'p': random.randint(1000000, 10000000),
|
|
'player': 'twitchweb',
|
|
'playlist_include_framerate': 'true',
|
|
'segment_preference': '4',
|
|
'sig': access_token['sig'],
|
|
'token': token,
|
|
}
|
|
self.opt_args = ['-ss', "00:00:16"]
|
|
self.raw_stream_url = f'https://usher.ttvnw.net/api/channel/hls/{channel_name}.m3u8?{urlencode(query)}'
|
|
return True
|
|
|
|
class BatchCheck(BatchCheckBase):
|
|
def __init__(self, urls):
|
|
BatchCheckBase.__init__(self, pattern_id=VALID_URL_BASE, urls=urls)
|
|
|
|
def check(self):
|
|
gql = self.get_streamer()
|
|
i = -1
|
|
for data in gql:
|
|
i += 1
|
|
user = data['data'].get('user')
|
|
if not user:
|
|
continue
|
|
stream = user['stream']
|
|
if not stream:
|
|
continue
|
|
yield self.usr_dict.get(self.usr_list[i])
|
|
|
|
def get_streamer(self):
|
|
ops = []
|
|
for channel_name in self.usr_list:
|
|
op = {
|
|
'operationName': 'StreamMetadata',
|
|
'variables': {'channelLogin': channel_name.lower()}
|
|
}
|
|
op['extensions'] = {
|
|
'persistedQuery': {
|
|
'version': 1,
|
|
'sha256Hash': _OPERATION_HASHES[op['operationName']],
|
|
}
|
|
}
|
|
ops.append(op)
|
|
gql = requests.post(
|
|
'https://gql.twitch.tv/gql',
|
|
json=ops,
|
|
headers={
|
|
'Content-Type': 'text/plain;charset=UTF-8',
|
|
'Client-ID': _CLIENT_ID,
|
|
}, timeout=15)
|
|
gql.close()
|
|
return gql.json()
|